def _get_cluster_info(self, application_name, cluster_name): im = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) try: return im.get_cluster_info(cluster_name) except MalformedDataError as e: logger.warning('Failed to parse symlink "%s"', e.info.path) return e.info
def instance_list(request, container_id): instance_list = [] for application_name, cluster_name in request.param: im = InstanceManagement(huskar_client, application_name, 'service') instance, _ = im.get_instance(cluster_name, container_id) instance.data = '' instance.save() instance_list.append(instance) return instance_list
def setup_default_zpath(self): im = InstanceManagement(huskar_client, self.application_name, SERVICE_SUBDOMAIN) info = im.get_service_info() try: if info.stat is None: info.data = {} info.save() except OutOfSyncError: pass
def delete_cluster(cls, application, cluster, strict=False): data_type = cls.client.sub_domain im = InstanceManagement(huskar_client, application, data_type) try: cluster_info = im.delete_cluster(cluster) except NotEmptyError as e: if strict: raise DataNotEmptyError(*e.args) else: if cluster_info is None and strict: raise DataNotExistsError('cluster does not exist')
def delete(self, application_name, cluster_name): """Deregisters a service instance. The ``write`` authority is required. See :ref:`application_auth` also. :param application_name: The name of application. :param cluster_name: The name of cluster. :form key: The name of instance. :<header Authorization: Huskar Token (See :ref:`token`) :status 404: The application is not found. :status 400: The request body is invalid. :status 200: The service instance is deregistered successfully. """ check_application_auth(application_name, Authority.WRITE) check_cluster_name(cluster_name, application_name) key = request.values['key'].strip() validate_fields(instance_schema, { 'application': application_name, 'cluster': cluster_name, 'key': key, }) im = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) instance, _ = im.get_instance(cluster_name, key, resolve=False) if instance.stat is None: abort( 404, '%s %s/%s/%s does not exist' % ( SERVICE_SUBDOMAIN, application_name, cluster_name, key, )) old_data = instance.data with audit_log(audit_log.types.DELETE_SERVICE, application_name=application_name, cluster_name=cluster_name, key=key, old_data=old_data): service_facade.delete(application_name, cluster_name, key, strict=True) # Writes container registry finally if is_container_id(key): cm = ContainerManagement(huskar_client, key) cm.deregister_from(application_name, cluster_name) return api_response()
def delete(self, application_name, cluster_name): """Deletes an instance. The ``write`` authority is required. See :ref:`application_auth` also. .. note:: When you delete the last instance of a cluster, the cluster will not be deleted automaticlly. Use the :ref:`cluster` API if you want to delete the empty cluster manually. :param application_name: The name of application. :param cluster_name: The name of cluster. :form key: The key of specified instance. :<header Authorization: Huskar Token (See :ref:`token`) :status 404: The application is not found. :status 200: The request is successful. """ check_application_auth(application_name, Authority.WRITE) check_cluster_name(cluster_name, application_name) key = request.values['key'].strip() validate_fields(instance_schema, { 'application': application_name, 'cluster': cluster_name, 'key': key, }) im = InstanceManagement(huskar_client, application_name, self.subdomain) instance, _ = im.get_instance(cluster_name, key, resolve=False) if instance.stat is None: abort( 404, '%s %s/%s/%s does not exist' % ( self.subdomain, application_name, cluster_name, key, )) old_data = instance.data with audit_log(self.DELETE_ACTION_TYPES[self.subdomain], application_name=application_name, cluster_name=cluster_name, key=key, old_data=old_data): self.facade.delete(application_name, cluster_name, key, strict=True) self._delete_comment(application_name, cluster_name, key) return api_response()
def delete(self, application_name, **kwargs): """Deletes all fields. The ``write`` authority is required. See :ref:`application_auth` also. :param application_name: The name of application. :param cluster_name: Optional. The name of cluster. :<header Authorization: Huskar Token (See :ref:`token`) :<header Content-Type: ``application/json`` :status 200: The request is successful. """ check_application_auth(application_name, Authority.WRITE) cluster_name = kwargs.get('cluster_name') if cluster_name: check_cluster_name(cluster_name, application_name) instance_management = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) info = self._load_silently(instance_management, **kwargs) old_data = info.get_info() info.set_info({}) info.save() audit_log.emit(self.UPDATE_ACTION_TYPE, old_data=old_data, new_data={}, application_name=application_name, **kwargs) return api_response()
def get(self, application_name, **kwargs): """Gets the service info. The response looks like:: { "data": { "protocol": "MySQL" }, "message": "", "status": "SUCCESS" } :param application_name: The name of application. :param cluster_name: Optional. The name of cluster. :<header Authorization: Huskar Token (See :ref:`token`) :status 404: The application or cluster does not exist. :status 200: The request is successful. """ cluster_name = kwargs.get('cluster_name') if cluster_name: check_cluster_name(cluster_name, application_name) instance_management = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) try: info = self._load(instance_management, **kwargs) except MalformedDataError: abort(404, 'Invalid data found.') else: data = info.get_info() return api_response(data)
def test_watch_multi_services( mocker, watcher, route_management, instance_management, test_application_name, set_route): test_application_name_bar = '{}_bar'.format(test_application_name) # Setup initial data set_route(test_application_name, 'alta1-channel-stable-1') set_route(test_application_name_bar, 'alta1-channel-stable-2') instance, _ = instance_management.get_instance( 'alta1-channel-stable-1', '169.254.0.1_5000', resolve=False) instance.data = '{}' instance.save() instance_management_bar = InstanceManagement( huskar_client, test_application_name_bar, SERVICE_SUBDOMAIN) instance_bar, _ = instance_management_bar.get_instance( 'alta1-channel-stable-2', '169.254.0.2_5000', resolve=False) instance_bar.data = '{}' instance_bar.save() # Setup watcher watcher.with_initial = True watcher.from_application_name = route_management.application_name watcher.from_cluster_name = route_management.cluster_name watcher.limit_cluster_name(test_application_name, 'service', 'direct') watcher.limit_cluster_name(test_application_name_bar, 'service', 'direct') watcher.holders.add(mocker.Mock(type_name='foobar')) watcher.watch(test_application_name, 'service') watcher.watch(test_application_name, 'config') watcher.watch(test_application_name_bar, 'service') watcher.watch(test_application_name_bar, 'config') assert next(iter(watcher)) == ('all', { 'service': { test_application_name: {'direct': { '169.254.0.1_5000': {'value': '{}'}, }}, test_application_name_bar: {'direct': { '169.254.0.2_5000': {'value': '{}'}, }}, }, 'switch': {}, 'config': {test_application_name: {}, test_application_name_bar: {}}, 'service_info': {}, })
def test_invalid_choice(application_name): instance_management = InstanceManagement( huskar_client, application_name, 'config') with raises(AssertionError): InstanceManagement(huskar_client, application_name, 'woo') with raises(AssertionError): instance_management.get_service_info() with raises(AssertionError): instance_management.get_cluster_info('any')
def get(self, application_name, cluster_name, key): """Gets the weight of specified service instance. :param application_name: The name of application. :param cluster_name: The name of cluster. :param key: The key of service instance. :<header Authorization: Huskar Token (See :ref:`token`) :status 404: The instance is not found. :status 200: The response looks like ``{"status":"SUCCESS", "data": {"weight": 10}}`` """ check_cluster_name(cluster_name, application_name) validate_fields(instance_schema, { 'application': application_name, 'cluster': cluster_name, 'key': key, }) im = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) instance, _ = im.get_instance(cluster_name, key, resolve=False) if instance.stat is None: abort( 404, '%s %s/%s/%s does not exist' % ( SERVICE_SUBDOMAIN, application_name, cluster_name, key, )) try: data = json.loads(instance.data) weight = int(data['meta']['weight']) except (ValueError, TypeError, KeyError): weight = 0 return api_response({'weight': weight})
def _vacuum_empty_clusters(type_name): for application_name in application_manifest.as_list(): if application_name in settings.AUTH_APPLICATION_BLACKLIST: continue logger.info('[%s] Check application %s', type_name, application_name) im = InstanceManagement(huskar_client, application_name, type_name) try: for cluster_name in im.list_cluster_names(): ident = (application_name, type_name, cluster_name) try: im.delete_cluster(cluster_name) except OutOfSyncError: logger.info('Skip %r because of changed version.', ident) except NotEmptyError as e: logger.info( 'Skip %r because %s.', ident, e.args[0].lower()) except MalformedDataError: logger.info('Skip %r because of unrecognized data.', ident) else: logger.info('Okay %r is gone.', ident) except Exception as e: logger.exception('Skip %s because %s.', application_name, e)
def _check_request(self, application_name, intent_map): if application_name in settings.LEGACY_APPLICATION_LIST: return im = InstanceManagement( self.huskar_client, application_name, SERVICE_SUBDOMAIN) im.set_context(self.from_application_name, self.from_cluster_name) for intent, icluster_names in intent_map.iteritems(): if not icluster_names: # pragma: no cover # TODO: fix continue dest_cluster_blacklist = settings.ROUTE_DEST_CLUSTER_BLACKLIST.get( application_name, []) if self.hijack_mode is self.Mode.checking: if icluster_names & set(dest_cluster_blacklist): continue if len(icluster_names) > 1: logger.info( '[%s]Unstable: %s %s -> %s %s', self.hijack_mode.value, self.from_application_name, self.from_cluster_name, application_name, intent_map) capture_message( '[%s]RouteHijack unstable' % self.hijack_mode.value, extra={ 'from_application_name': ( self.from_application_name), 'from_cluster_name': self.from_cluster_name, 'application_name': application_name, 'intent_map': repr(intent_map), 'intent': intent, }) continue resolved_name = im.resolve_cluster_name(intent) cluster_name = list(icluster_names)[0] cluster_name = ( im.resolve_cluster_name(cluster_name) or cluster_name) if resolved_name != cluster_name: logger.info( '[%s]Mismatch: %s %s -> %s %s %s %s', self.hijack_mode.value, self.from_application_name, self.from_cluster_name, application_name, intent_map, resolved_name, cluster_name) if self.hijack_mode is self.Mode.checking: capture_message( '[%s]RouteHijack mismatch' % self.hijack_mode.value, extra={ 'from_application_name': ( self.from_application_name), 'from_cluster_name': self.from_cluster_name, 'application_name': application_name, 'cluster_name': cluster_name, 'intent_map': repr(intent_map), 'intent': intent, 'resolved_name': resolved_name, }) self.analyse_mismatch(application_name, cluster_name, resolved_name, intent, intent_map)
def set_link(cls, application_name, cluster_name, link): im = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) if not im.list_instance_keys(link, resolve=False): raise ServiceLinkError('the target cluster is empty.') present_link_generator = (im.resolve_cluster_name(c) == cluster_name for c in im.list_cluster_names()) if any(present_link_generator): raise ServiceLinkError(('{} has been linked, cluster can only be ' 'linked once').format(cluster_name)) if im.resolve_cluster_name(link): raise ServiceLinkError( ('there is a link under {}, cluster can only be ' 'linked once').format(link)) if not cls._set_link(application_name, cluster_name, link): raise ServiceLinkExisted('{} is already linked to {}'.format( cluster_name, link))
def _make_im(self): return InstanceManagement(huskar_client, APP_NAME, CONFIG_SUBDOMAIN)
def instance_management(test_application_name): return InstanceManagement(huskar_client, test_application_name, SERVICE_SUBDOMAIN)
def post(self, application_name, cluster_name, key): """Updates the weight of specified service instance. :param application_name: The name of application. :param cluster_name: The name of cluster. :param key: The key of service instance. :form weight: The target weight of instance. It should be a positive integer. :form ephemeral: Whether the modification be ephemeral or persistent. Must be ephemeral (``1``) for now. :form ttl: When ephemeral is ``1``, will use this value as ttl. default: 5 * 60 s. :<header Authorization: Huskar Token (See :ref:`token`) :status 400: The request body is invalid. :status 404: The instance is not found. :status 409: There is another request has modified the instance. :status 200: The weight is updated successfully. """ check_application_auth(application_name, Authority.WRITE) check_cluster_name(cluster_name, application_name) validate_fields(instance_schema, { 'application': application_name, 'cluster': cluster_name, 'key': key, }) weight = request.form.get('weight', type=int) if not weight or weight < 0: abort(400, 'weight must be a positive integer') weight = unicode(weight) ephemeral = request.form.get('ephemeral', type=int) if ephemeral != 1: abort(400, 'ephemeral must be "1" for now') im = InstanceManagement(huskar_client, application_name, SERVICE_SUBDOMAIN) instance, _ = im.get_instance(cluster_name, key, resolve=False) if instance.stat is None: abort( 404, '%s %s/%s/%s does not exist' % ( SERVICE_SUBDOMAIN, application_name, cluster_name, key, )) try: old_data = instance.data new_data = json.loads(instance.data) meta = new_data.setdefault('meta', {}) meta['weight'] = weight new_data = json.dumps(new_data) instance.data = new_data except (ValueError, TypeError): capture_exception() # unexpected exception should be captured abort(500) try: instance.save() except OutOfSyncError: abort( 409, '%s %s/%s/%s has been modified by another request' % ( SERVICE_SUBDOMAIN, application_name, cluster_name, key, )) audit_log.emit(audit_log.types.UPDATE_SERVICE, application_name=application_name, cluster_name=cluster_name, key=key, old_data=old_data, new_data=new_data) return api_response()
def instance_management(application_name): return InstanceManagement(huskar_client, application_name, 'service')
class InstanceFacade(object): client = huskar_client def __init__(self, subdomain, application_name, include_comment=True): self.subdomain = subdomain self.application_name = application_name self.include_comment = include_comment self.im = InstanceManagement(self.client, application_name, subdomain) self.im.set_context(g.application_name, g.cluster_name) def fetch_instance_list(self, pairs, resolve=True): include_comment = self.include_comment and not g.auth.is_minimal_mode for cluster_name, key in pairs: info, physical_name = self.im.get_instance(cluster_name, key, resolve=resolve) if info.stat is None: continue data = { 'application': self.application_name, 'cluster': cluster_name, 'key': key, 'value': info.data, 'meta': self.make_meta_info(info), } if self.subdomain == SERVICE_SUBDOMAIN: data['runtime'] = self.make_runtime_field(info) if physical_name: data['cluster_physical_name'] = physical_name if include_comment: comment = get_comment(self.application_name, cluster_name, self.subdomain, key) data['comment'] = comment yield data @retry(OutOfSyncError, interval=0.5, max_retry=3) def set_instance(self, cluster_name, key, value, comment=None, overwrite=False): instance, _ = self.im.get_instance(cluster_name, key, resolve=False) if overwrite or instance.stat is None: instance.data = value instance.save() if self.include_comment and comment is not None: set_comment(self.application_name, cluster_name, self.subdomain, key, comment) return instance def get_instance(self, cluster_name, key, resolve=True): iterator = self.fetch_instance_list([(cluster_name, key)], resolve=resolve) return next(iterator, None) def get_instance_list(self, resolve=True): pairs = ((cluster_name, key) for cluster_name in self.im.list_cluster_names() for key in self.im.list_instance_keys(cluster_name, resolve=resolve)) return list(self.fetch_instance_list(pairs, resolve=resolve)) def get_instance_list_by_cluster(self, cluster_name, resolve=True): keys = self.im.list_instance_keys(cluster_name, resolve=resolve) pairs = ((cluster_name, k) for k in keys) return list(self.fetch_instance_list(pairs, resolve=resolve)) def get_merged_instance_list(self, cluster_name): overall_instance_list = self.get_instance_list_by_cluster(OVERALL) current_instance_list = self.get_instance_list_by_cluster(cluster_name) return merge_instance_list(self.application_name, overall_instance_list, current_instance_list, cluster_name) def get_cluster_list(self): cluster_names = self.im.list_cluster_names() for cluster_name in cluster_names: cluster_info = None if self.subdomain == SERVICE_SUBDOMAIN: try: cluster_info = self.im.get_cluster_info(cluster_name) meta = self.make_meta_info(cluster_info, is_cluster=True) except MalformedDataError as e: logger.warning('Failed to parse info "%s"', e.info.path) meta = self.make_meta_info(e.info, is_cluster=True) if cluster_info and cluster_info.data: route_map = cluster_info.get_route() yield { 'name': cluster_name, 'physical_name': cluster_info.get_link(), 'route': sorted(self.make_route_list(route_map)), 'meta': meta } else: yield { 'name': cluster_name, 'physical_name': None, 'route': [], 'meta': meta } else: yield {'name': cluster_name} @classmethod def check_instance_key(cls, subdomain, application_name, cluster_name, key): if (subdomain == CONFIG_SUBDOMAIN and key in INFRA_CONFIG_KEYS.values()): abort(400, 'The key {key} is reserved.'.format(key=key)) cls.check_instance_key_in_creation(subdomain, application_name, cluster_name, key) @classmethod def check_instance_key_in_creation(cls, subdomain, application_name, cluster_name, key): if subdomain != CONFIG_SUBDOMAIN: return if not switch.is_switched_on(SWITCH_ENABLE_CONFIG_PREFIX_BLACKLIST, False): return if config_facade.exists(application_name, cluster_name, key=key): return for prefix in settings.CONFIG_PREFIX_BLACKLIST: if key.startswith(prefix): abort( 400, 'The key {key} starts with {prefix} is denied.'.format( key=key, prefix=prefix)) @classmethod def make_route_list(cls, route_map): for route_key, cluster_name in route_map.iteritems(): route_key = parse_route_key(route_key) yield { 'application_name': route_key.application_name, 'intent': route_key.intent, 'cluster_name': cluster_name } @classmethod def make_runtime_field(cls, info): try: value = json.loads(info.data) except (TypeError, ValueError): logger.warning('Failed to parse %r', info.path) return if not isinstance(value, dict): logger.warning('Unexpected schema of %r', info.path) return state = value.get('state') or None return state and json.dumps({'state': state}) @classmethod def make_meta_info(cls, info, is_cluster=False): meta = { 'last_modified': int(info.stat.last_modified * 1000), 'created': int(info.stat.created * 1000), 'version': info.stat.version, } if is_cluster: if info.get_link() is None: meta['instance_count'] = info.stat.children_count else: meta['is_symbol_only'] = info.stat.children_count == 0 return meta
def __init__(self, subdomain, application_name, include_comment=True): self.subdomain = subdomain self.application_name = application_name self.include_comment = include_comment self.im = InstanceManagement(self.client, application_name, subdomain) self.im.set_context(g.application_name, g.cluster_name)