Development of an internal social media platform with personalised dashboards for students
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.

pooling.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. """
  2. """
  3. # Created on 2014.03.14
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2014 - 2018 Giovanni Cannata
  8. #
  9. # This file is part of ldap3.
  10. #
  11. # ldap3 is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU Lesser General Public License as published
  13. # by the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # ldap3 is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU Lesser General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU Lesser General Public License
  22. # along with ldap3 in the COPYING and COPYING.LESSER files.
  23. # If not, see <http://www.gnu.org/licenses/>.
  24. from datetime import datetime, MINYEAR
  25. from os import linesep
  26. from random import randint
  27. from time import sleep
  28. from .. import FIRST, ROUND_ROBIN, RANDOM, SEQUENCE_TYPES, STRING_TYPES, get_config_parameter
  29. from .exceptions import LDAPUnknownStrategyError, LDAPServerPoolError, LDAPServerPoolExhaustedError
  30. from .server import Server
  31. from ..utils.log import log, log_enabled, ERROR, BASIC, NETWORK
  32. POOLING_STRATEGIES = [FIRST, ROUND_ROBIN, RANDOM]
  33. class ServerPoolState(object):
  34. def __init__(self, server_pool):
  35. self.servers = [] # each element is a list: [server, last_checked_time, available]
  36. self.strategy = server_pool.strategy
  37. self.server_pool = server_pool
  38. self.last_used_server = 0
  39. self.refresh()
  40. self.initialize_time = datetime.now()
  41. if log_enabled(BASIC):
  42. log(BASIC, 'instantiated ServerPoolState: <%r>', self)
  43. def __str__(self):
  44. s = 'servers: ' + linesep
  45. if self.servers:
  46. for server in self.servers:
  47. s += str(server[0]) + linesep
  48. else:
  49. s += 'None' + linesep
  50. s += 'Pool strategy: ' + str(self.strategy) + linesep
  51. s += ' - Last used server: ' + ('None' if self.last_used_server == -1 else str(self.servers[self.last_used_server][0]))
  52. return s
  53. def refresh(self):
  54. self.servers = []
  55. for server in self.server_pool.servers:
  56. self.servers.append([server, datetime(MINYEAR, 1, 1), True]) # server, smallest date ever, supposed available
  57. self.last_used_server = randint(0, len(self.servers) - 1)
  58. def get_current_server(self):
  59. return self.servers[self.last_used_server][0]
  60. def get_server(self):
  61. if self.servers:
  62. if self.server_pool.strategy == FIRST:
  63. if self.server_pool.active:
  64. # returns the first active server
  65. self.last_used_server = self.find_active_server(starting=0)
  66. else:
  67. # returns always the first server - no pooling
  68. self.last_used_server = 0
  69. elif self.server_pool.strategy == ROUND_ROBIN:
  70. if self.server_pool.active:
  71. # returns the next active server in a circular range
  72. self.last_used_server = self.find_active_server(self.last_used_server + 1)
  73. else:
  74. # returns the next server in a circular range
  75. self.last_used_server = self.last_used_server + 1 if (self.last_used_server + 1) < len(self.servers) else 0
  76. elif self.server_pool.strategy == RANDOM:
  77. if self.server_pool.active:
  78. self.last_used_server = self.find_active_random_server()
  79. else:
  80. # returns a random server in the pool
  81. self.last_used_server = randint(0, len(self.servers) - 1)
  82. else:
  83. if log_enabled(ERROR):
  84. log(ERROR, 'unknown server pooling strategy <%s>', self.server_pool.strategy)
  85. raise LDAPUnknownStrategyError('unknown server pooling strategy')
  86. if log_enabled(BASIC):
  87. log(BASIC, 'server returned from Server Pool: <%s>', self.last_used_server)
  88. return self.servers[self.last_used_server][0]
  89. else:
  90. if log_enabled(ERROR):
  91. log(ERROR, 'no servers in Server Pool <%s>', self)
  92. raise LDAPServerPoolError('no servers in server pool')
  93. def find_active_random_server(self):
  94. counter = self.server_pool.active # can be True for "forever" or the number of cycles to try
  95. while counter:
  96. if log_enabled(NETWORK):
  97. log(NETWORK, 'entering loop for finding active server in pool <%s>', self)
  98. temp_list = self.servers[:] # copy
  99. while temp_list:
  100. # pops a random server from a temp list and checks its
  101. # availability, if not available tries another one
  102. server = temp_list.pop(randint(0, len(temp_list) - 1))
  103. if not server[2]: # server is offline
  104. if (isinstance(self.server_pool.exhaust, bool) and self.server_pool.exhaust) or (datetime.now() - server[1]).seconds < self.server_pool.exhaust: # keeps server offline
  105. if log_enabled(NETWORK):
  106. log(NETWORK, 'server <%s> excluded from checking because it is offline', server[0])
  107. continue
  108. if log_enabled(NETWORK):
  109. log(NETWORK, 'server <%s> reinserted in pool', server[0])
  110. server[1] = datetime.now()
  111. if log_enabled(NETWORK):
  112. log(NETWORK, 'checking server <%s> for availability', server[0])
  113. if server[0].check_availability():
  114. # returns a random active server in the pool
  115. server[2] = True
  116. return self.servers.index(server)
  117. else:
  118. server[2] = False
  119. if not isinstance(self.server_pool.active, bool):
  120. counter -= 1
  121. if log_enabled(ERROR):
  122. log(ERROR, 'no random active server available in Server Pool <%s> after maximum number of tries', self)
  123. raise LDAPServerPoolExhaustedError('no random active server available in server pool after maximum number of tries')
  124. def find_active_server(self, starting):
  125. conf_pool_timeout = get_config_parameter('POOLING_LOOP_TIMEOUT')
  126. counter = self.server_pool.active # can be True for "forever" or the number of cycles to try
  127. if starting >= len(self.servers):
  128. starting = 0
  129. while counter:
  130. if log_enabled(NETWORK):
  131. log(NETWORK, 'entering loop number <%s> for finding active server in pool <%s>', counter, self)
  132. index = -1
  133. pool_size = len(self.servers)
  134. while index < pool_size - 1:
  135. index += 1
  136. offset = index + starting if index + starting < pool_size else index + starting - pool_size
  137. if not self.servers[offset][2]: # server is offline
  138. if (isinstance(self.server_pool.exhaust, bool) and self.server_pool.exhaust) or (datetime.now() - self.servers[offset][1]).seconds < self.server_pool.exhaust: # keeps server offline
  139. if log_enabled(NETWORK):
  140. if isinstance(self.server_pool.exhaust, bool):
  141. log(NETWORK, 'server <%s> excluded from checking because is offline', self.servers[offset][0])
  142. else:
  143. log(NETWORK, 'server <%s> excluded from checking because is offline for %d seconds', self.servers[offset][0], (self.server_pool.exhaust - (datetime.now() - self.servers[offset][1]).seconds))
  144. continue
  145. if log_enabled(NETWORK):
  146. log(NETWORK, 'server <%s> reinserted in pool', self.servers[offset][0])
  147. self.servers[offset][1] = datetime.now()
  148. if log_enabled(NETWORK):
  149. log(NETWORK, 'checking server <%s> for availability', self.servers[offset][0])
  150. if self.servers[offset][0].check_availability():
  151. self.servers[offset][2] = True
  152. return offset
  153. else:
  154. self.servers[offset][2] = False # sets server offline
  155. if not isinstance(self.server_pool.active, bool):
  156. counter -= 1
  157. if log_enabled(NETWORK):
  158. log(NETWORK, 'waiting for %d seconds before retrying pool servers cycle', conf_pool_timeout)
  159. sleep(conf_pool_timeout)
  160. if log_enabled(ERROR):
  161. log(ERROR, 'no active server available in Server Pool <%s> after maximum number of tries', self)
  162. raise LDAPServerPoolExhaustedError('no active server available in server pool after maximum number of tries')
  163. def __len__(self):
  164. return len(self.servers)
  165. class ServerPool(object):
  166. def __init__(self,
  167. servers=None,
  168. pool_strategy=ROUND_ROBIN,
  169. active=True,
  170. exhaust=False):
  171. if pool_strategy not in POOLING_STRATEGIES:
  172. if log_enabled(ERROR):
  173. log(ERROR, 'unknown pooling strategy <%s>', pool_strategy)
  174. raise LDAPUnknownStrategyError('unknown pooling strategy')
  175. if exhaust and not active:
  176. if log_enabled(ERROR):
  177. log(ERROR, 'cannot instantiate pool with exhaust and not active')
  178. raise LDAPServerPoolError('pools can be exhausted only when checking for active servers')
  179. self.servers = []
  180. self.pool_states = dict()
  181. self.active = active
  182. self.exhaust = exhaust
  183. if isinstance(servers, SEQUENCE_TYPES + (Server, )):
  184. self.add(servers)
  185. elif isinstance(servers, STRING_TYPES):
  186. self.add(Server(servers))
  187. self.strategy = pool_strategy
  188. if log_enabled(BASIC):
  189. log(BASIC, 'instantiated ServerPool: <%r>', self)
  190. def __str__(self):
  191. s = 'servers: ' + linesep
  192. if self.servers:
  193. for server in self.servers:
  194. s += str(server) + linesep
  195. else:
  196. s += 'None' + linesep
  197. s += 'Pool strategy: ' + str(self.strategy)
  198. s += ' - ' + 'active: ' + (str(self.active) if self.active else 'False')
  199. s += ' - ' + 'exhaust pool: ' + (str(self.exhaust) if self.exhaust else 'False')
  200. return s
  201. def __repr__(self):
  202. r = 'ServerPool(servers='
  203. if self.servers:
  204. r += '['
  205. for server in self.servers:
  206. r += server.__repr__() + ', '
  207. r = r[:-2] + ']'
  208. else:
  209. r += 'None'
  210. r += ', pool_strategy={0.strategy!r}'.format(self)
  211. r += ', active={0.active!r}'.format(self)
  212. r += ', exhaust={0.exhaust!r}'.format(self)
  213. r += ')'
  214. return r
  215. def __len__(self):
  216. return len(self.servers)
  217. def __getitem__(self, item):
  218. return self.servers[item]
  219. def __iter__(self):
  220. return self.servers.__iter__()
  221. def add(self, servers):
  222. if isinstance(servers, Server):
  223. if servers not in self.servers:
  224. self.servers.append(servers)
  225. elif isinstance(servers, STRING_TYPES):
  226. self.servers.append(Server(servers))
  227. elif isinstance(servers, SEQUENCE_TYPES):
  228. for server in servers:
  229. if isinstance(server, Server):
  230. self.servers.append(server)
  231. elif isinstance(server, STRING_TYPES):
  232. self.servers.append(Server(server))
  233. else:
  234. if log_enabled(ERROR):
  235. log(ERROR, 'element must be a server in Server Pool <%s>', self)
  236. raise LDAPServerPoolError('server in ServerPool must be a Server')
  237. else:
  238. if log_enabled(ERROR):
  239. log(ERROR, 'server must be a Server of a list of Servers when adding to Server Pool <%s>', self)
  240. raise LDAPServerPoolError('server must be a Server or a list of Server')
  241. for connection in self.pool_states:
  242. # notifies connections using this pool to refresh
  243. self.pool_states[connection].refresh()
  244. def remove(self, server):
  245. if server in self.servers:
  246. self.servers.remove(server)
  247. else:
  248. if log_enabled(ERROR):
  249. log(ERROR, 'server %s to be removed not in Server Pool <%s>', server, self)
  250. raise LDAPServerPoolError('server not in server pool')
  251. for connection in self.pool_states:
  252. # notifies connections using this pool to refresh
  253. self.pool_states[connection].refresh()
  254. def initialize(self, connection):
  255. pool_state = ServerPoolState(self)
  256. # registers pool_state in ServerPool object
  257. self.pool_states[connection] = pool_state
  258. def get_server(self, connection):
  259. if connection in self.pool_states:
  260. return self.pool_states[connection].get_server()
  261. else:
  262. if log_enabled(ERROR):
  263. log(ERROR, 'connection <%s> not in Server Pool State <%s>', connection, self)
  264. raise LDAPServerPoolError('connection not in ServerPoolState')
  265. def get_current_server(self, connection):
  266. if connection in self.pool_states:
  267. return self.pool_states[connection].get_current_server()
  268. else:
  269. if log_enabled(ERROR):
  270. log(ERROR, 'connection <%s> not in Server Pool State <%s>', connection, self)
  271. raise LDAPServerPoolError('connection not in ServerPoolState')