etcdkeysize.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/python
  2. """Ansible module that recursively determines if the size of a key in an etcd cluster exceeds a given limit."""
  3. from ansible.module_utils.basic import AnsibleModule
  4. try:
  5. import etcd
  6. IMPORT_EXCEPTION_MSG = None
  7. except ImportError as err:
  8. IMPORT_EXCEPTION_MSG = str(err)
  9. from collections import namedtuple
  10. EtcdMock = namedtuple("etcd", ["EtcdKeyNotFound"])
  11. etcd = EtcdMock(KeyError)
  12. # pylint: disable=too-many-arguments
  13. def check_etcd_key_size(client, key, size_limit, total_size=0, depth=0, depth_limit=1000, visited=None):
  14. """Check size of an etcd path starting at given key. Returns tuple (string, bool)"""
  15. if visited is None:
  16. visited = set()
  17. if key in visited:
  18. return 0, False
  19. visited.add(key)
  20. try:
  21. result = client.read(key, recursive=False)
  22. except etcd.EtcdKeyNotFound:
  23. return 0, False
  24. size = 0
  25. limit_exceeded = False
  26. for node in result.leaves:
  27. if depth >= depth_limit:
  28. raise Exception("Maximum recursive stack depth ({}) exceeded.".format(depth_limit))
  29. if size_limit and total_size + size > size_limit:
  30. return size, True
  31. if not node.dir:
  32. size += len(node.value)
  33. continue
  34. key_size, limit_exceeded = check_etcd_key_size(client, node.key,
  35. size_limit,
  36. total_size + size,
  37. depth + 1,
  38. depth_limit, visited)
  39. size += key_size
  40. max_limit_exceeded = limit_exceeded or (total_size + size > size_limit)
  41. return size, max_limit_exceeded
  42. def main(): # pylint: disable=missing-docstring,too-many-branches
  43. module = AnsibleModule(
  44. argument_spec=dict(
  45. size_limit_bytes=dict(type="int", default=0),
  46. paths=dict(type="list", default=["/openshift.io/images"]),
  47. host=dict(type="str", default="127.0.0.1"),
  48. port=dict(type="int", default=4001),
  49. protocol=dict(type="str", default="http"),
  50. version_prefix=dict(type="str", default=""),
  51. allow_redirect=dict(type="bool", default=False),
  52. cert=dict(type="dict", default=""),
  53. ca_cert=dict(type="str", default=None),
  54. ),
  55. supports_check_mode=True
  56. )
  57. module.params["cert"] = (
  58. module.params["cert"]["cert"],
  59. module.params["cert"]["key"],
  60. )
  61. size_limit = module.params.pop("size_limit_bytes")
  62. paths = module.params.pop("paths")
  63. limit_exceeded = False
  64. try:
  65. # pylint: disable=no-member
  66. client = etcd.Client(**module.params)
  67. except AttributeError as attrerr:
  68. msg = str(attrerr)
  69. if IMPORT_EXCEPTION_MSG:
  70. msg = IMPORT_EXCEPTION_MSG
  71. if "No module named etcd" in IMPORT_EXCEPTION_MSG:
  72. # pylint: disable=redefined-variable-type
  73. msg = ('Unable to import the python "etcd" dependency. '
  74. 'Make sure python-etcd is installed on the host.')
  75. module.exit_json(
  76. failed=True,
  77. changed=False,
  78. size_limit_exceeded=limit_exceeded,
  79. msg=msg,
  80. )
  81. return
  82. size = 0
  83. for path in paths:
  84. path_size, limit_exceeded = check_etcd_key_size(client, path, size_limit - size)
  85. size += path_size
  86. if limit_exceeded:
  87. break
  88. module.exit_json(
  89. changed=False,
  90. size_limit_exceeded=limit_exceeded,
  91. )
  92. if __name__ == '__main__':
  93. main()