示例#1
0
 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
示例#2
0
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
示例#3
0
 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
示例#4
0
 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')
示例#5
0
    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()
示例#6
0
    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()
示例#7
0
    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()
示例#8
0
    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)
示例#9
0
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': {},
    })
示例#10
0
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')
示例#11
0
    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})
示例#12
0
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)
示例#13
0
    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)
示例#14
0
    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))
示例#15
0
 def _make_im(self):
     return InstanceManagement(huskar_client, APP_NAME, CONFIG_SUBDOMAIN)
示例#16
0
def instance_management(test_application_name):
    return InstanceManagement(huskar_client, test_application_name,
                              SERVICE_SUBDOMAIN)
示例#17
0
    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()
示例#18
0
def instance_management(application_name):
    return InstanceManagement(huskar_client, application_name, 'service')
示例#19
0
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
示例#20
0
 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)