All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.monitor.endpoint.MonitorServiceEndpoint Maven / Gradle / Ivy

package org.zodiac.monitor.endpoint;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.zodiac.monitor.data.ChangeItem;
import org.zodiac.monitor.data.Service;
import org.zodiac.monitor.data.ServiceHealth;
import org.zodiac.monitor.service.MonitorRegistrationService;
import org.zodiac.sdk.toolkit.util.AssertUtil;

import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.Collections;

/**
 * consul catalog api.
 *
 */
public class MonitorServiceEndpoint {

    private static final String CONSUL_IDX_HEADER = "X-Consul-Index";
    private static final String QUERY_PARAM_WAIT = "wait";
    private static final String QUERY_PARAM_INDEX = "index";
    private static final Pattern WAIT_PATTERN = Pattern.compile("(\\d*)(m|s|ms|h)");

    private final MonitorRegistrationService monitorRegistrationService;

    public MonitorServiceEndpoint(MonitorRegistrationService monitorRegistrationService) {
        this.monitorRegistrationService = monitorRegistrationService;
    }

//    @GetMapping(value = "/v1/catalog/services", produces = HttpMediaType.APPLICATION_JSON_VALUE)
    public Mono>> getServiceNames(
        @RequestParam(name = QUERY_PARAM_WAIT, required = false) String wait,
        @RequestParam(name = QUERY_PARAM_INDEX, required = false) Long index) {
        return getServiceNames(getWaitMillis(wait), index)
            .map(item -> createResponseEntity(item.getItem(), item.getChangeIndex()));
    }

//    @GetMapping(value = "/v1/catalog/service/{appName}", produces = HttpMediaType.APPLICATION_JSON_VALUE)
    public Mono>> getService(@PathVariable("appName") String appName,
        @RequestParam(value = QUERY_PARAM_WAIT, required = false) String wait,
        @RequestParam(value = QUERY_PARAM_INDEX, required = false) Long index) {
        Objects.requireNonNull(appName, "service name can not be null");
        return getService(appName, getWaitMillis(wait), index)
            .map(item -> createResponseEntity(item.getItem(), item.getChangeIndex()));
    }

//    @GetMapping(value = "/v1/health/service/{appName}", produces = HttpMediaType.APPLICATION_JSON_VALUE)
    public Mono>> getServiceHealth(@PathVariable("appName") String appName,
        @RequestParam(value = QUERY_PARAM_WAIT, required = false) String wait,
        @RequestParam(value = QUERY_PARAM_INDEX, required = false) Long index) {
        AssertUtil.isTrue(appName != null, "service name can not be null");
        return getService(appName, getWaitMillis(wait), index).map(item -> {
            List services =
                item.getItem().stream().map(this::getServiceHealth).collect(Collectors.toList());
            return createResponseEntity(services, item.getChangeIndex());
        });
    }

    protected final Mono>> getServiceNames(long waitMillis, Long index) {
        return monitorRegistrationService.getServiceNames(waitMillis, index);
    }

    protected final Mono>> getService(String appName, long waitMillis, Long index) {
        return monitorRegistrationService.getService(appName, waitMillis, index);
    }

    protected final ServiceHealth getServiceHealth(Service instanceInfo) {
        String address = instanceInfo.getAddress();
        ServiceHealth.Node node = new ServiceHealth.Node().setName(instanceInfo.getServiceName()).setAddress(address).setMeta(Collections.emptyMap());
        ServiceHealth.Service service = new ServiceHealth.Service().setId(instanceInfo.getServiceId())
            .setName(instanceInfo.getServiceName()).setTags(Collections.emptyList()).setAddress(address)
            .setMeta(instanceInfo.getServiceMeta()).setPort(instanceInfo.getServicePort());
        ServiceHealth.Check check = new ServiceHealth.Check().setNode(instanceInfo.getServiceName())
            .setCheckId("service:" + instanceInfo.getServiceId())
            .setName("Service '" + instanceInfo.getServiceId() + "' check")
            // nacos 实时性很高,可认定为健康
            .setStatus("UP");
        return new ServiceHealth().setNode(node).setService(service).setChecks(Collections.singletonList(check));
    }

    private static MultiValueMap createHeaders(long index) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(CONSUL_IDX_HEADER, String.valueOf(index));
        return headers;
    }

    protected static  ResponseEntity createResponseEntity(T body, long index) {
        return new ResponseEntity<>(body, createHeaders(index), HttpStatus.OK);
    }

    /*
     * Details to the wait behaviour can be found https://www.consul.io/api/index.html#blocking-queries
     */
    private static long getWaitMillis(String wait) {
        // default from consul docu
        long millis = TimeUnit.MINUTES.toMillis(5);
        if (wait != null) {
            Matcher matcher = WAIT_PATTERN.matcher(wait);
            if (matcher.matches()) {
                long value = Long.parseLong(matcher.group(1));
                TimeUnit timeUnit = parseTimeUnit(matcher.group(2));
                millis = timeUnit.toMillis(value);
            } else {
                throw new IllegalArgumentException("Invalid wait pattern");
            }
        }
        return millis + ThreadLocalRandom.current().nextInt(((int)millis / 16) + 1);
    }

    private static TimeUnit parseTimeUnit(String unit) {
        switch (unit) {
            case "h":
                return TimeUnit.HOURS;
            case "m":
                return TimeUnit.MINUTES;
            case "s":
                return TimeUnit.SECONDS;
            case "ms":
                return TimeUnit.MILLISECONDS;
            default:
                throw new IllegalArgumentException("No valid time unit");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy