Cluster upgrade extension for Fuel
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.

handlers.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 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. import copy
  16. import six
  17. from nailgun.api.v1.handlers import base
  18. from nailgun import errors
  19. from nailgun.extensions.manager import update_extensions_for_object
  20. from nailgun import objects
  21. from nailgun.task import manager
  22. from . import upgrade
  23. from . import validators
  24. from .objects import adapters
  25. class ClusterUpgradeCloneHandler(base.BaseHandler):
  26. single = objects.Cluster
  27. validator = validators.ClusterUpgradeValidator
  28. @base.handle_errors
  29. @base.validate
  30. @base.serialize
  31. def POST(self, cluster_id):
  32. """Initialize the upgrade of the cluster.
  33. Creates a new cluster with specified name and release_id. The
  34. new cluster is created with parameters that are copied from the
  35. cluster with the given cluster_id. The values of the generated
  36. and editable attributes are just copied from one to the other.
  37. :param cluster_id: ID of the cluster from which parameters would
  38. be copied
  39. :returns: JSON representation of the created cluster
  40. :http: * 200 (OK)
  41. * 400 (upgrade parameters are invalid)
  42. * 404 (node or release not found in db)
  43. """
  44. orig_cluster = adapters.NailgunClusterAdapter(
  45. self.get_object_or_404(self.single, cluster_id))
  46. request_data = self.checked_data(cluster=orig_cluster)
  47. new_cluster = upgrade.UpgradeHelper.clone_cluster(orig_cluster,
  48. request_data)
  49. valid = upgrade.UpgradeHelper.validate_network_roles(
  50. orig_cluster, new_cluster,
  51. )
  52. if not valid:
  53. raise errors.InvalidData("Network changes during upgrade"
  54. " is not supported.")
  55. update_extensions_for_object(new_cluster.cluster, ['cluster_upgrade'])
  56. return new_cluster.to_dict()
  57. class NodeReassignHandler(base.BaseHandler):
  58. single = objects.Cluster
  59. validator = validators.NodeReassignValidator
  60. task_manager = manager.ProvisioningTaskManager
  61. def handle_task(self, cluster_id, nodes):
  62. try:
  63. task_manager = self.task_manager(cluster_id=cluster_id)
  64. task = task_manager.execute(nodes)
  65. except Exception as exc:
  66. raise self.http(400, msg=six.text_type(exc))
  67. self.raise_task(task)
  68. @base.handle_errors
  69. @base.validate
  70. def POST(self, cluster_id):
  71. """Reassign nodes to the given cluster.
  72. The given nodes will be assigned from the current cluster to the
  73. given cluster, by default it involves the reprovisioning of the
  74. nodes. If the 'reprovision' flag is set to False, then the nodes
  75. will be just reassigned. If the 'roles' list is specified, then
  76. the given roles will be used as 'pending_roles' in case of
  77. the reprovisioning or otherwise as 'roles'.
  78. :param cluster_id: ID of the cluster nodes should be assigned to.
  79. :returns: None
  80. :http: * 202 (OK)
  81. * 400 (Incorrect node state, problem with task execution,
  82. conflicting or incorrect roles)
  83. * 404 (Cluster or node not found)
  84. """
  85. cluster = adapters.NailgunClusterAdapter(
  86. self.get_object_or_404(self.single, cluster_id))
  87. data = self.checked_data(cluster=cluster)
  88. reprovision = data.get('reprovision', True)
  89. given_roles = data.get('roles', [])
  90. nodes_to_provision = []
  91. for node_id in data['nodes_ids']:
  92. node = adapters.NailgunNodeAdapter(
  93. self.get_object_or_404(objects.Node, node_id))
  94. nodes_to_provision.append(node.node)
  95. roles, pending_roles = upgrade.UpgradeHelper.get_node_roles(
  96. reprovision, node.roles, given_roles)
  97. upgrade.UpgradeHelper.assign_node_to_cluster(
  98. node, cluster, roles, pending_roles)
  99. if reprovision:
  100. self.handle_task(cluster_id, nodes_to_provision)
  101. class CopyVIPsHandler(base.BaseHandler):
  102. single = objects.Cluster
  103. validator = validators.CopyVIPsValidator
  104. @base.handle_errors
  105. @base.validate
  106. @base.serialize
  107. def POST(self, cluster_id):
  108. """Copy VIPs from original cluster to new one
  109. Original cluster object is obtained from existing relation between
  110. clusters that is created on cluster clone operation
  111. :param cluster_id: id of cluster that VIPs must be copied to
  112. :returns: Collection of JSON-serialised VIPs.
  113. :http: * 200 (OK)
  114. * 400 (validation failed)
  115. * 404 (seed cluster is not found)
  116. """
  117. from .objects import relations
  118. cluster = self.get_object_or_404(self.single, cluster_id)
  119. relation = relations.UpgradeRelationObject.get_cluster_relation(
  120. cluster.id)
  121. self.checked_data(cluster=cluster, relation=relation)
  122. # get original cluster object and create adapter with it
  123. orig_cluster_adapter = adapters.NailgunClusterAdapter.get_by_uid(
  124. relation.orig_cluster_id)
  125. seed_cluster_adapter = adapters.NailgunClusterAdapter(cluster)
  126. upgrade.UpgradeHelper.copy_vips(orig_cluster_adapter,
  127. seed_cluster_adapter)
  128. cluster_vips = objects.IPAddrCollection.get_vips_by_cluster_id(
  129. cluster.id)
  130. return objects.IPAddrCollection.to_list(cluster_vips)
  131. class CreateUpgradeReleaseHandler(base.BaseHandler):
  132. @staticmethod
  133. def merge_network_roles(base_nets, orig_nets):
  134. """Create network metadata based on two releases.
  135. Overwrite base default_mapping by orig default_maping values.
  136. """
  137. base_nets = copy.deepcopy(base_nets)
  138. orig_nets = copy.deepcopy(orig_nets)
  139. orig_network_dict = {n['id']: n for n in orig_nets}
  140. for base_net in base_nets:
  141. orig_net = orig_network_dict.get(base_net['id'])
  142. if orig_net is not None:
  143. base_net['default_mapping'] = orig_net['default_mapping']
  144. return base_nets
  145. @base.serialize
  146. def POST(self, cluster_id, release_id):
  147. """Create release for upgrade purposes.
  148. Creates a new release with network_roles_metadata based the given
  149. release and re-use network parameters from the given cluster.
  150. :returns: JSON representation of the created cluster
  151. :http: * 200 (OK)
  152. * 404 (Cluster or release not found.)
  153. """
  154. base_release = self.get_object_or_404(objects.Release, release_id)
  155. orig_cluster = self.get_object_or_404(objects.Cluster, cluster_id)
  156. orig_release = orig_cluster.release
  157. network_metadata = self.merge_network_roles(
  158. base_release.network_roles_metadata,
  159. orig_release.network_roles_metadata)
  160. data = dict(base_release)
  161. data['network_roles_metadata'] = network_metadata
  162. data['name'] = '{0} Upgrade ({1})'.format(
  163. base_release.name, orig_release.id)
  164. deployment_tasks = objects.Release.get_deployment_tasks(base_release)
  165. data['deployment_tasks'] = deployment_tasks
  166. del data['id']
  167. new_release = objects.Release.create(data)
  168. return objects.Release.to_dict(new_release)