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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. try:
  252. if limit is not None:
  253. limit = int(limit)
  254. if limit <= 0:
  255. msg = _('limit param must be positive')
  256. raise exception.InvalidInput(reason=msg)
  257. except ValueError:
  258. msg = _('limit param must be an integer')
  259. raise exception.InvalidInput(reason=msg)
  260. if filters:
  261. LOG.debug("Searching by: %s.", six.text_type(filters))
  262. checkpoints = self.protection_api.list_checkpoints(
  263. context, provider_id, marker, limit,
  264. sort_keys=sort_keys,
  265. sort_dirs=sort_dirs,
  266. filters=filters,
  267. offset=offset)
  268. LOG.info("Get all checkpoints completed successfully.")
  269. return checkpoints
  270. @validation.schema(checkpoint_schema.create)
  271. def checkpoints_create(self, req, provider_id, body):
  272. """Creates a new checkpoint."""
  273. context = req.environ['karbor.context']
  274. LOG.debug('Create checkpoint request '
  275. 'body: %s provider_id:%s', body, provider_id)
  276. context.can(provider_policy.CHECKPOINT_CREATE_POLICY)
  277. checkpoint = body['checkpoint']
  278. LOG.debug('Create checkpoint request checkpoint: %s',
  279. checkpoint)
  280. if not provider_id:
  281. msg = _("provider_id must be provided when creating "
  282. "a checkpoint.")
  283. raise exception.InvalidInput(reason=msg)
  284. plan_id = checkpoint.get("plan_id")
  285. plan = objects.Plan.get_by_id(context, plan_id)
  286. if not plan:
  287. raise exception.PlanNotFound(plan_id=plan_id)
  288. # check the provider_id
  289. if provider_id != plan.get("provider_id"):
  290. msg = _("The parameter provider_id is not the same as "
  291. "the value in the plan.")
  292. raise exception.InvalidPlan(reason=msg)
  293. extra_info = checkpoint.get("extra_info", None)
  294. if extra_info is not None:
  295. if not isinstance(extra_info, dict):
  296. msg = _("The extra_info in checkpoint must be a dict when "
  297. "creating a checkpoint.")
  298. raise exception.InvalidInput(reason=msg)
  299. elif not all(map(lambda s: isinstance(s, six.string_types),
  300. extra_info.keys())):
  301. msg = _("Key of extra_info in checkpoint must be string when"
  302. "creating a checkpoint.")
  303. raise exception.InvalidInput(reason=msg)
  304. else:
  305. extra_info = {
  306. 'created_by': constants.MANUAL
  307. }
  308. checkpoint_extra_info = None
  309. if extra_info is not None:
  310. checkpoint_extra_info = jsonutils.dumps(extra_info)
  311. checkpoint_properties = {
  312. 'project_id': context.project_id,
  313. 'status': constants.CHECKPOINT_STATUS_PROTECTING,
  314. 'provider_id': provider_id,
  315. "protection_plan": {
  316. "id": plan.get("id"),
  317. "name": plan.get("name"),
  318. "resources": plan.get("resources"),
  319. },
  320. "extra_info": checkpoint_extra_info
  321. }
  322. try:
  323. checkpoint_id = self.protection_api.protect(context, plan,
  324. checkpoint_properties)
  325. except Exception as error:
  326. msg = _("Create checkpoint failed: %s") % error
  327. raise exc.HTTPBadRequest(explanation=msg)
  328. checkpoint_properties['id'] = checkpoint_id
  329. LOG.info("Create the checkpoint successfully. checkpoint_id:%s",
  330. checkpoint_id)
  331. returnval = self._checkpoint_view_builder.detail(
  332. req, checkpoint_properties)
  333. return returnval
  334. def checkpoints_show(self, req, provider_id, checkpoint_id):
  335. """Return data about the given checkpoint id."""
  336. context = req.environ['karbor.context']
  337. LOG.info("Show checkpoint with id: %s.", checkpoint_id)
  338. LOG.info("provider_id: %s.", provider_id)
  339. try:
  340. checkpoint = self._checkpoint_get(context, provider_id,
  341. checkpoint_id)
  342. except exception.CheckpointNotFound as error:
  343. raise exc.HTTPNotFound(explanation=error.msg)
  344. except exception.AccessCheckpointNotAllowed as error:
  345. raise exc.HTTPForbidden(explanation=error.msg)
  346. LOG.info("Show checkpoint request issued successfully.")
  347. LOG.info("checkpoint: %s", checkpoint)
  348. retval = self._checkpoint_view_builder.detail(req, checkpoint)
  349. LOG.info("retval: %s", retval)
  350. return retval
  351. def _checkpoint_get(self, context, provider_id, checkpoint_id):
  352. if not uuidutils.is_uuid_like(provider_id):
  353. msg = _("Invalid provider id provided.")
  354. raise exc.HTTPBadRequest(explanation=msg)
  355. if not uuidutils.is_uuid_like(checkpoint_id):
  356. msg = _("Invalid checkpoint id provided.")
  357. raise exc.HTTPBadRequest(explanation=msg)
  358. try:
  359. context.can(provider_policy.CHECKPOINT_GET_POLICY)
  360. except exception.PolicyNotAuthorized:
  361. # raise CheckpointNotFound instead to make sure karbor behaves
  362. # as it used to
  363. raise exception.CheckpointNotFound(checkpoint_id=checkpoint_id)
  364. checkpoint = self.protection_api.show_checkpoint(
  365. context, provider_id, checkpoint_id)
  366. if checkpoint is None:
  367. raise exception.CheckpointNotFound(checkpoint_id=checkpoint_id)
  368. LOG.info("Checkpoint info retrieved successfully.")
  369. return checkpoint
  370. def checkpoints_delete(self, req, provider_id, checkpoint_id):
  371. """Delete a checkpoint."""
  372. context = req.environ['karbor.context']
  373. LOG.info("Delete checkpoint with id: %s.", checkpoint_id)
  374. LOG.info("provider_id: %s.", provider_id)
  375. if not uuidutils.is_uuid_like(provider_id):
  376. msg = _("Invalid provider id provided.")
  377. raise exc.HTTPBadRequest(explanation=msg)
  378. if not uuidutils.is_uuid_like(checkpoint_id):
  379. msg = _("Invalid checkpoint id provided.")
  380. raise exc.HTTPBadRequest(explanation=msg)
  381. context.can(provider_policy.CHECKPOINT_DELETE_POLICY)
  382. try:
  383. self.protection_api.delete(context, provider_id, checkpoint_id)
  384. except exception.DeleteCheckpointNotAllowed as error:
  385. raise exc.HTTPForbidden(explantion=error.msg)
  386. LOG.info("Delete checkpoint request issued successfully.")
  387. return {}
  388. def create_resource():
  389. return wsgi.Resource(ProvidersController())