api2.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. #!/usr/bin/env python
  2. # Licensed to Cloudera, Inc. under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. Cloudera, Inc. licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import logging
  18. import json
  19. import random
  20. import threading
  21. import time
  22. from django.utils.translation import ugettext as _
  23. from desktop.lib.exceptions_renderable import PopupException
  24. from libzookeeper.models import ZookeeperClient
  25. from libsentry.client2 import SentryClient
  26. from libsentry.conf import HOSTNAME, PORT
  27. from libsentry.sentry_site import get_sentry_server_ha_enabled, get_sentry_server_ha_zookeeper_quorum, get_sentry_server_ha_zookeeper_namespace
  28. LOG = logging.getLogger(__name__)
  29. _api_cache = None
  30. _api_cache_lock = threading.Lock()
  31. def ha_error_handler(func):
  32. def decorator(*args, **kwargs):
  33. retries = 15
  34. while retries > 0:
  35. try:
  36. return func(*args, **kwargs)
  37. except SentryException, e:
  38. raise e
  39. except Exception, e:
  40. retries -= 1
  41. if not get_sentry_server_ha_enabled() or retries == 0:
  42. raise e
  43. else:
  44. # Right now retries on any error and pull a fresh list of servers from ZooKeeper
  45. LOG.info('Retrying fetching an available client in ZooKeeper.')
  46. global _api_cache
  47. _api_cache = None
  48. time.sleep(1)
  49. args[0].client = _get_client(args[0].client.username, args[0].client.component)
  50. LOG.info('Picked %s' % args[0].client)
  51. return decorator
  52. def get_api(user, component):
  53. if component == 'solr':
  54. component = component.upper()
  55. client = _get_client(user.username, component)
  56. return SentryApi(client)
  57. class SentryApi(object):
  58. def __init__(self, client):
  59. self.client = client
  60. @ha_error_handler
  61. def create_sentry_role(self, roleName):
  62. response = self.client.create_sentry_role(roleName)
  63. if response.status.value == 0:
  64. return response
  65. else:
  66. raise SentryException(response)
  67. @ha_error_handler
  68. def drop_sentry_role(self, roleName):
  69. response = self.client.drop_sentry_role(roleName)
  70. if response.status.value == 0:
  71. return response
  72. else:
  73. raise SentryException(response)
  74. @ha_error_handler
  75. def alter_sentry_role_grant_privilege(self, roleName, tSentryPrivilege=None):
  76. response = self.client.alter_sentry_role_grant_privilege(roleName, tSentryPrivilege)
  77. if response.status.value == 0:
  78. return response
  79. else:
  80. raise SentryException(response)
  81. @ha_error_handler
  82. def alter_sentry_role_revoke_privilege(self, roleName, tSentryPrivilege=None):
  83. response = self.client.alter_sentry_role_revoke_privilege(roleName, tSentryPrivilege)
  84. if response.status.value == 0:
  85. return response
  86. else:
  87. raise SentryException(response)
  88. @ha_error_handler
  89. def alter_sentry_role_add_groups(self, roleName, groups):
  90. response = self.client.alter_sentry_role_add_groups(roleName, groups)
  91. if response.status.value == 0:
  92. return response
  93. else:
  94. raise SentryException(response)
  95. @ha_error_handler
  96. def alter_sentry_role_delete_groups(self, roleName, groups):
  97. response = self.client.alter_sentry_role_delete_groups(roleName, groups)
  98. if response.status.value == 0:
  99. return response
  100. else:
  101. raise SentryException(response)
  102. @ha_error_handler
  103. def list_sentry_roles_by_group(self, groupName=None):
  104. response = self.client.list_sentry_roles_by_group(groupName)
  105. if response.status.value == 0:
  106. roles = []
  107. for role in response.roles:
  108. roles.append({
  109. 'name': role.roleName,
  110. 'groups': list(role.groups)
  111. })
  112. return roles
  113. else:
  114. raise SentryException(response)
  115. @ha_error_handler
  116. def list_sentry_privileges_by_role(self, serviceName, roleName, authorizables=None):
  117. response = self.client.list_sentry_privileges_by_role(serviceName, roleName, authorizables)
  118. if response.status.value == 0:
  119. return [self._massage_privilege(privilege) for privilege in response.privileges]
  120. else:
  121. raise SentryException(response)
  122. @ha_error_handler
  123. def list_sentry_privileges_for_provider(self, groups, roleSet=None, authorizableHierarchy=None):
  124. response = self.client.list_sentry_privileges_for_provider(groups, roleSet, authorizableHierarchy)
  125. if response.status.value == 0:
  126. return response
  127. else:
  128. raise SentryException(response)
  129. @ha_error_handler
  130. def list_sentry_privileges_by_authorizable(self, serviceName, authorizableSet, groups=None, roleSet=None):
  131. response = self.client.list_sentry_privileges_by_authorizable(serviceName, authorizableSet, groups, roleSet)
  132. if response.status.value != 0:
  133. raise SentryException(response)
  134. _privileges = []
  135. for authorizable, roles in response.privilegesMapByAuth.iteritems():
  136. _roles = {}
  137. for role, privileges in roles.privilegeMap.iteritems():
  138. _roles[role] = [self._massage_privilege(privilege) for privilege in privileges]
  139. _privileges.append((self._massage_string_authorizable(authorizable), _roles))
  140. return _privileges
  141. @ha_error_handler
  142. def drop_sentry_privileges(self, authorizableHierarchy):
  143. response = self.client.drop_sentry_privilege(authorizableHierarchy)
  144. if response.status.value == 0:
  145. return response
  146. else:
  147. raise SentryException(response)
  148. @ha_error_handler
  149. def rename_sentry_privileges(self, oldAuthorizable, newAuthorizable):
  150. response = self.client.rename_sentry_privilege(oldAuthorizable, newAuthorizable)
  151. if response.status.value == 0:
  152. return response
  153. else:
  154. raise SentryException(response)
  155. def _massage_privilege(self, privilege):
  156. return {
  157. 'component': privilege.component,
  158. 'serviceName': privilege.serviceName,
  159. 'authorizables': self._massage_authorizable(privilege.authorizables),
  160. 'action': 'ALL' if privilege.action == '*' else privilege.action.upper(),
  161. 'timestamp': privilege.createTime,
  162. 'grantorPrincipal': privilege.grantorPrincipal,
  163. 'grantOption': privilege.grantOption == 1,
  164. }
  165. def _massage_authorizable(self, authorizables):
  166. return [{'type': auth.type, 'name': auth.name} for auth in authorizables]
  167. def _massage_string_authorizable(self, authorizables):
  168. return [{'type': auth.split('=')[0], 'name': auth.split('=')[1]} for auth in authorizables.split('->')]
  169. class SentryException(Exception):
  170. def __init__(self, e):
  171. super(SentryException, self).__init__(e)
  172. self.message = e.status.message
  173. def __str__(self):
  174. return self.message
  175. def _get_client(username, component):
  176. if get_sentry_server_ha_enabled():
  177. servers = _get_server_properties()
  178. if servers:
  179. server = random.choice(servers)
  180. else:
  181. raise PopupException(_('No Sentry servers are available.'))
  182. else:
  183. server = {
  184. 'hostname': HOSTNAME.get(),
  185. 'port': PORT.get()
  186. }
  187. return SentryClient(server['hostname'], server['port'], username, component)
  188. def _get_server_properties():
  189. global _api_cache
  190. if not _api_cache: # If we need to refresh the list or if previously no servers were up
  191. _api_cache_lock.acquire()
  192. try:
  193. if not _api_cache:
  194. servers = []
  195. with ZookeeperClient(hosts=get_sentry_server_ha_zookeeper_quorum()) as client:
  196. sentry_servers = client.get_children_data(namespace=get_sentry_server_ha_zookeeper_namespace())
  197. for data in sentry_servers:
  198. server = json.loads(data.decode("utf-8"))
  199. servers.append({'hostname': server['address'], 'port': server['sslPort'] if server['sslPort'] else server['port']})
  200. _api_cache = servers
  201. except Exception, e:
  202. raise PopupException(_('Error in retrieving Sentry server properties from Zookeeper.'), detail=e)
  203. finally:
  204. _api_cache_lock.release()
  205. return _api_cache