Skip to content

Commit

Permalink
Merge branch 'release/0.23.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
ljupcovangelski committed Jun 1, 2021
2 parents 087774f + 7c2c08e commit 01f1872
Show file tree
Hide file tree
Showing 277 changed files with 2,549 additions and 1,564 deletions.
1 change: 0 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ build:ci --test_env=ROOT_LOG_LEVEL=ERROR

build:ci --noshow_progress
build:ci --verbose_failures
build:ci --remote_cache=https://storage.googleapis.com/airy-ci-cache

test:ci --flaky_test_attempts=2
2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: PR Labeler
on:
pull_request:
pull_request_target:
types: [opened]

jobs:
Expand Down
20 changes: 18 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@ jobs:
- name: Enable CI settings
run: |
echo "$GCS_SA_KEY" > key.json
cat <<EOF >>.bazelrc
common --config=ci
EOF
- name: Add bazel cache secret
if: ${{ github.actor != 'dependabot[bot]' }}
run: |
echo "$GCS_SA_KEY" > key.json
cat <<EOF >>.bazelrc
build:ci --remote_cache=https://storage.googleapis.com/airy-ci-cache
build:ci --google_credentials=key.json
EOF
env:
Expand Down Expand Up @@ -63,12 +70,21 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GITHUB_BRANCH: ${{ github.ref }}
- name: Publish http-client library to npm
if: ${{ startsWith(github.ref, 'refs/tags/npm-v') }}
if: ${{ startsWith(github.ref, 'refs/heads/main') }}
run: |
sudo apt-get install -y expect
bazel run //lib/typescript/httpclient:publish-npm release
env:
DEPLOY_NPM_USERNAME: ${{ secrets.DEPLOY_NPM_USERNAME }}
DEPLOY_NPM_PASSWORD: ${{ secrets.DEPLOY_NPM_PASSWORD }}
DEPLOY_NPM_EMAIL: ${{ secrets.DEPLOY_NPM_EMAIL }}
- name: Publish chat-plugin library to npm
if: ${{ startsWith(github.ref, 'refs/heads/main') }}
run: |
sudo apt-get install -y expect
bazel run //frontend/chat-plugin:publish-npm release
env:
DEPLOY_NPM_USERNAME: ${{ secrets.DEPLOY_NPM_USERNAME }}
DEPLOY_NPM_PASSWORD: ${{ secrets.DEPLOY_NPM_PASSWORD }}
DEPLOY_NPM_EMAIL: ${{ secrets.DEPLOY_NPM_EMAIL }}
GITHUB_BRANCH: ${{ github.ref }}
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.22.0
0.23.0
4 changes: 2 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
# Airy Bazel tools
git_repository(
name = "com_github_airyhq_bazel_tools",
commit = "bbfbc0844c30b52e146690412030cfe9c6b475e3",
commit = "5943abd41d625ea3bca952bb0087cc01e95b932f",
remote = "https://github.com/airyhq/bazel-tools.git",
shallow_since = "1620236403 +0200",
shallow_since = "1622467587 +0200",
)

load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.airy.core.api.config;

