class ServiceRegistryClient(object): def __init__(self, clock, cluster_nodes): self.clock = clock self.cluster_nodes = [] for cluster_node in cluster_nodes: if not cluster_node.startswith('http://'): cluster_node = 'http://%s' % (cluster_node,) self.cluster_nodes.append((cluster_node, requests.Session())) random.shuffle(self.cluster_nodes) self.breaker = CircuitBreakerSet(clock.time, logging.getLogger( 'service-discovery-client')) self.breaker.handle_error(RequestException) self.breaker.handle_error(ConnectionError) self.breaker.handle_error(TooManyRedirects) # backward compatible self.resolve = Resolver(self).resolve_url def _request(self, method, uri, **kwargs): """Issue a request to SOME of the nodes in the cluster.""" for node, session in self.cluster_nodes: try: with self.breaker.context(node): response = session.request(method, urljoin(node, uri), **kwargs) if response.status_code >= 500: raise RequestException() return response except CircuitOpenError: continue else: raise Exception("NO MACHIEN TO TALK TOOO") def register(self, form_name, service, instance_name, data): """Register an instance with a formation.""" return _Registration(self, form_name, service, instance_name, data).start() def build_announcement(self, formation, service, instance, ports={}, **kwargs): announcement = { 'formation': formation, 'service': service, 'instance': instance, 'ports': ports.copy(), } announcement.update(kwargs) return announcement def query_formation(self, form_name, factory=dict): """Query all instances of a formation. Will return a generator that yields (instance name, data) for each instance. @param factory: Data factory. Will be passed a JSON object of the instance data, expects to return a representation of that data. """ response = self._request('GET', '/%s' % (form_name,)) response.raise_for_status() for key, data in response.json().items(): yield (key, factory(data)) def formation_cache(self, form_name, factory=dict, interval=15): """Return a cache for a specific formation that will be kept up to date until stopped. """ return _FormationCache(self, form_name, factory, interval).start()
class ServiceRegistryClient(object): def __init__(self, clock, cluster_nodes=None): self.clock = clock self.cluster_nodes = [] if cluster_nodes is None: cluster_nodes = os.getenv( 'GILLIAM_SERVICE_REGISTRY', '').split(',') for cluster_node in cluster_nodes: if not cluster_node.startswith('http://'): cluster_node = 'http://%s' % (cluster_node,) self.cluster_nodes.append((cluster_node, requests.Session())) random.shuffle(self.cluster_nodes) self.breaker = CircuitBreakerSet(clock.time, logging.getLogger( 'service-discovery-client')) self.breaker.handle_error(RequestException) self.breaker.handle_error(ConnectionError) self.breaker.handle_error(TooManyRedirects) # backward compatible self.resolve = Resolver(self).resolve_url def _request(self, method, uri, **kwargs): """Issue a request to SOME of the nodes in the cluster.""" for node, session in self.cluster_nodes: try: with self.breaker.context(node): response = session.request(method, urljoin(node, uri), **kwargs) if response.status_code >= 500: raise RequestException() return response except CircuitOpenError: continue else: raise Exception("NO MACHIEN TO TALK TOOO") def register(self, form_name, service, instance_name, data): """Register an instance with a formation.""" return _Registration(self, form_name, service, instance_name, data).start() def build_announcement(self, formation, service, instance, ports={}, **kwargs): announcement = { 'formation': formation, 'service': service, 'instance': instance, 'ports': ports.copy(), } announcement.update(kwargs) return announcement def query_formation(self, form_name, factory=dict): """Query all instances of a formation. Will return a generator that yields (instance name, data) for each instance. @param factory: Data factory. Will be passed a JSON object of the instance data, expects to return a representation of that data. """ response = self._request('GET', '/%s' % (form_name,)) response.raise_for_status() for key, data in response.json().items(): yield (key, factory(data)) def formation_cache(self, form_name, factory=dict, interval=15): """Return a cache for a specific formation that will be kept up to date until stopped. """ return _FormationCache(self, form_name, factory, interval).start()
class ServiceRegistryClient(object): def __init__(self, clock, cluster_nodes): self.clock = clock self.cluster_nodes = [] for cluster_node in cluster_nodes: if not cluster_node.startswith('http://'): cluster_node = 'http://%s' % (cluster_node,) self.cluster_nodes.append((cluster_node, requests.Session())) random.shuffle(self.cluster_nodes) self.breaker = CircuitBreakerSet(clock.time, logging.getLogger( 'service-discovery-client')) self.breaker.handle_error(RequestException) self.breaker.handle_error(ConnectionError) self.breaker.handle_error(TooManyRedirects) def _request(self, method, uri, **kwargs): """Issue a request to SOME of the nodes in the cluster.""" for node, session in self.cluster_nodes: try: with self.breaker.context(node): response = session.request(method, urljoin(node, uri), **kwargs) if response.status_code >= 500: raise RequestException() return response except CircuitOpenError: continue else: raise Exception("NO MACHIEN TO TALK TOOO") def register(self, form_name, instance_name, data): """Register an instance with a formation.""" return _Registration(self, form_name, instance_name, data).start() def query_formation(self, form_name, factory=dict): """Query all instances of a formation. Will return a generator that yields (instance name, data) for each instance. @param factory: Data factory. Will be passed a JSON object of the instance data, expects to return a representation of that data. """ response = self._request('GET', '/%s' % (form_name,)) response.raise_for_status() for key, data in response.json().items(): yield (key, factory(data)) def formation_cache(self, form_name, factory=dict, interval=15): """Return a cache for a specific formation that will be kept up to date until stopped. """ return _FormationCache(self, form_name, factory, interval).start() def _resolve_port(self, d, port): if str(port) not in d['ports']: raise ValueError("instance do not expose port") return d['ports'][str(port)] # FIXME: refactor def _resolve_any(self, port, formation, service): alts = [d for (k, d) in self.query_formation(formation) if k.startswith(service + '.')] if not alts: raise Exception("no instances") alt = random.choice(alts) return alt['host'], self._resolve_port(alt, port) def _resolve_specific(self, port, formation, service, name): alts = [d for (k, d) in self.query_formation(formation) if k.startswith(service + '.' + name)] if not alts: raise Exception("no instances") alt = random.choice(alts) return alt['host'], self._resolve_port(alt, port) def resolve(self, url): """Resolve a URL into a direct url.""" u = urlparse.urlsplit(url) assert u.hostname.endswith('.service'), "must end with .service" parts = u.hostname.split('.') if len(parts) == 4: hostname, port = self._resolve_specific(u.port, parts[2], parts[1], parts[0]) elif len(parts) == 3: hostname, port = self._resolve_any(u.port, parts[1], parts[0]) netloc = '%s:%d' % (hostname, port) return urlparse.urlunsplit((u.scheme, netloc, u.path, u.query, u.fragment))