Fuel UI
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.

cluster.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2013 Mirantis, Inc.
  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.
  15. """
  16. Handlers dealing with clusters
  17. """
  18. import traceback
  19. import web
  20. from nailgun.api.v1.handlers.base import BaseHandler
  21. from nailgun.api.v1.handlers.base import CollectionHandler
  22. from nailgun.api.v1.handlers.base import DeferredTaskHandler
  23. from nailgun.api.v1.handlers.base import handle_errors
  24. from nailgun.api.v1.handlers.base import OrchestratorDeploymentTasksHandler
  25. from nailgun.api.v1.handlers.base import serialize
  26. from nailgun.api.v1.handlers.base import SingleHandler
  27. from nailgun.api.v1.handlers.base import validate
  28. from nailgun.api.v1.handlers.deployment_graph import \
  29. RelatedDeploymentGraphCollectionHandler
  30. from nailgun.api.v1.handlers.deployment_graph import \
  31. RelatedDeploymentGraphHandler
  32. from nailgun.api.v1.validators.cluster import ClusterAttributesValidator
  33. from nailgun.api.v1.validators.cluster import ClusterChangesValidator
  34. from nailgun.api.v1.validators.cluster import ClusterStopDeploymentValidator
  35. from nailgun.api.v1.validators.cluster import ClusterValidator
  36. from nailgun.api.v1.validators.extension import ExtensionValidator
  37. from nailgun import errors
  38. from nailgun.extensions import remove_extensions_from_object
  39. from nailgun.extensions import update_extensions_for_object
  40. from nailgun.logger import logger
  41. from nailgun import objects
  42. from nailgun import utils
  43. from nailgun.task.manager import ApplyChangesTaskManager
  44. from nailgun.task.manager import ClusterDeletionManager
  45. from nailgun.task.manager import ResetEnvironmentTaskManager
  46. from nailgun.task.manager import StopDeploymentTaskManager
  47. class ClusterHandler(SingleHandler):
  48. """Cluster single handler"""
  49. single = objects.Cluster
  50. validator = ClusterValidator
  51. @handle_errors
  52. @validate
  53. @serialize
  54. def PUT(self, obj_id):
  55. """:returns: JSONized Cluster object.
  56. :http: * 200 (OK)
  57. * 400 (error occured while processing of data)
  58. * 404 (cluster not found in db)
  59. """
  60. obj = self.get_object_or_404(self.single, obj_id)
  61. data = self.checked_data(
  62. self.validator.validate_update,
  63. instance=obj
  64. )
  65. # NOTE(aroma):if node is being assigned to the cluster, and if network
  66. # template has been set for the cluster, network template will
  67. # also be applied to node; in such case relevant errors might
  68. # occur so they must be handled in order to form proper HTTP
  69. # response for user
  70. try:
  71. self.single.update(obj, data)
  72. except errors.NetworkTemplateCannotBeApplied as exc:
  73. raise self.http(400, exc.message)
  74. return self.single.to_dict(obj)
  75. @handle_errors
  76. @validate
  77. def DELETE(self, obj_id):
  78. """:returns: {}
  79. :http: * 202 (cluster deletion process launched)
  80. * 400 (failed to execute cluster deletion process)
  81. * 404 (cluster not found in db)
  82. """
  83. cluster = self.get_object_or_404(self.single, obj_id)
  84. task_manager = ClusterDeletionManager(cluster_id=cluster.id)
  85. try:
  86. logger.debug('Trying to execute cluster deletion task')
  87. task = task_manager.execute(
  88. force=utils.parse_bool(web.input(force='0').force)
  89. )
  90. except Exception as e:
  91. logger.warn('Error while execution '
  92. 'cluster deletion task: %s' % str(e))
  93. logger.warn(traceback.format_exc())
  94. raise self.http(400, str(e))
  95. raise self.http(202, objects.Task.to_json(task))
  96. class ClusterCollectionHandler(CollectionHandler):
  97. """Cluster collection handler"""
  98. collection = objects.ClusterCollection
  99. validator = ClusterValidator
  100. class ClusterChangesHandler(DeferredTaskHandler):
  101. log_message = u"Trying to start deployment at environment '{env_id}'"
  102. log_error = u"Error during execution of deployment " \
  103. u"task on environment '{env_id}': {error}"
  104. task_manager = ApplyChangesTaskManager
  105. validator = ClusterChangesValidator
  106. @classmethod
  107. def get_transaction_options(cls, cluster, options):
  108. """Find sequence 'default' to use for deploy-changes handler."""
  109. sequence = objects.DeploymentSequence.get_by_name_for_release(
  110. cluster.release, 'deploy-changes'
  111. )
  112. if sequence:
  113. return {
  114. 'dry_run': options['dry_run'],
  115. 'noop_run': options['noop_run'],
  116. 'force': options['force'],
  117. 'graphs': sequence.graphs,
  118. }
  119. @classmethod
  120. def get_options(cls):
  121. data = web.input(graph_type=None, dry_run="0", noop_run="0")
  122. return {
  123. 'graph_type': data.graph_type or None,
  124. 'force': False,
  125. 'dry_run': utils.parse_bool(data.dry_run),
  126. 'noop_run': utils.parse_bool(data.noop_run),
  127. }
  128. class ClusterChangesForceRedeployHandler(ClusterChangesHandler):
  129. log_message = u"Trying to force deployment of the environment '{env_id}'"
  130. log_error = u"Error during execution of a forced deployment task " \
  131. u"on environment '{env_id}': {error}"
  132. @classmethod
  133. def get_options(cls):
  134. data = web.input(graph_type=None, dry_run="0", noop_run="0")
  135. return {
  136. 'graph_type': data.graph_type or None,
  137. 'force': True,
  138. 'dry_run': utils.parse_bool(data.dry_run),
  139. 'noop_run': utils.parse_bool(data.noop_run),
  140. }
  141. class ClusterStopDeploymentHandler(DeferredTaskHandler):
  142. log_message = u"Trying to stop deployment on environment '{env_id}'"
  143. log_error = u"Error during execution of deployment " \
  144. u"stopping task on environment '{env_id}': {error}"
  145. task_manager = StopDeploymentTaskManager
  146. validator = ClusterStopDeploymentValidator
  147. class ClusterResetHandler(DeferredTaskHandler):
  148. log_message = u"Trying to reset environment '{env_id}'"
  149. log_error = u"Error during execution of resetting task " \
  150. u"on environment '{env_id}': {error}"
  151. task_manager = ResetEnvironmentTaskManager
  152. @classmethod
  153. def get_options(cls):
  154. return {
  155. 'force': utils.parse_bool(web.input(force='0').force)
  156. }
  157. class ClusterAttributesHandler(BaseHandler):
  158. """Cluster attributes handler"""
  159. fields = (
  160. "editable",
  161. )
  162. validator = ClusterAttributesValidator
  163. @handle_errors
  164. @validate
  165. @serialize
  166. def GET(self, cluster_id):
  167. """:returns: JSONized Cluster attributes.
  168. :http: * 200 (OK)
  169. * 404 (cluster not found in db)
  170. * 500 (cluster has no attributes)
  171. """
  172. cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  173. if not cluster.attributes:
  174. raise self.http(500, "No attributes found!")
  175. return {
  176. 'editable': objects.Cluster.get_editable_attributes(
  177. cluster, all_plugins_versions=True)
  178. }
  179. def PUT(self, cluster_id):
  180. """:returns: JSONized Cluster attributes.
  181. :http: * 200 (OK)
  182. * 400 (wrong attributes data specified)
  183. * 404 (cluster not found in db)
  184. * 500 (cluster has no attributes)
  185. """
  186. # Due to the fact that we don't support PATCH requests and we're
  187. # using PUT requests for the same purpose with non-complete data,
  188. # let's follow DRY principle and call PATCH handler for now.
  189. # In future, we have to use PUT method for overwrite the whole
  190. # entity and PATCH method for changing its parts.
  191. return self.PATCH(cluster_id)
  192. @handle_errors
  193. @validate
  194. @serialize
  195. def PATCH(self, cluster_id):
  196. """:returns: JSONized Cluster attributes.
  197. :http: * 200 (OK)
  198. * 400 (wrong attributes data specified)
  199. * 403 (attribute changing is not allowed)
  200. * 404 (cluster not found in db)
  201. * 500 (cluster has no attributes)
  202. """
  203. cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  204. if not cluster.attributes:
  205. raise self.http(500, "No attributes found!")
  206. force = utils.parse_bool(web.input(force='0').force)
  207. data = self.checked_data(cluster=cluster, force=force)
  208. try:
  209. objects.Cluster.patch_attributes(cluster, data)
  210. except errors.NailgunException as exc:
  211. raise self.http(400, exc.message)
  212. return {
  213. 'editable': objects.Cluster.get_editable_attributes(
  214. cluster, all_plugins_versions=True)
  215. }
  216. class ClusterAttributesDefaultsHandler(BaseHandler):
  217. """Cluster default attributes handler"""
  218. fields = (
  219. "editable",
  220. )
  221. @handle_errors
  222. @validate
  223. @serialize
  224. def GET(self, cluster_id):
  225. """:returns: JSONized default Cluster attributes.
  226. :http: * 200 (OK)
  227. * 404 (cluster not found in db)
  228. * 500 (cluster has no attributes)
  229. """
  230. cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  231. attrs = objects.Cluster.get_default_editable_attributes(cluster)
  232. if not attrs:
  233. raise self.http(500, "No attributes found!")
  234. return {"editable": attrs}
  235. @handle_errors
  236. @validate
  237. @serialize
  238. def PUT(self, cluster_id):
  239. """:returns: JSONized Cluster attributes.
  240. :http: * 200 (OK)
  241. * 400 (wrong attributes data specified)
  242. * 404 (cluster not found in db)
  243. * 500 (cluster has no attributes)
  244. """
  245. cluster = self.get_object_or_404(
  246. objects.Cluster,
  247. cluster_id,
  248. log_404=(
  249. "error",
  250. "There is no cluster "
  251. "with id '{0}' in DB.".format(cluster_id)
  252. )
  253. )
  254. if not cluster.attributes:
  255. logger.error('ClusterAttributesDefaultsHandler: no attributes'
  256. ' found for cluster_id %s' % cluster_id)
  257. raise self.http(500, "No attributes found!")
  258. cluster.attributes.editable = (
  259. objects.Cluster.get_default_editable_attributes(cluster))
  260. objects.Cluster.add_pending_changes(cluster, "attributes")
  261. logger.debug('ClusterAttributesDefaultsHandler:'
  262. ' editable attributes for cluster_id %s were reset'
  263. ' to default' % cluster_id)
  264. return {"editable": cluster.attributes.editable}
  265. class ClusterAttributesDeployedHandler(BaseHandler):
  266. """Cluster deployed attributes handler"""
  267. @handle_errors
  268. @validate
  269. @serialize
  270. def GET(self, cluster_id):
  271. """:returns: JSONized deployed Cluster editable attributes with plugins
  272. :http: * 200 (OK)
  273. * 404 (cluster not found in db)
  274. * 404 (cluster does not have deployed attributes)
  275. """
  276. cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  277. attrs = objects.Transaction.get_cluster_settings(
  278. objects.TransactionCollection.get_last_succeed_run(cluster)
  279. )
  280. if not attrs:
  281. raise self.http(
  282. 404, "Cluster does not have deployed attributes!"
  283. )
  284. return attrs
  285. class ClusterGeneratedData(BaseHandler):
  286. """Cluster generated data"""
  287. @handle_errors
  288. @validate
  289. @serialize
  290. def GET(self, cluster_id):
  291. """:returns: JSONized cluster generated data
  292. :http: * 200 (OK)
  293. * 404 (cluster not found in db)
  294. """
  295. cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  296. return cluster.attributes.generated
  297. class ClusterDeploymentTasksHandler(OrchestratorDeploymentTasksHandler):
  298. """Cluster Handler for deployment graph serialization."""
  299. single = objects.Cluster
  300. class ClusterPluginsDeploymentTasksHandler(BaseHandler):
  301. """Handler for cluster plugins merged deployment tasks serialization."""
  302. single = objects.Cluster
  303. @handle_errors
  304. @validate
  305. @serialize
  306. def GET(self, obj_id):
  307. """:returns: Deployment tasks
  308. :http: * 200 OK
  309. * 404 (object not found)
  310. """
  311. obj = self.get_object_or_404(self.single, obj_id)
  312. graph_type = web.input(graph_type=None).graph_type or None
  313. tasks = self.single.get_plugins_deployment_tasks(
  314. obj, graph_type=graph_type)
  315. return tasks
  316. class ClusterReleaseDeploymentTasksHandler(BaseHandler):
  317. """Handler for cluster release deployment tasks serialization."""
  318. single = objects.Cluster
  319. @handle_errors
  320. @validate
  321. @serialize
  322. def GET(self, obj_id):
  323. """:returns: Deployment tasks
  324. :http: * 200 OK
  325. * 404 (object not found)
  326. """
  327. obj = self.get_object_or_404(self.single, obj_id)
  328. graph_type = web.input(graph_type=None).graph_type or None
  329. tasks = self.single.get_release_deployment_tasks(
  330. obj, graph_type=graph_type)
  331. return tasks
  332. class ClusterOwnDeploymentTasksHandler(BaseHandler):
  333. """Handler for cluster own deployment tasks serialization."""
  334. single = objects.Cluster
  335. @handle_errors
  336. @validate
  337. @serialize
  338. def GET(self, obj_id):
  339. """:returns: Cluster own deployment tasks
  340. :http: * 200 OK
  341. * 404 (object not found)
  342. """
  343. obj = self.get_object_or_404(self.single, obj_id)
  344. graph_type = web.input(graph_type=None).graph_type or None
  345. tasks = self.single.get_own_deployment_tasks(
  346. obj, graph_type=graph_type)
  347. return tasks
  348. class ClusterDeploymentGraphCollectionHandler(
  349. RelatedDeploymentGraphCollectionHandler):
  350. """Cluster Handler for deployment graphs configuration."""
  351. related = objects.Cluster
  352. class ClusterExtensionsHandler(BaseHandler):
  353. """Cluster extensions handler"""
  354. validator = ExtensionValidator
  355. def _get_cluster_obj(self, cluster_id):
  356. return self.get_object_or_404(
  357. objects.Cluster, cluster_id,
  358. log_404=(
  359. "error",
  360. "There is no cluster with id '{0}' in DB.".format(cluster_id)
  361. )
  362. )
  363. @handle_errors
  364. @validate
  365. @serialize
  366. def GET(self, cluster_id):
  367. """:returns: JSONized list of enabled Cluster extensions
  368. :http: * 200 (OK)
  369. * 404 (cluster not found in db)
  370. """
  371. cluster = self._get_cluster_obj(cluster_id)
  372. return cluster.extensions
  373. @handle_errors
  374. @validate
  375. @serialize
  376. def PUT(self, cluster_id):
  377. """:returns: JSONized list of enabled Cluster extensions
  378. :http: * 200 (OK)
  379. * 400 (there is no such extension available)
  380. * 404 (cluster not found in db)
  381. """
  382. cluster = self._get_cluster_obj(cluster_id)
  383. data = self.checked_data()
  384. update_extensions_for_object(cluster, data)
  385. return cluster.extensions
  386. @handle_errors
  387. @validate
  388. def DELETE(self, cluster_id):
  389. """Disables the extensions for specified cluster
  390. Takes (JSONed) list of extension names to disable.
  391. :http: * 204 (OK)
  392. * 400 (there is no such extension enabled)
  393. * 404 (cluster not found in db)
  394. """
  395. cluster = self._get_cluster_obj(cluster_id)
  396. # TODO(agordeev): web.py does not support parsing of array arguments
  397. # in the queryset so we specify the input as comma-separated list
  398. extension_names = self.get_param_as_set('extension_names', default=[])
  399. data = self.checked_data(self.validator.validate_delete,
  400. data=extension_names, cluster=cluster)
  401. remove_extensions_from_object(cluster, data)
  402. raise self.http(204)
  403. class ClusterDeploymentGraphHandler(RelatedDeploymentGraphHandler):
  404. """Cluster Handler for deployment graph configuration."""
  405. related = objects.Cluster