图片解析应用
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.

169 lines
5.6 KiB

  1. """Zookeeper lease implementations
  2. :Maintainer: Lars Albertsson <lars.albertsson@gmail.com>
  3. :Maintainer: Jyrki Pulliainen <jyrki@spotify.com>
  4. :Status: Beta
  5. """
  6. import datetime
  7. import json
  8. import socket
  9. from kazoo.exceptions import CancelledError
  10. class NonBlockingLease(object):
  11. """Exclusive lease that does not block.
  12. An exclusive lease ensures that only one client at a time owns the lease.
  13. The client may renew the lease without losing it by obtaining a new lease
  14. with the same path and same identity. The lease object evaluates to True
  15. if the lease was obtained.
  16. A common use case is a situation where a task should only run on a single
  17. host. In this case, the clients that did not obtain the lease should exit
  18. without performing the protected task.
  19. The lease stores time stamps using client clocks, and will therefore only
  20. work if client clocks are roughly synchronised. It uses UTC, and works
  21. across time zones and daylight savings.
  22. Example usage: with a :class:`~kazoo.client.KazooClient` instance::
  23. zk = KazooClient()
  24. zk.start()
  25. # Hold lease over an hour in order to keep job on same machine,
  26. # with failover if it dies.
  27. lease = zk.NonBlockingLease(
  28. "/db_leases/hourly_cleanup", datetime.timedelta(minutes = 70),
  29. identifier = "DB hourly cleanup on " + socket.gethostname())
  30. if lease:
  31. do_hourly_database_cleanup()
  32. """
  33. # Bump when storage format changes
  34. _version = 1
  35. _date_format = "%Y-%m-%dT%H:%M:%S"
  36. _byte_encoding = "utf-8"
  37. def __init__(
  38. self,
  39. client,
  40. path,
  41. duration,
  42. identifier=None,
  43. utcnow=datetime.datetime.utcnow,
  44. ):
  45. """Create a non-blocking lease.
  46. :param client: A :class:`~kazoo.client.KazooClient` instance.
  47. :param path: The lease path to use.
  48. :param duration: Duration during which the lease is reserved. A
  49. :class:`~datetime.timedelta` instance.
  50. :param identifier: Unique name to use for this lease holder. Reuse in
  51. order to renew the lease. Defaults to
  52. :meth:`socket.gethostname()`.
  53. :param utcnow: Clock function, by default returning
  54. :meth:`datetime.datetime.utcnow()`. Used for testing.
  55. """
  56. ident = identifier or socket.gethostname()
  57. self.obtained = False
  58. self._attempt_obtaining(client, path, duration, ident, utcnow)
  59. def _attempt_obtaining(self, client, path, duration, ident, utcnow):
  60. client.ensure_path(path)
  61. holder_path = path + "/lease_holder"
  62. lock = client.Lock(path, ident)
  63. try:
  64. with lock:
  65. now = utcnow()
  66. if client.exists(holder_path):
  67. raw, _ = client.get(holder_path)
  68. data = self._decode(raw)
  69. if data["version"] != self._version:
  70. # We need an upgrade, let someone else take the lease
  71. return
  72. current_end = datetime.datetime.strptime(
  73. data["end"], self._date_format
  74. )
  75. if data["holder"] != ident and now < current_end:
  76. # Another client is still holding the lease
  77. return
  78. client.delete(holder_path)
  79. end_lease = (now + duration).strftime(self._date_format)
  80. new_data = {
  81. "version": self._version,
  82. "holder": ident,
  83. "end": end_lease,
  84. }
  85. client.create(holder_path, self._encode(new_data))
  86. self.obtained = True
  87. except CancelledError:
  88. pass
  89. def _encode(self, data_dict):
  90. return json.dumps(data_dict).encode(self._byte_encoding)
  91. def _decode(self, raw):
  92. return json.loads(raw.decode(self._byte_encoding))
  93. # Python 2.x
  94. def __nonzero__(self):
  95. return self.obtained
  96. # Python 3.x
  97. def __bool__(self):
  98. return self.obtained
  99. class MultiNonBlockingLease(object):
  100. """Exclusive lease for multiple clients.
  101. This type of lease is useful when a limited set of hosts should run a
  102. particular task. It will attempt to obtain leases trying a sequence of
  103. ZooKeeper lease paths.
  104. :param client: A :class:`~kazoo.client.KazooClient` instance.
  105. :param count: Number of host leases allowed.
  106. :param path: ZooKeeper path under which lease files are stored.
  107. :param duration: Duration during which the lease is reserved. A
  108. :class:`~datetime.timedelta` instance.
  109. :param identifier: Unique name to use for this lease holder. Reuse in order
  110. to renew the lease.
  111. Defaults do :meth:`socket.gethostname()`.
  112. :param utcnow: Clock function, by default returning
  113. :meth:`datetime.datetime.utcnow()`. Used for testing.
  114. """
  115. def __init__(
  116. self,
  117. client,
  118. count,
  119. path,
  120. duration,
  121. identifier=None,
  122. utcnow=datetime.datetime.utcnow,
  123. ):
  124. self.obtained = False
  125. for num in range(count):
  126. ls = NonBlockingLease(
  127. client,
  128. "%s/%d" % (path, num),
  129. duration,
  130. identifier=identifier,
  131. utcnow=utcnow,
  132. )
  133. if ls:
  134. self.obtained = True
  135. break
  136. # Python 2.x
  137. def __nonzero__(self):
  138. return self.obtained
  139. # Python 3.x
  140. def __bool__(self):
  141. return self.obtained