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 20KB

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