Application Data Protection as a Service in OpenStack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

providers.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. """The providers api."""
  13. from oslo_config import cfg
  14. from oslo_log import log as logging
  15. from oslo_serialization import jsonutils
  16. from oslo_utils import uuidutils
  17. from webob import exc
  18. from karbor.api import common
  19. from karbor.api.openstack import wsgi
  20. from karbor.api.schemas import checkpoints as checkpoint_schema
  21. from karbor.api import validation
  22. from karbor.common import constants
  23. from karbor import exception
  24. from karbor.i18n import _
  25. from karbor import objects
  26. from karbor.policies import providers as provider_policy
  27. from karbor.services.protection import api as protection_api
  28. from karbor import utils
  29. import six
  30. query_provider_filters_opts = [
  31. cfg.ListOpt(
  32. 'query_provider_filters',
  33. default=['name', 'description'],
  34. help=(
  35. "Provider filter options which non-admin user could use to "
  36. "query providers. Default values are: ['name', 'description']"
  37. )
  38. ),
  39. ]
  40. query_checkpoint_filters_opts = [
  41. cfg.ListOpt(
  42. 'query_checkpoint_filters',
  43. default=['project_id', 'plan_id', 'start_date', 'end_date'],
  44. help=(
  45. "Checkpoint filter options which non-admin user could use to "
  46. "query checkpoints. Default values are: ['project_id', "
  47. "'plan_id', 'start_date', 'end_date']"
  48. )
  49. ),
  50. ]
  51. CONF = cfg.CONF
  52. CONF.register_opts(query_provider_filters_opts)
  53. CONF.register_opts(query_checkpoint_filters_opts)
  54. LOG = logging.getLogger(__name__)
  55. class ProviderViewBuilder(common.ViewBuilder):
  56. """Model a server API response as a python dictionary."""
  57. _collection_name = "providers"
  58. def detail(self, request, provider):
  59. """Detailed view of a single provider."""
  60. provider_ref = {
  61. 'provider': {
  62. 'id': provider.get('id'),
  63. 'name': provider.get('name'),
  64. 'description': provider.get('description'),
  65. 'extended_info_schema': provider.get('extended_info_schema'),
  66. }
  67. }
  68. return provider_ref
  69. def detail_list(self, request, providers, provider_count=None):
  70. """Detailed view of a list of providers."""
  71. return self._list_view(self.detail, request, providers,
  72. provider_count,
  73. self._collection_name)
  74. def _list_view(self, func, request, providers, provider_count,
  75. coll_name=_collection_name):
  76. """Provide a view for a list of provider.
  77. :param func: Function used to format the provider data
  78. :param request: API request
  79. :param providers: List of providers in dictionary format
  80. :param provider_count: Length of the original list of providers
  81. :param coll_name: Name of collection, used to generate the next link
  82. for a pagination query
  83. :returns: Provider data in dictionary format
  84. """
  85. providers_list = [func(request, provider)['provider']
  86. for provider in providers]
  87. providers_links = self._get_collection_links(request,
  88. providers,
  89. coll_name,
  90. provider_count)
  91. providers_dict = {
  92. "providers": providers_list
  93. }
  94. if providers_links:
  95. providers_dict['providers_links'] = providers_links
  96. return providers_dict
  97. class CheckpointViewBuilder(common.ViewBuilder):
  98. """Model a server API response as a python dictionary."""
  99. _collection_name = "checkpoints"
  100. def detail(self, request, checkpoint):
  101. """Detailed view of a single checkpoint."""
  102. checkpoint_ref = {
  103. 'checkpoint': {
  104. 'id': checkpoint.get('id'),
  105. 'project_id': checkpoint.get('project_id'),
  106. 'status': checkpoint.get('status'),
  107. 'protection_plan': checkpoint.get('protection_plan'),
  108. 'resource_graph': checkpoint.get('resource_graph'),
  109. 'created_at': checkpoint.get('created_at'),
  110. 'extra_info': checkpoint.get('extra_info'),
  111. }
  112. }
  113. return checkpoint_ref
  114. def detail_list(self, request, checkpoints, checkpoint_count=None):
  115. """Detailed view of a list of checkpoints."""
  116. return self._list_view(self.detail, request, checkpoints,
  117. checkpoint_count,
  118. self._collection_name)
  119. def _list_view(self, func, request, checkpoints, checkpoint_count,
  120. coll_name=_collection_name):
  121. """Provide a view for a list of checkpoint.
  122. :param func: Function used to format the checkpoint data
  123. :param request: API request
  124. :param checkpoints: List of checkpoints in dictionary format
  125. :param checkpoint_count: Length of the original list of checkpoints
  126. :param coll_name: Name of collection, used to generate the next link
  127. for a pagination query
  128. :returns: Checkpoint data in dictionary format
  129. """
  130. checkpoints_list = [func(request, checkpoint)['checkpoint']
  131. for checkpoint in checkpoints]
  132. checkpoints_links = self._get_collection_links(request,
  133. checkpoints,
  134. coll_name,
  135. checkpoint_count)
  136. checkpoints_dict = {
  137. "checkpoints": checkpoints_list
  138. }
  139. if checkpoints_links:
  140. checkpoints_dict['checkpoints_links'] = checkpoints_links
  141. return checkpoints_dict
  142. class ProvidersController(wsgi.Controller):
  143. """The Providers API controller for the OpenStack API."""
  144. _view_builder_class = ProviderViewBuilder
  145. def __init__(self):
  146. self.protection_api = protection_api.API()
  147. self._checkpoint_view_builder = CheckpointViewBuilder()
  148. super(ProvidersController, self).__init__()
  149. def show(self, req, id):
  150. """Return data about the given provider id."""
  151. context = req.environ['karbor.context']
  152. LOG.info("Show provider with id: %s", id)
  153. try:
  154. provider = self._provider_get(context, id)
  155. except exception.ProviderNotFound as error:
  156. raise exc.HTTPNotFound(explanation=error.msg)
  157. LOG.info("Show provider request issued successfully.",
  158. resource={'id': provider.get("id")})
  159. return self._view_builder.detail(req, provider)
  160. def index(self, req):
  161. """Returns a list of providers, transformed through view builder."""
  162. context = req.environ['karbor.context']
  163. LOG.info("Show provider list", context=context)
  164. params = req.params.copy()
  165. marker, limit, offset = common.get_pagination_params(params)
  166. sort_keys, sort_dirs = common.get_sort_params(params)
  167. filters = params
  168. utils.remove_invalid_filter_options(
  169. context,
  170. filters,
  171. self._get_provider_filter_options())
  172. utils.check_filters(filters)
  173. providers = self._get_all(context, marker, limit,
  174. sort_keys=sort_keys,
  175. sort_dirs=sort_dirs,
  176. filters=filters,
  177. offset=offset)
  178. retval_providers = self._view_builder.detail_list(req, providers)
  179. LOG.info("Show provider list request issued successfully.")
  180. return retval_providers
  181. def _get_all(self, context, marker=None, limit=None, sort_keys=None,
  182. sort_dirs=None, filters=None, offset=None):
  183. context.can(provider_policy.GET_ALL_POLICY)
  184. if filters is None:
  185. filters = {}
  186. try:
  187. if limit is not None:
  188. limit = int(limit)
  189. if limit <= 0:
  190. msg = _('limit param must be positive')
  191. raise exception.InvalidInput(reason=msg)
  192. except ValueError:
  193. msg = _('limit param must be an integer')
  194. raise exception.InvalidInput(reason=msg)
  195. if filters:
  196. LOG.debug("Searching by: %s.", six.text_type(filters))
  197. providers = self.protection_api.list_providers(
  198. context, marker, limit,
  199. sort_keys=sort_keys,
  200. sort_dirs=sort_dirs,
  201. filters=filters,
  202. offset=offset)
  203. LOG.info("Get all providers completed successfully.")
  204. return providers
  205. def _get_provider_filter_options(self):
  206. """Return provider search options allowed by non-admin."""
  207. return CONF.query_provider_filters
  208. def _get_checkpoint_filter_options(self):
  209. """Return checkpoint search options allowed by non-admin."""
  210. return CONF.query_checkpoint_filters
  211. def _provider_get(self, context, provider_id):
  212. if not uuidutils.is_uuid_like(provider_id):
  213. msg = _("Invalid provider id provided.")
  214. raise exc.HTTPBadRequest(explanation=msg)
  215. try:
  216. context.can(provider_policy.GET_POLICY)
  217. except exception.PolicyNotAuthorized:
  218. # raise ProviderNotFound instead to make sure karbor behaves
  219. # as it used to
  220. raise exception.ProviderNotFound(provider_id=provider_id)
  221. provider = self.protection_api.show_provider(context, provider_id)
  222. LOG.info("Provider info retrieved successfully.")
  223. return provider
  224. def checkpoints_index(self, req, provider_id):
  225. """Returns a list of checkpoints, transformed through view builder."""
  226. context = req.environ['karbor.context']
  227. LOG.info("Show checkpoints list. provider_id:%s", provider_id)
  228. params = req.params.copy()
  229. marker, limit, offset = common.get_pagination_params(params)
  230. sort_keys, sort_dirs = common.get_sort_params(params)
  231. filters = params
  232. utils.remove_invalid_filter_options(
  233. context,
  234. filters,
  235. self._get_checkpoint_filter_options())
  236. utils.check_filters(filters)
  237. checkpoints = self._checkpoints_get_all(
  238. context, provider_id, marker, limit,
  239. sort_keys=sort_keys, sort_dirs=sort_dirs,
  240. filters=filters, offset=offset)
  241. retval_checkpoints = self._checkpoint_view_builder.detail_list(
  242. req, checkpoints)
  243. LOG.info("Show checkpoints list request issued successfully.")
  244. return retval_checkpoints
  245. def _checkpoints_get_all(self, context, provider_id, marker=None,
  246. limit=None, sort_keys=None, sort_dirs=None,
  247. filters=None, offset=None):
  248. context.can(provider_policy.CHECKPOINT_GET_ALL_POLICY)
  249. if filters is None:
  250. filters = {}
  251. all_tenants = utils.get_bool_param(
  252. 'all_tenants', filters) and context.is_admin
  253. try:
  254. if limit is not None:
  255. limit = int(limit)
  256. if limit <= 0:
  257. msg = _('limit param must be positive')
  258. raise exception.InvalidInput(reason=msg)
  259. except ValueError:
  260. msg = _('limit param must be an integer')
  261. raise exception.InvalidInput(reason=msg)
  262. if filters:
  263. LOG.debug("Searching by: %s.", six.text_type(filters))
  264. if all_tenants:
  265. del filters['all_tenants']
  266. checkpoints = self.protection_api.list_checkpoints(
  267. context, provider_id, marker, limit,
  268. sort_keys=sort_keys,
  269. sort_dirs=sort_dirs,
  270. filters=filters,
  271. offset=offset,
  272. all_tenants=all_tenants
  273. )
  274. LOG.info("Get all checkpoints completed successfully.")
  275. return checkpoints
  276. @validation.schema(checkpoint_schema.create)
  277. def checkpoints_create(self, req, provider_id, body):
  278. """Creates a new checkpoint."""
  279. context = req.environ['karbor.context']
  280. LOG.debug('Create checkpoint request '
  281. 'body: %s provider_id:%s', body, provider_id)
  282. context.can(provider_policy.CHECKPOINT_CREATE_POLICY)
  283. checkpoint = body['checkpoint']
  284. LOG.debug('Create checkpoint request checkpoint: %s',
  285. checkpoint)
  286. if not provider_id:
  287. msg = _("provider_id must be provided when creating "
  288. "a checkpoint.")
  289. raise exception.InvalidInput(reason=msg)
  290. plan_id = checkpoint.get("plan_id")
  291. plan = objects.Plan.get_by_id(context, plan_id)
  292. if not plan:
  293. raise exception.PlanNotFound(plan_id=plan_id)
  294. # check the provider_id
  295. if provider_id != plan.get("provider_id"):
  296. msg = _("The parameter provider_id is not the same as "
  297. "the value in the plan.")
  298. raise exception.InvalidPlan(reason=msg)
  299. extra_info = checkpoint.get("extra_info", None)
  300. if extra_info is not None:
  301. if not isinstance(extra_info, dict):
  302. msg = _("The extra_info in checkpoint must be a dict when "
  303. "creating a checkpoint.")
  304. raise exception.InvalidInput(reason=msg)
  305. elif not all(map(lambda s: isinstance(s, six.string_types),
  306. extra_info.keys())):
  307. msg = _("Key of extra_info in checkpoint must be string when"
  308. "creating a checkpoint.")
  309. raise exception.InvalidInput(reason=msg)
  310. else:
  311. extra_info = {
  312. 'created_by': constants.MANUAL
  313. }
  314. checkpoint_extra_info = None
  315. if extra_info is not None:
  316. checkpoint_extra_info = jsonutils.dumps(extra_info)
  317. checkpoint_properties = {
  318. 'project_id': context.project_id,
  319. 'status': constants.CHECKPOINT_STATUS_PROTECTING,
  320. 'provider_id': provider_id,
  321. "protection_plan": {
  322. "id": plan.get("id"),
  323. "name": plan.get("name"),
  324. "resources": plan.get("resources"),
  325. },
  326. "extra_info": checkpoint_extra_info
  327. }
  328. try:
  329. checkpoint_id = self.protection_api.protect(context, plan,
  330. checkpoint_properties)
  331. except Exception as error:
  332. msg = _("Create checkpoint failed: %s") % error
  333. raise exc.HTTPBadRequest(explanation=msg)
  334. checkpoint_properties['id'] = checkpoint_id
  335. LOG.info("Create the checkpoint successfully. checkpoint_id:%s",
  336. checkpoint_id)
  337. returnval = self._checkpoint_view_builder.detail(
  338. req, checkpoint_properties)
  339. return returnval
  340. def checkpoints_show(self, req, provider_id, checkpoint_id):
  341. """Return data about the given checkpoint id."""
  342. context = req.environ['karbor.context']
  343. LOG.info("Show checkpoint with id: %s.", checkpoint_id)
  344. LOG.info("provider_id: %s.", provider_id)
  345. try:
  346. checkpoint = self._checkpoint_get(context, provider_id,
  347. checkpoint_id)
  348. except exception.CheckpointNotFound as error:
  349. raise exc.HTTPNotFound(explanation=error.msg)
  350. except exception.AccessCheckpointNotAllowed as error:
  351. raise exc.HTTPForbidden(explanation=error.msg)
  352. LOG.info("Show checkpoint request issued successfully.")
  353. LOG.info("checkpoint: %s", checkpoint)
  354. retval = self._checkpoint_view_builder.detail(req, checkpoint)
  355. LOG.info("retval: %s", retval)
  356. return retval
  357. def _checkpoint_get(self, context, provider_id, checkpoint_id):
  358. if not uuidutils.is_uuid_like(provider_id):
  359. msg = _("Invalid provider id provided.")
  360. raise exc.HTTPBadRequest(explanation=msg)
  361. if not uuidutils.is_uuid_like(checkpoint_id):
  362. msg = _("Invalid checkpoint id provided.")
  363. raise exc.HTTPBadRequest(explanation=msg)
  364. try:
  365. context.can(provider_policy.CHECKPOINT_GET_POLICY)
  366. except exception.PolicyNotAuthorized:
  367. # raise CheckpointNotFound instead to make sure karbor behaves
  368. # as it used to
  369. raise exception.CheckpointNotFound(checkpoint_id=checkpoint_id)
  370. checkpoint = self.protection_api.show_checkpoint(
  371. context, provider_id, checkpoint_id)
  372. if checkpoint is None:
  373. raise exception.CheckpointNotFound(checkpoint_id=checkpoint_id)
  374. LOG.info("Checkpoint info retrieved successfully.")
  375. return checkpoint
  376. def checkpoints_delete(self, req, provider_id, checkpoint_id):
  377. """Delete a checkpoint."""
  378. context = req.environ['karbor.context']
  379. LOG.info("Delete checkpoint with id: %s.", checkpoint_id)
  380. LOG.info("provider_id: %s.", provider_id)
  381. if not uuidutils.is_uuid_like(provider_id):
  382. msg = _("Invalid provider id provided.")
  383. raise exc.HTTPBadRequest(explanation=msg)
  384. if not uuidutils.is_uuid_like(checkpoint_id):
  385. msg = _("Invalid checkpoint id provided.")
  386. raise exc.HTTPBadRequest(explanation=msg)
  387. context.can(provider_policy.CHECKPOINT_DELETE_POLICY)
  388. try:
  389. self.protection_api.delete(context, provider_id, checkpoint_id)
  390. except exception.DeleteCheckpointNotAllowed as error:
  391. raise exc.HTTPForbidden(explantion=error.msg)
  392. LOG.info("Delete checkpoint request issued successfully.")
  393. return {}
  394. def create_resource():
  395. return wsgi.Resource(ProvidersController())