validators.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. # -*- coding: utf-8 -*-
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS,
  10. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  11. # implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. Contains all core validation functions.
  16. """
  17. import os
  18. import re
  19. import socket
  20. import logging
  21. from . import utils
  22. from .exceptions import ParamValidationError
  23. __all__ = ('ParamValidationError', 'validate_integer', 'validate_float',
  24. 'validate_regexp', 'validate_port', 'validate_not_empty',
  25. 'validate_options', 'validate_multi_options', 'validate_ip',
  26. 'validate_multi_ip', 'validate_file', 'validate_ping',
  27. 'validate_multi_ping', 'validate_ssh', 'validate_multi_ssh',
  28. 'validate_sshkey', 'validate_ldap_url', 'validate_ldap_dn',
  29. 'validate_export', 'validate_multi_export',
  30. 'validate_writeable_directory')
  31. def validate_integer(param, options=None):
  32. """
  33. Raises ParamValidationError if given param is not integer.
  34. """
  35. if not param:
  36. return
  37. options = options or []
  38. try:
  39. int(param)
  40. except ValueError:
  41. logging.debug('validate_integer(%s, options=%s) failed.' %
  42. (param, options))
  43. msg = 'Given value is not an integer: %s'
  44. raise ParamValidationError(msg % param)
  45. def validate_float(param, options=None):
  46. """
  47. Raises ParamValidationError if given param is not a float.
  48. """
  49. if not param:
  50. return
  51. options = options or []
  52. try:
  53. float(param)
  54. except ValueError:
  55. logging.debug('validate_float(%s, options=%s) failed.' %
  56. (param, options))
  57. msg = 'Given value is not a float: %s'
  58. raise ParamValidationError(msg % param)
  59. def validate_regexp(param, options=None):
  60. """
  61. Raises ParamValidationError if given param doesn't match at least
  62. one of regular expressions given in options.
  63. """
  64. if not param:
  65. return
  66. options = options or []
  67. for regex in options:
  68. if re.search(regex, param):
  69. break
  70. else:
  71. logging.debug('validate_regexp(%s, options=%s) failed.' %
  72. (param, options))
  73. msg = 'Given value does not match required regular expression: %s'
  74. raise ParamValidationError(msg % param)
  75. def validate_multi_regexp(param, options=None):
  76. """
  77. Raises ParamValidationError if any of the comma separated values given
  78. in param doesn't match one of the regular expressions given in options.
  79. """
  80. options = options or []
  81. for i in param.split(','):
  82. validate_regexp(i.strip(), options=options)
  83. def validate_port(param, options=None):
  84. """
  85. Raises ParamValidationError if given param is not a decimal number
  86. in range (0, 65535).
  87. """
  88. if not param:
  89. return
  90. options = options or []
  91. validate_integer(param, options)
  92. port = int(param)
  93. if not (port >= 0 and port < 65535):
  94. logging.debug('validate_port(%s, options=%s) failed.' %
  95. (param, options))
  96. msg = 'Given value is outside the range of (0, 65535): %s'
  97. raise ParamValidationError(msg % param)
  98. def validate_not_empty(param, options=None):
  99. """
  100. Raises ParamValidationError if given param is empty.
  101. """
  102. options = options or []
  103. if not param and param is not False:
  104. logging.debug('validate_not_empty(%s, options=%s) failed.' %
  105. (param, options))
  106. msg = 'Given value is not allowed: %s'
  107. raise ParamValidationError(msg % param)
  108. def validate_options(param, options=None):
  109. """
  110. Raises ParamValidationError if given param is not member of options.
  111. """
  112. if not param:
  113. return
  114. options = options or []
  115. validate_not_empty(param, options)
  116. if param not in options:
  117. logging.debug('validate_options(%s, options=%s) failed.' %
  118. (param, options))
  119. msg = 'Given value is not member of allowed values %s: %s'
  120. raise ParamValidationError(msg % (options, param))
  121. def validate_multi_options(param, options=None):
  122. """
  123. Validates if comma separated values given in params are members
  124. of options.
  125. """
  126. if not param:
  127. return
  128. options = options or []
  129. for i in param.split(','):
  130. validate_options(i.strip(), options=options)
  131. def validate_ip(param, options=None):
  132. """
  133. Raises ParamValidationError if given parameter value is not in IPv4
  134. or IPv6 address.
  135. """
  136. if not param:
  137. return
  138. for family in (socket.AF_INET, socket.AF_INET6):
  139. try:
  140. socket.inet_pton(family, param)
  141. return family
  142. except socket.error:
  143. continue
  144. else:
  145. logging.debug('validate_ip(%s, options=%s) failed.' %
  146. (param, options))
  147. msg = 'Given host is not in IP address format: %s'
  148. raise ParamValidationError(msg % param)
  149. def validate_multi_ip(param, options=None):
  150. """
  151. Raises ParamValidationError if comma separated IP addresses given
  152. parameter value are in IPv4 or IPv6 aformat.
  153. """
  154. for host in param.split(','):
  155. host = host.split('/', 1)[0]
  156. validate_ip(host.strip(), options)
  157. def validate_file(param, options=None):
  158. """
  159. Raises ParamValidationError if provided file in param does not exist.
  160. """
  161. if not param:
  162. return
  163. options = options or []
  164. if not os.path.isfile(param):
  165. logging.debug('validate_file(%s, options=%s) failed.' %
  166. (param, options))
  167. msg = 'Given file does not exist: %s'
  168. raise ParamValidationError(msg % param)
  169. def validate_writeable_directory(param, options=None):
  170. """
  171. Raises ParamValidationError if provided directory does not exist or
  172. is not writeable.
  173. """
  174. if not param:
  175. return
  176. options = options or []
  177. path = os.path.expanduser(param)
  178. if not ((os.path.isdir(path) and os.access(path, os.W_OK)) or
  179. os.access(
  180. os.path.normpath(os.path.join(path, os.pardir)), os.W_OK)):
  181. logging.debug('validate_writeable_directory(%s, options=%s) failed.' %
  182. (param, options))
  183. msg = 'Given directory does not exist or is not writeable: %s'
  184. raise ParamValidationError(msg % param)
  185. def validate_ping(param, options=None):
  186. """
  187. Raises ParamValidationError if provided host does not answer to ICMP
  188. echo request.
  189. """
  190. if not param:
  191. return
  192. options = options or []
  193. rc, out = utils.execute(['/bin/ping', '-c', '1', str(param)],
  194. can_fail=False)
  195. if rc != 0:
  196. logging.debug('validate_ping(%s, options=%s) failed.' %
  197. (param, options))
  198. msg = 'Given host is unreachable: %s'
  199. raise ParamValidationError(msg % param)
  200. def validate_multi_ping(param, options=None):
  201. """
  202. Raises ParamValidationError if comma separated host given in param
  203. do not answer to ICMP echo request.
  204. """
  205. options = options or []
  206. for host in param.split(","):
  207. validate_ping(host.strip())
  208. _tested_ports = []
  209. def touch_port(host, port):
  210. """
  211. Check that provided host is listening on provided port.
  212. """
  213. key = "%s:%d" % (host, port)
  214. if key in _tested_ports:
  215. return
  216. sock = socket.create_connection((host, port))
  217. sock.shutdown(socket.SHUT_RDWR)
  218. sock.close()
  219. _tested_ports.append(key)
  220. def validate_ssh(param, options=None):
  221. """
  222. Raises ParamValidationError if provided host does not listen
  223. on port 22.
  224. """
  225. if not param:
  226. return
  227. options = options or []
  228. try:
  229. touch_port(param.strip(), 22)
  230. except socket.error:
  231. logging.debug('validate_ssh(%s, options=%s) failed.' %
  232. (param, options))
  233. msg = 'Given host does not listen on port 22: %s'
  234. raise ParamValidationError(msg % param)
  235. def validate_multi_ssh(param, options=None):
  236. """
  237. Raises ParamValidationError if comma separated host provided
  238. in param do not listen on port 22.
  239. """
  240. options = options or []
  241. for host in param.split(","):
  242. validate_ssh(host)
  243. def validate_sshkey(param, options=None):
  244. """
  245. Raises ParamValidationError if provided sshkey file is not public key.
  246. """
  247. if not param:
  248. return
  249. with open(param) as sshkey:
  250. line = sshkey.readline()
  251. msg = None
  252. if not re.search('ssh-|ecdsa', line):
  253. msg = ('Invalid content header in %s, Public SSH key is required.'
  254. % param)
  255. if re.search('BEGIN [RD]SA PRIVATE KEY', line):
  256. msg = 'Public SSH key is required. You passed private key.'
  257. if msg:
  258. raise ParamValidationError(msg)
  259. def validate_ldap_url(param, options=None):
  260. """
  261. Raises ParamValidationError if provided param is not a valid LDAP URL
  262. """
  263. if not param:
  264. return
  265. try:
  266. import ldapurl
  267. except ImportError:
  268. msg = (
  269. 'The python ldap package is required to use this functionality.'
  270. )
  271. raise ParamValidationError(msg)
  272. try:
  273. ldapurl.LDAPUrl(param)
  274. except ValueError as ve:
  275. msg = ('The given string [%s] is not a valid LDAP URL: %s' %
  276. (param, ve))
  277. raise ParamValidationError(msg)
  278. def validate_ldap_dn(param, options=None):
  279. """
  280. Raises ParamValidationError if provided param is not a valid LDAP DN
  281. """
  282. if not param:
  283. return
  284. try:
  285. import ldap
  286. import ldap.dn
  287. except ImportError:
  288. msg = (
  289. 'The python ldap package is required to use this functionality.'
  290. )
  291. raise ParamValidationError(msg)
  292. try:
  293. ldap.dn.str2dn(param)
  294. except ldap.DECODING_ERROR as de:
  295. msg = ('The given string [%s] is not a valid LDAP DN: %s' %
  296. (param, de))
  297. raise ParamValidationError(msg)
  298. def validate_export(param, options=None):
  299. """
  300. Raises ParamValidationError if the nfs export is not valid.
  301. """
  302. msg = ('The nfs export [%s] is not a valid export - use squares around ipv6 addresses -.' %
  303. param)
  304. try:
  305. [ip, export] = param.split(':/')
  306. except ValueError:
  307. raise ParamValidationError(msg)
  308. get_squares = re.search(r'\[([^]]+)\]', ip)
  309. ip_to_test = ip
  310. if get_squares:
  311. # this should be a valid ipv6 address.
  312. ip_to_test = get_squares.group(1)
  313. if not utils.network.is_ipv6(ip_to_test):
  314. raise ParamValidationError(msg)
  315. else:
  316. # this should be an ipv4. Cannot have ipv6 without square braquet
  317. # notation here, as the mount will fail.
  318. if not utils.network.is_ipv4(ip):
  319. raise ParamValidationError(msg)
  320. validate_ip(ip_to_test, options)
  321. if not export:
  322. raise ParamValidationError(msg)
  323. def validate_multi_export(param, options=None):
  324. """
  325. Raises ParamValidationError if comma separated nfs export given
  326. in param is not valid
  327. """
  328. for export in param.split(","):
  329. validate_export(export)
  330. def validate_neutron(param, options=None):
  331. """
  332. Raises ParamValidationError if neutron is not enabled.
  333. This is intended to make user aware nova-network has been removed
  334. in ocata cycle.
  335. """
  336. validate_options(param, options=options)
  337. if param != 'y':
  338. msg = ('Nova network support has been removed in Ocata. Neutron service must be enabled')
  339. raise ParamValidationError(msg)