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

v4.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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 six
  16. from fuel_plugin_builder.validators.schemas import SchemaV3
  17. COMPONENTS_TYPES_STR = '|'.join(
  18. ['hypervisor', 'network', 'storage', 'additional_service'])
  19. COMPONENT_NAME_PATTERN = \
  20. '^({0}):([0-9a-z_-]+:)*[0-9a-z_-]+$'.format(COMPONENTS_TYPES_STR)
  21. COMPATIBLE_COMPONENT_NAME_PATTERN = \
  22. '^({0}):([0-9a-z_-]+:)*([0-9a-z_-]+|(\*)?)$'.format(COMPONENTS_TYPES_STR)
  23. TASK_NAME_PATTERN = TASK_ROLE_PATTERN = '^[0-9a-zA-Z_-]+$|^\*$'
  24. NETWORK_ROLE_PATTERN = '^[0-9a-z_-]+$'
  25. FILE_PERMISSIONS_PATTERN = '^[0-7]{4}$'
  26. TASK_VERSION_PATTERN = '^\d+.\d+.\d+$'
  27. STAGE_PATTERN = '^(post_deployment|pre_deployment)' \
  28. '(/[-+]?([0-9]*\.[0-9]+|[0-9]+))?$'
  29. ROLE_ALIASES = ('roles', 'groups', 'role')
  30. TASK_OBLIGATORY_FIELDS = ['id', 'type']
  31. ROLELESS_TASKS = ('stage')
  32. class SchemaV4(SchemaV3):
  33. def __init__(self):
  34. super(SchemaV4, self).__init__()
  35. self.role_pattern = TASK_ROLE_PATTERN
  36. self.roleless_tasks = ROLELESS_TASKS
  37. self.role_aliases = ROLE_ALIASES
  38. @property
  39. def _task_relation(self):
  40. return {
  41. 'type': 'object',
  42. 'required': ['name'],
  43. 'properties': {
  44. 'name': {'type': 'string'},
  45. 'role': self._task_role,
  46. 'policy': {
  47. 'type': 'string',
  48. 'enum': ['all', 'any']
  49. }
  50. }
  51. }
  52. @property
  53. def _task_role(self):
  54. return {
  55. 'oneOf': [
  56. {
  57. 'type': 'string',
  58. 'format': 'fuel_task_role_format'
  59. },
  60. {
  61. 'type': 'array',
  62. 'items': {
  63. 'type': 'string',
  64. 'format': 'fuel_task_role_format'
  65. }
  66. }
  67. ]
  68. }
  69. @property
  70. def _task_strategy(self):
  71. return {
  72. 'type': 'object',
  73. 'properties': {
  74. 'type': {
  75. 'enum': ['parallel', 'one_by_one']
  76. }
  77. }
  78. }
  79. @property
  80. def _task_stage(self):
  81. return {'type': 'string', 'pattern': STAGE_PATTERN}
  82. @property
  83. def _task_reexecute(self):
  84. return {
  85. 'type': 'array',
  86. 'items': {
  87. 'type': 'string',
  88. 'enum': ['deploy_changes']
  89. }
  90. }
  91. def _gen_task_schema(self, task_types, required=None,
  92. parameters=None):
  93. """Generate deployment task schema using prototype.
  94. :param task_types: task types
  95. :type task_types: str|list
  96. :param required: new required fields
  97. :type required: list
  98. :param parameters: new properties dict
  99. :type parameters: dict
  100. :return:
  101. :rtype: dict
  102. """
  103. if not task_types:
  104. raise ValueError('Task type should not be empty')
  105. if isinstance(task_types, six.string_types):
  106. task_types = [task_types]
  107. # patch strategy parameter
  108. parameters = parameters or {
  109. "type": "object",
  110. }
  111. parameters.setdefault("properties", {})
  112. parameters["properties"].setdefault("strategy", self._task_strategy)
  113. task_specific_req_fields = list(set(TASK_OBLIGATORY_FIELDS +
  114. (required or [])))
  115. return {
  116. '$schema': 'http://json-schema.org/draft-04/schema#',
  117. 'type': 'object',
  118. 'required': task_specific_req_fields,
  119. 'properties': {
  120. 'type': {'enum': task_types},
  121. 'id': {
  122. 'type': 'string',
  123. 'pattern': TASK_NAME_PATTERN},
  124. 'version': {
  125. 'type': 'string', "pattern": TASK_VERSION_PATTERN},
  126. 'role': self._task_role,
  127. 'groups': self._task_role,
  128. 'roles': self._task_role,
  129. 'required_for': self.task_group,
  130. 'requires': self.task_group,
  131. 'cross-depends': {
  132. 'type': 'array',
  133. 'items': self._task_relation},
  134. 'cross-depended-by': {
  135. 'type': 'array',
  136. 'items': self._task_relation},
  137. 'stage': self._task_stage,
  138. 'tasks': { # used only for 'group' tasks
  139. 'type': 'array',
  140. 'items': {
  141. 'type': 'string',
  142. 'pattern': TASK_ROLE_PATTERN}},
  143. 'reexecute_on': self._task_reexecute,
  144. 'parameters': parameters or {},
  145. },
  146. }
  147. @property
  148. def deployment_task_schema(self):
  149. return {
  150. '$schema': 'http://json-schema.org/draft-04/schema#',
  151. 'type': 'array',
  152. 'items': {
  153. "$ref": "#/definitions/anyTask"
  154. },
  155. "definitions": {
  156. "anyTask": self._gen_task_schema(
  157. [
  158. 'copy_files',
  159. 'group',
  160. 'reboot',
  161. 'shell',
  162. 'skipped',
  163. 'stage',
  164. 'sync',
  165. 'puppet',
  166. 'upload_file',
  167. ]
  168. )
  169. }
  170. }
  171. @property
  172. def copy_files_task(self):
  173. return self._gen_task_schema(
  174. "copy_files",
  175. ['parameters'],
  176. {
  177. 'type': 'object',
  178. 'required': ['files'],
  179. 'properties': {
  180. 'files': {
  181. 'type': 'array',
  182. 'minItems': 1,
  183. 'items': {
  184. 'type': 'object',
  185. 'required': ['src', 'dst'],
  186. 'properties': {
  187. 'src': {'type': 'string'},
  188. 'dst': {'type': 'string'}}}},
  189. 'permissions': {
  190. 'type': 'string',
  191. 'pattern': FILE_PERMISSIONS_PATTERN},
  192. 'dir_permissions': {
  193. 'type': 'string',
  194. 'pattern': FILE_PERMISSIONS_PATTERN}}})
  195. @property
  196. def group_task(self):
  197. return self._gen_task_schema("group", [])
  198. @property
  199. def puppet_task(self):
  200. return self._gen_task_schema(
  201. "puppet",
  202. [],
  203. {
  204. 'type': 'object',
  205. 'required': [
  206. 'puppet_manifest', 'puppet_modules', 'timeout'],
  207. 'properties': {
  208. 'puppet_manifest': {
  209. 'type': 'string', 'minLength': 1},
  210. 'puppet_modules': {
  211. 'type': 'string', 'minLength': 1},
  212. 'timeout': {'type': 'integer'},
  213. 'retries': {'type': 'integer'}
  214. }
  215. }
  216. )
  217. @property
  218. def reboot_task(self):
  219. return self._gen_task_schema(
  220. "reboot",
  221. [],
  222. {
  223. 'type': 'object',
  224. 'properties': {
  225. 'timeout': {'type': 'integer'}
  226. }
  227. }
  228. )
  229. @property
  230. def shell_task(self):
  231. return self._gen_task_schema(
  232. "shell",
  233. [],
  234. {
  235. 'type': 'object',
  236. 'required': ['cmd'],
  237. 'properties': {
  238. 'cmd': {
  239. 'type': 'string'},
  240. 'retries': {
  241. 'type': 'integer'},
  242. 'interval': {
  243. 'type': 'integer'},
  244. 'timeout': {
  245. 'type': 'integer'}
  246. }
  247. }
  248. )
  249. @property
  250. def skipped_task(self):
  251. return self._gen_task_schema("skipped")
  252. @property
  253. def stage_task(self):
  254. return self._gen_task_schema("stage")
  255. @property
  256. def sync_task(self):
  257. return self._gen_task_schema(
  258. "sync",
  259. ['parameters'],
  260. {
  261. 'type': 'object',
  262. 'required': ['src', 'dst'],
  263. 'properties': {
  264. 'src': {'type': 'string'},
  265. 'dst': {'type': 'string'},
  266. 'timeout': {'type': 'integer'}
  267. }
  268. }
  269. )
  270. @property
  271. def upload_file_task(self):
  272. return self._gen_task_schema(
  273. "upload_file",
  274. ['parameters'],
  275. {
  276. 'type': 'object',
  277. 'required': ['path', 'data'],
  278. 'properties': {
  279. 'path': {'type': 'string'},
  280. 'data': {'type': 'string'}
  281. }
  282. }
  283. )
  284. @property
  285. def package_version(self):
  286. return {'enum': ['4.0.0']}
  287. @property
  288. def metadata_schema(self):
  289. schema = super(SchemaV4, self).metadata_schema
  290. schema['required'].append('is_hotpluggable')
  291. schema['properties']['is_hotpluggable'] = {'type': 'boolean'}
  292. schema['properties']['groups']['items']['enum'].append('equipment')
  293. return schema
  294. @property
  295. def attr_root_schema(self):
  296. schema = super(SchemaV4, self).attr_root_schema
  297. schema['properties']['attributes']['properties'] = {
  298. 'metadata': {
  299. 'type': 'object',
  300. 'properties': {
  301. 'group': {
  302. 'enum': [
  303. 'general', 'security',
  304. 'compute', 'network',
  305. 'storage', 'logging',
  306. 'openstack_services', 'other'
  307. ]
  308. }
  309. }
  310. }
  311. }
  312. return schema
  313. @property
  314. def components_items(self):
  315. return {
  316. 'type': 'array',
  317. 'items': {
  318. 'type': 'object',
  319. 'required': ['name'],
  320. 'properties': {
  321. 'name': {
  322. 'type': 'string',
  323. 'pattern': COMPATIBLE_COMPONENT_NAME_PATTERN
  324. },
  325. 'message': {'type': 'string'}
  326. }
  327. }
  328. }
  329. @property
  330. def components_schema(self):
  331. return {
  332. '$schema': 'http://json-schema.org/draft-04/schema#',
  333. 'type': 'array',
  334. 'items': {
  335. 'required': ['name', 'label'],
  336. 'type': 'object',
  337. 'additionalProperties': False,
  338. 'properties': {
  339. 'name': {
  340. 'type': 'string',
  341. 'pattern': COMPONENT_NAME_PATTERN
  342. },
  343. 'label': {'type': 'string'},
  344. 'description': {'type': 'string'},
  345. 'compatible': self.components_items,
  346. 'requires': self.components_items,
  347. 'incompatible': self.components_items,
  348. 'bind': {'type': 'array'}
  349. }
  350. }
  351. }