import co.airy.core.api.config.payload.ClientConfigResponsePayload;
import co.airy.spring.auth.PrincipalAccess;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
Expand All @@ -17,7 +18,7 @@ public ClientConfigController(ServiceDiscovery serviceDiscovery) {
@PostMapping("/client.config")
public ResponseEntity<ClientConfigResponsePayload> getConfig(Authentication auth) {
return ResponseEntity.ok(ClientConfigResponsePayload.builder()
.components(serviceDiscovery.getComponents())
.services(serviceDiscovery.getServices())
.userProfile(PrincipalAccess.getUserProfile(auth))
.build());
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package co.airy.core.api.config;

import co.airy.log.AiryLoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.Future;

@Component
public class HealthApi {
private static final Logger log = AiryLoggerFactory.getLogger(HealthApi.class);
private final ObjectMapper objectMapper = new ObjectMapper();
private final String namespace;
private final RestTemplate restTemplate;

public HealthApi(@Value("${kubernetes.namespace}") String namespace, RestTemplate restTemplate) {
this.namespace = namespace;
this.restTemplate = restTemplate;
}

@Async
public Future<Boolean> isHealthy(String service) {
try {
final ResponseEntity<String> response = restTemplate.getForEntity(String.format("http://%s.%s/actuator/health", service, namespace), String.class);
log.info("response body {}", response.getBody());
final JsonNode jsonNode = objectMapper.readTree(response.getBody());
return new AsyncResult<>("UP".equalsIgnoreCase(jsonNode.get("status").textValue()));
} catch (Exception e) {
return new AsyncResult<>(false);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,41 +1,66 @@
package co.airy.core.api.config;

import co.airy.core.api.config.dto.ServiceInfo;
import co.airy.core.api.config.payload.ServicesResponsePayload;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toMap;

@Component
public class ServiceDiscovery {
private final String namespace;
private final RestTemplate restTemplate;
private final HealthApi healthApi;

private Map<String, Map<String, Object>> components = new ConcurrentHashMap<>();
private final Map<String, ServiceInfo> services = new ConcurrentHashMap<>();

public ServiceDiscovery(@Value("${kubernetes.namespace}") String namespace, RestTemplate restTemplate) {
public ServiceDiscovery(@Value("${kubernetes.namespace}") String namespace, RestTemplate restTemplate, HealthApi healthApi) {
this.namespace = namespace;
this.restTemplate = restTemplate;
this.healthApi = healthApi;
}

public Map<String, Map<String, Object>> getComponents() {
return components;
public Map<String, ServiceInfo> getServices() {
return services;
}

@Scheduled(fixedRate = 1_000)
private void updateComponentsStatus() {
final ResponseEntity<ComponentsResponsePayload> response = restTemplate.getForEntity("http://airy-controller.default/components", ComponentsResponsePayload.class);
Map<String, Map<String, Object>> newComponents = new ConcurrentHashMap<>();
for (String component: response.getBody().getComponents()) {
newComponents.put(component, Map.of("enabled", true));
}
components.clear();
components.putAll(newComponents);
public void updateComponentsStatus() {
final ResponseEntity<ServicesResponsePayload> response = restTemplate.getForEntity(String.format("http://airy-controller.%s/services", namespace),
ServicesResponsePayload.class);

final Map<String, ServiceInfo> newServices = response.getBody().getServices();
// Start all requests in parallel
final Map<String, Future<Boolean>> healthRequests = newServices
.keySet().stream()
.collect(toMap(serviceName -> serviceName, healthApi::isHealthy));

healthRequests.forEach((serviceName, value) -> {
Boolean healthResponse;
try {
healthResponse = value.get(30, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
healthResponse = false;
}

newServices.get(serviceName).setHealthy(healthResponse);
});

this.services.clear();
this.services.putAll(newServices);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package co.airy.core.api.config.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ServiceInfo {
private boolean enabled;
private boolean healthy;
private String component;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.airy.core.api.config;
package co.airy.core.api.config.payload;

import co.airy.core.api.config.dto.ServiceInfo;
import co.airy.spring.auth.session.UserProfile;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -13,6 +14,6 @@
@NoArgsConstructor
@AllArgsConstructor
public class ClientConfigResponsePayload {
private Map<String, Map<String, Object>> components;
private Map<String, ServiceInfo> services;
private UserProfile userProfile;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.airy.core.api.config.payload;

import co.airy.core.api.config.dto.ServiceInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Map;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ServicesResponsePayload implements Serializable {
private Map<String, ServiceInfo> services;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,32 @@ static void afterAll() throws Exception {
@BeforeEach
void beforeEach() throws Exception {
webTestHelper.waitUntilHealthy();

mockServer = MockRestServiceServer.createServer(restTemplate);
}

@Test
public void canReturnConfig() throws Exception {
mockServer.expect(once(), requestTo(new URI("http://airy-controller.default/components")))
mockServer.expect(once(), requestTo(new URI("http://airy-controller.default/services")))
.andExpect(method(HttpMethod.GET))
.andRespond(
withSuccess("{\"services\": {\"api-communication\":{\"enabled\":true,\"component\":\"api-communication\"}}}", MediaType.APPLICATION_JSON)
);

mockServer.expect(once(), requestTo(new URI("http://api-communication.default/actuator/health")))
.andExpect(method(HttpMethod.GET))
.andRespond(
withSuccess("{\"components\": [\"api-communication\"]}", MediaType.APPLICATION_JSON)
withSuccess("{\"status\": \"DOWN\"}", MediaType.APPLICATION_JSON)
);

retryOnException(() -> webTestHelper.post("/client.config", "{}")
.andExpect(status().isOk())
.andExpect(jsonPath("$.components.*", hasSize(1)))
.andExpect(jsonPath("$.components", hasKey("api-communication")))
.andExpect(jsonPath("$.components.*.enabled", everyItem(is(true)))),
.andExpect(jsonPath("$.services.*", hasSize(1)))
.andExpect(jsonPath("$.services", hasKey("api-communication")))
.andExpect(jsonPath("$.services.*.enabled", everyItem(is(true))))
.andExpect(jsonPath("$.services.*.healthy", everyItem(is(false)))),
"client.config call failed");

mockServer.verify();
}

}
Loading

0 comments on commit 01f1872

Please sign in to comment.