diff --git a/bm-agent/build.gradle b/bm-agent/build.gradle index 0125c05f..6189bbe5 100644 --- a/bm-agent/build.gradle +++ b/bm-agent/build.gradle @@ -7,13 +7,15 @@ plugins { } group = 'org.benchmarker' -version = '0.0.1-SNAPSHOT' +version = '1.0.0' def excludeJacocoTestCoverageReport = [ // bmagent 'org/benchmarker/bmagent/BmAgentApplication**', 'org/benchmarker/bmagent/sse/**', 'org/benchmarker/bmagent/consts/SystemSchedulerConst', + 'org/benchmarker/bmagent/status/**', + 'org/benchmarker/bmagent/initializer/**', // 'org/benchmarker/bmagent/**', ] @@ -21,6 +23,16 @@ java { sourceCompatibility = '17' } +ext { + set('springCloudVersion', "2023.0.0") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + jacoco { toolVersion = "0.8.8" } @@ -41,16 +53,23 @@ dependencies { implementation 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + implementation 'org.springframework.boot:spring-boot-starter-actuator' // add webflux implementation 'org.springframework.boot:spring-boot-starter-webflux' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-testcontainers' testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.1' + + implementation "io.jsonwebtoken:jjwt-api:0.11.1" + runtimeOnly "io.jsonwebtoken:jjwt-impl:0.11.1" + runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.11.1" if (isAppleSilicon()) { runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64") } + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + implementation project(':bm-common') } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/BmAgentApplication.java b/bm-agent/src/main/java/org/benchmarker/bmagent/BmAgentApplication.java index 6ff05c2b..4fb07973 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/BmAgentApplication.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/BmAgentApplication.java @@ -1,13 +1,24 @@ package org.benchmarker.bmagent; +import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class BmAgentApplication { + @Bean + public ExitCodeGenerator exitCodeGenerator() { + return new ExitCodeGenerator() { + @Override + public int getExitCode() { + return 42; + } + }; + } public static void main(String[] args) { + System.setProperty("spring.application.name","bm-agent"); SpringApplication.run(BmAgentApplication.class, args); } - } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/consts/HeaderConst.java b/bm-agent/src/main/java/org/benchmarker/bmagent/consts/HeaderConst.java new file mode 100644 index 00000000..3a4bc40d --- /dev/null +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/consts/HeaderConst.java @@ -0,0 +1,7 @@ +package org.benchmarker.bmagent.consts; + +public interface HeaderConst { + String bearerPrefix = "Bearer "; + String tokenKey = "Authorization"; + +} diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/consts/SystemSchedulerConst.java b/bm-agent/src/main/java/org/benchmarker/bmagent/consts/SystemSchedulerConst.java index 572fd52b..7d502532 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/consts/SystemSchedulerConst.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/consts/SystemSchedulerConst.java @@ -7,5 +7,8 @@ public interface SystemSchedulerConst { */ Long systemSchedulerId = -100L; String systemUsageSchedulerName = "cpu-memory-usage-update"; + Integer connectControllerTimeout = 10; // seconds + Integer connectionFailedLimit = 50; + } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/controller/AgentApiController.java b/bm-agent/src/main/java/org/benchmarker/bmagent/controller/AgentApiController.java index eaa25a59..d023f350 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/controller/AgentApiController.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/controller/AgentApiController.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.Map; +import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.benchmarker.bmagent.AgentInfo; @@ -42,15 +43,16 @@ public class AgentApiController { * @param action String * @return SseEmitter */ - @PostMapping("/templates/{template_id}") + @PostMapping("/groups/{group_id}/templates/{template_id}") public SseEmitter manageSSE(@PathVariable("template_id") Long templateId, + @PathVariable("group_id") String groupId, @RequestParam("action") String action, @RequestBody TemplateInfo templateInfo) { log.info(templateInfo.toString()); - agentStatusManager.getAndUpdateStatusIfReady( - AgentStatus.TESTING).orElseThrow(() -> new RuntimeException("agent is not ready")); if (action.equals("start")) { - return sseManageService.start(templateId, templateInfo); + agentStatusManager.getAndUpdateStatusIfReady( + AgentStatus.TESTING).orElseThrow(() -> new RuntimeException("agent is not ready")); + return sseManageService.start(templateId, groupId, templateInfo); } else { sseManageService.stop(templateId); return null; @@ -73,10 +75,12 @@ public AgentInfo getStatus() { String scheme = request.getScheme(); // http or https String serverName = request.getServerName(); int serverPort = request.getServerPort(); + Set longs = scheduledTaskService.getStatus().keySet(); String agentServerUrl = scheme + "://" + serverName + ":" + serverPort; return AgentInfo.builder() + .templateId(longs) .cpuUsage(agentStatusManager.getCpuUsage()) .memoryUsage(agentStatusManager.getMemoryUsage()) .startedAt(agentStatusManager.getStartedAt()) diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/initializer/Initializer.java b/bm-agent/src/main/java/org/benchmarker/bmagent/initializer/Initializer.java index 37407889..817bfc96 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/initializer/Initializer.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/initializer/Initializer.java @@ -2,24 +2,39 @@ import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.benchmarker.bmagent.consts.SystemSchedulerConst; import org.benchmarker.bmagent.schedule.ScheduledTaskService; import org.benchmarker.bmagent.status.AgentStatusManager; +import org.springframework.beans.BeansException; import org.springframework.boot.CommandLineRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @Component +@Profile("!test") +@Slf4j @RequiredArgsConstructor -public class Initializer implements CommandLineRunner { +public class Initializer implements CommandLineRunner, ApplicationContextAware { private final ScheduledTaskService scheduledTaskService; private final AgentStatusManager agentStatusManager; + private ApplicationContext applicationContext; + @Override public void run(String... args) throws Exception { + log.info("init"); // cpu, memory usage checker scheduledTaskService.startChild(SystemSchedulerConst.systemSchedulerId, SystemSchedulerConst.systemUsageSchedulerName, agentStatusManager::updateStats, 0, 1, TimeUnit.SECONDS); } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/pref/HttpSender.java b/bm-agent/src/main/java/org/benchmarker/bmagent/pref/HttpSender.java index a576174b..605f2c70 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/pref/HttpSender.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/pref/HttpSender.java @@ -17,10 +17,13 @@ import java.util.stream.IntStream; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.benchmarker.bmagent.AgentStatus; import org.benchmarker.bmagent.service.IScheduledTaskService; +import org.benchmarker.bmagent.status.AgentStatusManager; import org.benchmarker.bmagent.util.WebClientSupport; import org.benchmarker.bmcommon.dto.TemplateInfo; import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import reactor.core.publisher.Mono; /** @@ -35,14 +38,17 @@ public class HttpSender { private final ResultManagerService resultManagerService; private final IScheduledTaskService scheduledTaskService; + private final AgentStatusManager agentStatusManager; + public HttpSender(ResultManagerService resultManagerService, - IScheduledTaskService scheduledTaskService) { + IScheduledTaskService scheduledTaskService, AgentStatusManager agentStatusManager) { this.resultManagerService = resultManagerService; this.scheduledTaskService = scheduledTaskService; + this.agentStatusManager = agentStatusManager; } - private Integer defaultMaxRequestsPerUser = 100000000; + private Integer defaultMaxRequestsPerUser = Integer.MAX_VALUE; private Integer defaultMaxDuration = 5; // 5 hours private AtomicInteger totalRequests = new AtomicInteger(0); private AtomicInteger totalSuccess = new AtomicInteger(0); @@ -66,7 +72,8 @@ public HttpSender(ResultManagerService resultManagerService, * * @param templateInfo {@link TemplateInfo} */ - public void sendRequests(TemplateInfo templateInfo) throws MalformedURLException { + public void sendRequests(SseEmitter sseEmitter, TemplateInfo templateInfo) throws MalformedURLException { + URL url = new URL(templateInfo.getUrl()); RequestHeadersSpec req = WebClientSupport.create(templateInfo.getMethod(), templateInfo.getUrl(), @@ -85,16 +92,20 @@ public void sendRequests(TemplateInfo templateInfo) throws MalformedURLException } Duration duration = templateInfo.getMaxDuration(); + log.info("Now send multiple HTTP request to target server"); + log.info(templateInfo.toString()); // Future setup futures = IntStream.range(0, templateInfo.getVuser()) .mapToObj(i -> CompletableFuture.runAsync(() -> { long startTime = System.currentTimeMillis(); // 시작 시간 기록 long endTime = startTime + duration.toMillis(); + for (int j = 0; j < templateInfo.getMaxRequest(); j++) { // 만약 running 이 아니거나 시간이 끝났다면, if (!isRunning || System.currentTimeMillis() > endTime) { - return; + agentStatusManager.updateAgentStatus(AgentStatus.READY); + break; } long requestStartTime = System.currentTimeMillis(); // 요청 시작 시간 기록 req.exchangeToMono(resp -> { @@ -192,6 +203,7 @@ public Map calculateMttfbPercentile(List percentile) { * downstream 에서만 제거 가능. 외부에서는 cancel 불가능... */ public void cancelRequests() { + log.info("cancel requests"); isRunning = false; } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/security/JwtTokenProvider.java b/bm-agent/src/main/java/org/benchmarker/bmagent/security/JwtTokenProvider.java new file mode 100644 index 00000000..76254682 --- /dev/null +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/security/JwtTokenProvider.java @@ -0,0 +1,79 @@ +package org.benchmarker.bmagent.security; + + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * {@link JwtTokenProvider} class is used to create Json Web Token. + */ +@Slf4j +@Component +@Setter +public class JwtTokenProvider { + + /** + * Access token's expiration time + */ + public static String expirationTime; + + /** + * Refresh token's expiration time + */ + public static String refreshExpirationTime; + + /** + * Secret key for signing the token + */ + public static String secret; + + @Value("${token.expiration_time}") + private void setExpirationTime(String expirationTime) { + JwtTokenProvider.expirationTime = expirationTime; + } + + @Value("${token.refresh_expiration_time}") + private void setRefreshExpirationTime(String refreshExpirationTime) { + JwtTokenProvider.refreshExpirationTime = refreshExpirationTime; + } + + @Value("${token.secret}") + private void setSecret(String secret) { + JwtTokenProvider.secret = secret; + } + + + /** + * Create Json Web Token with user's username and authorities + * + *

Here, this method generate accessToken

+ *

JWT's payload will looks like below

+ *
+     *  {
+     *      "sub": "agent"
+     *  }
+     * 
+ * + * @return Json-web-token + */ + public static String createAccessToken() { + Claims claims = Jwts.claims().setSubject("agent"); + + Long expirationTimeLong = Long.parseLong(expirationTime); + final Date createdDate = new Date(); + final Date expirationDate = new Date(createdDate.getTime() + expirationTimeLong); + return Jwts.builder() + .setClaims(claims) + .setSubject("agent") + .setIssuedAt(createdDate) + .setExpiration(expirationDate) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } +} diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/service/ISseManageService.java b/bm-agent/src/main/java/org/benchmarker/bmagent/service/ISseManageService.java index 9f19f158..2afa9802 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/service/ISseManageService.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/service/ISseManageService.java @@ -19,7 +19,7 @@ public interface ISseManageService extends SseManageConsts { * @param templateInfo TemplateInfo * @return SseEmitter */ - SseEmitter start(Long id, TemplateInfo templateInfo); + SseEmitter start(Long id, String groupId, TemplateInfo templateInfo); /** * Stop the SSE emitter for the given id diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/sse/SseManageService.java b/bm-agent/src/main/java/org/benchmarker/bmagent/sse/SseManageService.java index 5679a89b..80dd7008 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/sse/SseManageService.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/sse/SseManageService.java @@ -47,9 +47,9 @@ public class SseManageService extends AbstractSseManageService { * @see ScheduledTaskService */ @Override - public SseEmitter start(Long id, TemplateInfo templateInfo) { + public SseEmitter start(Long id, String groupId, TemplateInfo templateInfo) { SseEmitter emitter = new SseEmitter(SSE_TIMEOUT); - + LocalDateTime startAt = LocalDateTime.now(); // when the client disconnects, complete the SseEmitter alwaysDoStop(id, emitter); @@ -61,7 +61,8 @@ public SseEmitter start(Long id, TemplateInfo templateInfo) { // Save the SseEmitter to the map sseEmitterHashMap.put(id, emitter); - httpSender.put(id, new HttpSender(resultManagerService, scheduledTaskService)); + + httpSender.put(id, new HttpSender(resultManagerService, scheduledTaskService, agentStatusManager)); HttpSender htps = httpSender.get(id); LocalDateTime now = LocalDateTime.now(); @@ -69,11 +70,10 @@ public SseEmitter start(Long id, TemplateInfo templateInfo) { // 1초마다 TestResult 를 보내는 스케줄러 시작 scheduledTaskService.start(id, () -> { - LocalDateTime cur = LocalDateTime.now(); + LocalDateTime curTime = LocalDateTime.now(); Map tpsP = htps.calculateTpsPercentile(percentiles); Map mttfbP = htps.calculateMttfbPercentile(percentiles); - CommonTestResult data = getCommonTestResult(templateInfo, htps, now, cur, tpsP, mttfbP); - log.info(data.toString()); + CommonTestResult data = getCommonTestResult(groupId,templateInfo, htps, now, curTime, tpsP, mttfbP); resultManagerService.save(id, data); send(id, resultManagerService.find(id)); }, 0, 1, TimeUnit.SECONDS); @@ -82,7 +82,15 @@ public SseEmitter start(Long id, TemplateInfo templateInfo) { // async + non-blocking 필수 CompletableFuture.runAsync(() -> { try { - htps.sendRequests(templateInfo); + htps.sendRequests(emitter, templateInfo); + LocalDateTime finished = LocalDateTime.now(); + Map tpsP = htps.calculateTpsPercentile(percentiles); + Map mttfbP = htps.calculateMttfbPercentile(percentiles); + CommonTestResult data = getCommonTestResult(groupId,templateInfo, htps, now, finished, tpsP, mttfbP); + data.setFinishedAt(finished.toString()); + data.setTestStatus(AgentStatus.TESTING_FINISH); + send(id, data); + emitter.complete(); } catch (MalformedURLException e) { log.error(e.getMessage()); } @@ -97,17 +105,18 @@ public SseEmitter start(Long id, TemplateInfo templateInfo) { * * @param templateInfo * @param htps - * @param now + * @param start * @param cur * @param tpsP * @param mttfbP * @return CommonTestResult */ - private CommonTestResult getCommonTestResult(TemplateInfo templateInfo, HttpSender htps, - LocalDateTime now, LocalDateTime cur, Map tpsP, + private CommonTestResult getCommonTestResult(String groupId,TemplateInfo templateInfo, HttpSender htps, + LocalDateTime start, LocalDateTime cur, Map tpsP, Map mttfbP) { return CommonTestResult.builder() - .startedAt(now.toString()) + .groupId(groupId) + .startedAt(start.toString()) .totalRequests(htps.getTotalRequests().get()) .totalSuccess(htps.getTotalSuccess().get()) .totalErrors(htps.getTotalErrors().get()) @@ -116,14 +125,14 @@ private CommonTestResult getCommonTestResult(TemplateInfo templateInfo, HttpSend .url(templateInfo.getUrl()) .method(templateInfo.getMethod()) .totalUsers(templateInfo.getVuser()) - .totalDuration(Duration.between(now, cur).toString()) + .totalDuration(Duration.between(start, cur).toString()) .MTTFBPercentiles(mttfbP) .TPSPercentiles(tpsP) .testStatus(agentStatusManager.getStatus().get()) + .finishedAt(cur.toString()) // TODO temp .mttfbAverage("0") .tpsAverage(0) - .finishedAt("-") .build(); } @@ -169,14 +178,17 @@ public void send(Long id, Object data) { private void alwaysDoStop(Long id, SseEmitter emitter) { emitter.onCompletion(() -> { log.info("SSE Completed"); + agentStatusManager.updateAgentStatus(AgentStatus.READY); this.stop(id); }); emitter.onTimeout(() -> { log.warn("SSE Timeout"); + agentStatusManager.updateAgentStatus(AgentStatus.READY); this.stop(id); }); emitter.onError((ex) -> { log.info("SSE connection error for template ID: {}", id); + agentStatusManager.updateAgentStatus(AgentStatus.READY); this.stop(id); // Call method to clean up resources }); } diff --git a/bm-agent/src/main/java/org/benchmarker/bmagent/status/AgentStatusManager.java b/bm-agent/src/main/java/org/benchmarker/bmagent/status/AgentStatusManager.java index 4910cf6b..8dfba213 100644 --- a/bm-agent/src/main/java/org/benchmarker/bmagent/status/AgentStatusManager.java +++ b/bm-agent/src/main/java/org/benchmarker/bmagent/status/AgentStatusManager.java @@ -4,6 +4,8 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; +import java.net.UnknownHostException; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -11,8 +13,23 @@ import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; + +import org.benchmarker.bmagent.AgentInfo; import org.benchmarker.bmagent.AgentStatus; +import org.benchmarker.bmagent.consts.HeaderConst; +import org.benchmarker.bmagent.consts.SystemSchedulerConst; +import org.benchmarker.bmagent.security.JwtTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + /** * Agent instance status and information manager @@ -27,12 +44,68 @@ public class AgentStatusManager { private AtomicReference status = new AtomicReference<>(AgentStatus.READY); private double cpuUsage; private double memoryUsage; - // server start time + private Boolean isConnected = false; + @Value("${controller.url}") + private String controllerUrl; private final ZonedDateTime startedAt = ZonedDateTime.now(); + @Autowired + private ServerProperties serverProperties; // Mutex lock object private Object lock = new Object(); + + private AgentInfo getInfo() throws UnknownHostException { + log.info(getServerProperties().toString()); + + String agentServerUrl = "http://" + serverProperties.getAddress() + ":" + serverProperties.getPort(); + AgentInfo info = AgentInfo.builder() + .cpuUsage(cpuUsage) + .memoryUsage(memoryUsage) + .startedAt(startedAt) + .serverUrl(agentServerUrl) + .status(status.get()) + .build(); + log.info(info.toString()); + + return info; + } + + // deprecate + public boolean connect() { + try { + log.info("connection request to " + controllerUrl); + HttpClient httpClient = HttpClient.create() + .responseTimeout(Duration.ofSeconds(SystemSchedulerConst.connectControllerTimeout)); + + ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); + WebClient webClient = WebClient.builder() + .clientConnector(connector) + .baseUrl(controllerUrl) // 기본 URL 설정 + .build(); + + String accessToken = JwtTokenProvider.createAccessToken(); + + ClientResponse response = webClient.post() + .header(HeaderConst.tokenKey, HeaderConst.bearerPrefix + accessToken) + .bodyValue(getInfo()) + .exchange() + .block(); + + if (response != null && response.statusCode() == HttpStatus.OK) { + // Response status is OK + isConnected = true; + return true; + } else { + return false; + } + }catch (Exception e){ + log.error(e.getMessage()); + return false; + } + } + + /** * Check the current status is READY state * @@ -57,12 +130,12 @@ public void updateAgentStatus(AgentStatus status) { * @param status * @return Optional empty if not READY, changed status if READY */ - public Optional getAndUpdateStatusIfReady(AgentStatus status){ - synchronized (lock){ - if (this.status.get()==AgentStatus.READY){ + public Optional getAndUpdateStatusIfReady(AgentStatus status) { + synchronized (lock) { + if (this.status.get() == AgentStatus.READY) { this.status.set(status); return Optional.of(status); - }else{ + } else { return Optional.empty(); } } diff --git a/bm-agent/src/main/resources/application.yaml b/bm-agent/src/main/resources/application.yaml index 54b155ff..3797fd43 100644 --- a/bm-agent/src/main/resources/application.yaml +++ b/bm-agent/src/main/resources/application.yaml @@ -1,2 +1,44 @@ server: - port: 8081 \ No newline at end of file + port: 8081 +controller: + url: http://localhost:8080/api/agent/register + +spring: + application: + name: bma + profiles: + active: prod + +token: + secret: '21edc783bca7b12425ccd76e9bb135d868b478286e1ea49965c9ae7138345fb3a5dc90cebae25c76d246529664bd223c81842fa37387b0d444986d30828288bb' + expiration_time: 86400000 # 1 day + refresh_expiration_time: 604800000 # 7 days + +eureka: + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + +management: + endpoints: + web: + exposure: + include: "*" # 모든 엔드포인트를 노출합니다. 필요에 따라 특정 엔드포인트만 선택할 수도 있습니다. + endpoint: + health: + show-details: always # Health 엔드포인트에서 상세 정보를 항상 표시합니다. + health: + groups: + readiness: + include: "*" # 모든 상태를 readiness 그룹에 포함합니다. + liveness: + include: "*" # 모든 상태를 liveness 그룹에 포함합니다. + info: + app: + name: "BM-Agent" # 애플리케이션 이름을 설정합니다. + version: "1.0.0" # 애플리케이션 버전을 설정합니다. + contact: + name: "gyumin hwangbo" # 연락처 이름을 설정합니다. + email: "ghkdqhrbals@gmail.com" # 이메일 주소를 설정합니다. \ No newline at end of file diff --git a/bm-agent/src/test/java/org/benchmarker/bmagent/controller/AgentApiControllerTest.java b/bm-agent/src/test/java/org/benchmarker/bmagent/controller/AgentApiControllerTest.java index e41a6466..dae9be5b 100644 --- a/bm-agent/src/test/java/org/benchmarker/bmagent/controller/AgentApiControllerTest.java +++ b/bm-agent/src/test/java/org/benchmarker/bmagent/controller/AgentApiControllerTest.java @@ -77,11 +77,11 @@ public void testStartSSE() throws IOException { mockSseEmitter.send("Data 2"); mockSseEmitter.complete(); return null; - }).when(sseManageService).start(eq(1L), any()); + }).when(sseManageService).start(eq(1L),any(), any()); // 호출 TemplateInfo build = TemplateInfo.builder().build(); - agentApiController.manageSSE(1L, "start", build); + agentApiController.manageSSE(1L, "groupId","start", build); // then // SseEmitter 로 전송된 메시지 모두 캡처 @@ -100,7 +100,7 @@ public void testStopSSE() throws IOException { // when TemplateInfo build = TemplateInfo.builder().build(); - agentApiController.manageSSE(templateId, "stop", build); + agentApiController.manageSSE(templateId, "groupId","stop", build); // then // sseManageService.stop() 메서드가 호출되었는지 검증 diff --git a/bm-agent/src/test/java/org/benchmarker/bmagent/initializer/InitializerTest.java b/bm-agent/src/test/java/org/benchmarker/bmagent/initializer/InitializerTest.java index dbac76b9..d9e06df3 100644 --- a/bm-agent/src/test/java/org/benchmarker/bmagent/initializer/InitializerTest.java +++ b/bm-agent/src/test/java/org/benchmarker/bmagent/initializer/InitializerTest.java @@ -1,10 +1,7 @@ package org.benchmarker.bmagent.initializer; -import static java.lang.Thread.sleep; -import static org.assertj.core.api.Assertions.assertThat; -import java.util.Map; -import java.util.concurrent.ScheduledExecutorService; +import static org.assertj.core.api.Assertions.assertThat; import org.benchmarker.bmagent.consts.SystemSchedulerConst; import org.benchmarker.bmagent.schedule.ScheduledTaskService; import org.benchmarker.bmagent.status.AgentStatusManager; @@ -23,17 +20,12 @@ class InitializerTest { @Test @DisplayName("System scheduler running check") void test1() throws Exception { - Map schedulers = scheduledTaskService.getSchedulers( - SystemSchedulerConst.systemSchedulerId); - assertThat(schedulers).isNotEmpty(); - assertThat(schedulers).isNotNull(); - assertThat(schedulers.keySet().size()).isEqualTo(1); - assertThat(schedulers.keySet().toArray()[0]).isEqualTo(SystemSchedulerConst.systemUsageSchedulerName); - sleep(2000); // wait 2 seconds and scheduler will calculate cpu&memory usage in a seconds - assertThat(agentStatusManager.getCpuUsage()).isNotNull(); - assertThat(agentStatusManager.getMemoryUsage()).isNotNull(); - System.out.println("cpu usage test : " + agentStatusManager.getCpuUsage()); - System.out.println("memory usage test : " + agentStatusManager.getMemoryUsage()); + Initializer initializer = new Initializer(scheduledTaskService,agentStatusManager); + initializer.run(); + assertThat(SystemSchedulerConst.connectControllerTimeout).isEqualTo(10); + assertThat(SystemSchedulerConst.systemSchedulerId).isEqualTo(-100L); + assertThat(SystemSchedulerConst.systemUsageSchedulerName).isEqualTo("cpu-memory-usage-update"); + assertThat(SystemSchedulerConst.connectionFailedLimit).isEqualTo(50); } } \ No newline at end of file diff --git a/bm-agent/src/test/java/org/benchmarker/bmagent/pref/HttpSenderTest.java b/bm-agent/src/test/java/org/benchmarker/bmagent/pref/HttpSenderTest.java index 862f8ad1..bd8ec538 100644 --- a/bm-agent/src/test/java/org/benchmarker/bmagent/pref/HttpSenderTest.java +++ b/bm-agent/src/test/java/org/benchmarker/bmagent/pref/HttpSenderTest.java @@ -7,11 +7,14 @@ import java.time.Duration; import java.util.Map; import org.benchmarker.bmagent.schedule.ScheduledTaskService; +import org.benchmarker.bmagent.status.AgentStatusManager; + import org.benchmarker.bmcommon.dto.TemplateInfo; import org.benchmarker.util.MockServer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * Schel @@ -20,9 +23,10 @@ class HttpSenderTest extends MockServer { private ScheduledTaskService scheduledTaskService = new ScheduledTaskService(); private ResultManagerService resultManagerService = new ResultManagerService(); + private AgentStatusManager agentStatusManager = new AgentStatusManager(); @BeforeEach - void setup(){ + void setup() { ScheduledTaskService scheduledTaskService = new ScheduledTaskService(); ResultManagerService resultManagerService = new ResultManagerService(); } @@ -31,7 +35,8 @@ void setup(){ @DisplayName("url 포멧 에러 시 에러 반환") void test2() throws MalformedURLException { // given - HttpSender httpSender = new HttpSender(resultManagerService, scheduledTaskService); + HttpSender httpSender = new HttpSender(resultManagerService, scheduledTaskService, + agentStatusManager); TemplateInfo get = TemplateInfo.builder() .id("1") @@ -46,18 +51,24 @@ void test2() throws MalformedURLException { .build(); // then - assertThrows((MalformedURLException.class),()->{ + + assertThrows((MalformedURLException.class), () -> { // when - httpSender.sendRequests(get); + httpSender.sendRequests(new SseEmitter(),get); + httpSender.cancelRequests(); }); + + } @Test @DisplayName("performance testing") void test() throws MalformedURLException { // given - HttpSender httpSender = new HttpSender(resultManagerService, scheduledTaskService); - addMockResponse("ok",50); // mock 50 response + + HttpSender httpSender = new HttpSender(resultManagerService, scheduledTaskService, + agentStatusManager); + addMockResponse("ok", 50); // mock 50 response TemplateInfo get = TemplateInfo.builder() .id("1") @@ -72,8 +83,8 @@ void test() throws MalformedURLException { .build(); // when - httpSender.sendRequests(get); - scheduledTaskService.shutdown(0L); + httpSender.sendRequests(new SseEmitter(),get); + scheduledTaskService.shutdown(1L); // then assertThat(httpSender.getTpsMap()).isNotNull(); diff --git a/bm-agent/src/test/java/org/benchmarker/bmagent/security/JwtTokenProviderTest.java b/bm-agent/src/test/java/org/benchmarker/bmagent/security/JwtTokenProviderTest.java new file mode 100644 index 00000000..0078c8a2 --- /dev/null +++ b/bm-agent/src/test/java/org/benchmarker/bmagent/security/JwtTokenProviderTest.java @@ -0,0 +1,23 @@ +package org.benchmarker.bmagent.security; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class JwtTokenProviderTest { + + private JwtTokenProvider jwtTokenProvider; + + @BeforeEach + void setup(){ + jwtTokenProvider = new JwtTokenProvider(); + } + + @Test + @DisplayName("token creation") + void test1(){ + String accessToken = JwtTokenProvider.createAccessToken(); + } +} \ No newline at end of file diff --git a/bm-agent/src/test/java/org/benchmarker/bmagent/sse/SseManageServiceTest.java b/bm-agent/src/test/java/org/benchmarker/bmagent/sse/SseManageServiceTest.java index 743587a6..18f51e00 100644 --- a/bm-agent/src/test/java/org/benchmarker/bmagent/sse/SseManageServiceTest.java +++ b/bm-agent/src/test/java/org/benchmarker/bmagent/sse/SseManageServiceTest.java @@ -47,7 +47,7 @@ void start_ShouldStartSseEmitterAndScheduledTask() throws InterruptedException { resultManagerService.save(id, resultStub); // when - SseEmitter result = sseManageService.start(id, new TemplateInfo()); + SseEmitter result = sseManageService.start(id, "groupId", new TemplateInfo()); // then assertThat(result).isNotNull(); @@ -64,10 +64,10 @@ void startAndShutdown() throws InterruptedException { Long id = 1L; CommonTestResult resultStub = RandomUtils.generateRandomTestResult(); resultManagerService.save(id, resultStub); - SseEmitter result = sseManageService.start(id, new TemplateInfo()); + SseEmitter result = sseManageService.start(id, "groupId", new TemplateInfo()); // when - SseEmitter res = sseManageService.start(id, new TemplateInfo()); + SseEmitter res = sseManageService.start(id, "groupId", new TemplateInfo()); // then assertThat(res).isNull(); @@ -81,7 +81,7 @@ void stop_ShouldDoNothingIfEmitterAlreadyStopped() throws InterruptedException { Long id = 1L; CommonTestResult resultStub = RandomUtils.generateRandomTestResult(); resultManagerService.save(id, resultStub); - sseManageService.start(id, new TemplateInfo()); + sseManageService.start(id, "groupId", new TemplateInfo()); sseManageService.stop(id); // when diff --git a/bm-agent/src/test/resources/application.yaml b/bm-agent/src/test/resources/application.yaml new file mode 100644 index 00000000..bd2f6b33 --- /dev/null +++ b/bm-agent/src/test/resources/application.yaml @@ -0,0 +1,18 @@ + + +server: + port: 8081 +controller: + url: http://localhost:8080/api/agent/register + +token: + secret: '21edc783bca7b12425ccd76e9bb135d868b478286e1ea49965c9ae7138345fb3a5dc90cebae25c76d246529664bd223c81842fa37387b0d444986d30828288bb' + expiration_time: 86400000 # 1 day + refresh_expiration_time: 604800000 # 7 days + +spring: + profiles: + active: test + cloud: + discovery: + enabled: false \ No newline at end of file diff --git a/bm-common/build.gradle b/bm-common/build.gradle index c0ca4d33..f0f7224e 100644 --- a/bm-common/build.gradle +++ b/bm-common/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'org.benchmarker' -version = '0.0.1-SNAPSHOT' +version = '0.0.1' java { sourceCompatibility = '17' diff --git a/bm-common/src/main/java/org/benchmarker/bmagent/AgentInfo.java b/bm-common/src/main/java/org/benchmarker/bmagent/AgentInfo.java index a663f6ba..245d0114 100644 --- a/bm-common/src/main/java/org/benchmarker/bmagent/AgentInfo.java +++ b/bm-common/src/main/java/org/benchmarker/bmagent/AgentInfo.java @@ -1,6 +1,7 @@ package org.benchmarker.bmagent; import java.time.ZonedDateTime; +import java.util.Set; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -16,6 +17,7 @@ @AllArgsConstructor public class AgentInfo { private AgentStatus status; + private Set templateId; private double cpuUsage; private double memoryUsage; private String serverUrl; diff --git a/bm-common/src/main/java/org/benchmarker/bmcommon/dto/CommonTestResult.java b/bm-common/src/main/java/org/benchmarker/bmcommon/dto/CommonTestResult.java index 9ace18d6..2760a0ec 100644 --- a/bm-common/src/main/java/org/benchmarker/bmcommon/dto/CommonTestResult.java +++ b/bm-common/src/main/java/org/benchmarker/bmcommon/dto/CommonTestResult.java @@ -22,6 +22,8 @@ public class CommonTestResult { @JsonProperty("test_id") private int testId; + @JsonProperty("group_id") + private String groupId; @JsonProperty("started_at") private String startedAt; @JsonProperty("finished_at") diff --git a/bm-controller/build.gradle b/bm-controller/build.gradle index 832bd225..5bd0125c 100644 --- a/bm-controller/build.gradle +++ b/bm-controller/build.gradle @@ -13,6 +13,13 @@ version = rootProject.getVersion() ext { resilience4jVersion = '2.2.0' // 사용할 버전을 여기에 기입하세요. + set('springCloudVersion', "2023.0.1") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } } testlogger { @@ -24,6 +31,8 @@ testlogger { def excludeJacocoTestCoverageReport = [ 'org/benchmarker/bmcontroller/home/**', 'org/benchmarker/bmcontroller/template/**', + 'org/benchmarker/bmcontroller/prerun/**', + 'org/benchmarker/bmcontroller/preftest/**', 'org/benchmarker/bmcontroller/common/beans/**', 'org/benchmarker/bmcontroller/user/controller/UserController.class', 'org/benchmarker/BmControllerApplication.class', @@ -31,6 +40,11 @@ def excludeJacocoTestCoverageReport = [ 'org/benchmarker/bmcontroller/common/util/NoOp.class', 'org/benchmarker/bmcontroller/controller/AdminUserController**', 'org/benchmarker/bmcontroller/user/controller/GroupController.class', + 'org/benchmarker/bmcontroller/scheduler/**', + 'org/benchmarker/bmcontroller/scheduler/ScheduledTaskService**', + 'org/benchmarker/bmcontroller/agent/**', + 'org/benchmarker/bmagent/status/**', + ] @@ -56,7 +70,6 @@ bootJar { } } - repositories { mavenCentral() } @@ -69,7 +82,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation "io.jsonwebtoken:jjwt-api:0.11.1" + testImplementation 'org.testng:testng:7.1.0' testImplementation 'io.projectreactor:reactor-test' // Verifier @@ -81,7 +96,7 @@ dependencies { jacocoAggregation project(":bm-agent") // implementation "io.github.resilience4j:resilience4j-all:${resilience4jVersion}" implementation 'com.squareup.okhttp3:mockwebserver:4.9.1' - + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' implementation 'org.springframework.boot:spring-boot-starter-validation' @@ -98,6 +113,14 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + if (isAppleSilicon()) { + runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64") + } + +} + +boolean isAppleSilicon() { + return System.getProperty("os.name") == "Mac OS X" && System.getProperty("os.arch") == "aarch64" } ext { @@ -132,7 +155,9 @@ test { excludes = ["org/benchmarker/bmcontroller/home/HomeController.class", "org/benchmarker/bmcontroller/user/controller/UserController.class", 'org/benchmarker/bmcontroller/controller/AdminUserController**', - "org/benchmarker/bmcontroller/BmControllerApplication.class"] + "org/benchmarker/bmcontroller/BmControllerApplication.class", + 'org/benchmarker/bmcontroller/scheduler/ScheduledTaskService**', + 'org/benchmarker/bmcontroller/agent/**'] } finalizedBy jacocoTestReport } diff --git a/bm-controller/src/main/java/org/benchmarker/BmControllerApplication.java b/bm-controller/src/main/java/org/benchmarker/BmControllerApplication.java index c095014d..a289d131 100644 --- a/bm-controller/src/main/java/org/benchmarker/BmControllerApplication.java +++ b/bm-controller/src/main/java/org/benchmarker/BmControllerApplication.java @@ -7,6 +7,7 @@ public class BmControllerApplication { public static void main(String[] args) { + System.setProperty("spring.application.name","bm-controller"); SpringApplication.run(BmControllerApplication.class, args); } diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentListener.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentListener.java index bafe594c..54348340 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentListener.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentListener.java @@ -1,12 +1,17 @@ package org.benchmarker.bmcontroller.agent; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import lombok.RequiredArgsConstructor; +import org.benchmarker.bmagent.AgentInfo; import org.benchmarker.bmcontroller.common.error.ErrorCode; import org.benchmarker.bmcontroller.common.error.GlobalException; import org.benchmarker.bmcontroller.security.JwtTokenProvider; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @@ -21,8 +26,8 @@ public class AgentListener { * * @return String */ - @GetMapping("/api/endpoint") - public ResponseEntity agent(HttpServletRequest request) { + @PostMapping("/api/agent/register") + public ResponseEntity agent(HttpServletRequest request, @RequestBody AgentInfo agentInfo) { String authorization = request.getHeader("Authorization"); if (authorization == null) { throw new GlobalException(ErrorCode.UNAUTHORIZED); @@ -36,7 +41,13 @@ public ResponseEntity agent(HttpServletRequest request) { } // Add agent server url and port to the list - String url = agentServerManager.add(request.getRemoteAddr()+":"+request.getRemotePort()); - return ResponseEntity.ok(url + " is added successfully"); +// agentServerManager.add(agentInfo); + return ResponseEntity.ok(agentInfo.getServerUrl() + " is added successfully"); + } + + @GetMapping("/api/agents") + public List getAgents(){ + ConcurrentHashMap agentsUrl = agentServerManager.getAgentsUrl(); + return agentsUrl.values().stream().toList(); } } diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentServerManager.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentServerManager.java index a6c07467..fa4e9b03 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentServerManager.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/agent/AgentServerManager.java @@ -1,39 +1,54 @@ package org.benchmarker.bmcontroller.agent; +import java.util.HashMap; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.benchmarker.bmagent.AgentInfo; import org.benchmarker.bmagent.AgentStatus; import org.springframework.stereotype.Component; @Component +@Getter +@Slf4j public class AgentServerManager { - private final ConcurrentHashMap agentsUrl; - - public AgentServerManager() { - agentsUrl = new ConcurrentHashMap<>(); + private final ConcurrentHashMap agentsUrl = new ConcurrentHashMap<>(); + private final HashMap agentMapped = new HashMap<>(); + public void add(String url, AgentInfo agentInfo) { + agentsUrl.put(url, agentInfo); } - public String add(String url) { - agentsUrl.put(url, AgentStatus.READY); - return url; + public void update(String url, AgentInfo agentInfo){ + if (agentsUrl.get(agentInfo.getServerUrl())==null){ + log.error("cannot find agent"); + return; + } + agentsUrl.put(agentInfo.getServerUrl(), agentInfo); } - public void remove(String url) { - agentsUrl.remove(url); + public Optional getReadyAgent(){ + for (AgentInfo agentInfo : agentsUrl.values()){ + if (agentInfo.getStatus()== AgentStatus.READY){ + return Optional.of(agentInfo); + } + } + return Optional.empty(); } - public void updateStatus(String url, AgentStatus status) { - agentsUrl.put(url, status); - } - - public AgentStatus getStatus(String url) { - return agentsUrl.get(url); + public void removeAgent(String url){ + agentsUrl.remove(url); } - public ConcurrentHashMap getAllAgents() { - return agentsUrl; + public void addTemplateRunnerAgent(Long id, String url){ + agentMapped.put(id,url); } - public boolean isAgentExist(String url) { - return agentsUrl.containsKey(url); + public void removeTemplateRunnerAgent(Long id){ + String url = agentMapped.get(id); + if (url != null){ + agentMapped.remove(id); + agentsUrl.remove(url); + } } } diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/common/controller/GlobalRestControllerAdvice.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/common/controller/GlobalRestControllerAdvice.java index d924daa4..20af4c15 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/common/controller/GlobalRestControllerAdvice.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/common/controller/GlobalRestControllerAdvice.java @@ -2,6 +2,7 @@ import jakarta.validation.ConstraintViolationException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.benchmarker.bmcontroller.common.error.ErrorCode; import org.benchmarker.bmcontroller.common.error.GlobalErrorResponse; import org.benchmarker.bmcontroller.common.error.GlobalException; @@ -13,6 +14,7 @@ @RestControllerAdvice @RequiredArgsConstructor +@Slf4j public class GlobalRestControllerAdvice { @ExceptionHandler(GlobalException.class) @@ -32,6 +34,7 @@ public ResponseEntity handlerResponseEntity(AccessDeniedExc MethodArgumentNotValidException.class, }) public ResponseEntity handleBadRequestException(Exception e) { + log.error(e.getMessage()); return GlobalErrorResponse.toResponseEntity(ErrorCode.BAD_REQUEST); } } diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/controller/PerftestController.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/controller/PerftestController.java index 1e24713b..2241f827 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/controller/PerftestController.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/controller/PerftestController.java @@ -4,7 +4,10 @@ import lombok.extern.slf4j.Slf4j; import org.benchmarker.bmcommon.dto.CommonTestResult; import org.benchmarker.bmcommon.dto.TemplateInfo; +import org.benchmarker.bmcontroller.agent.AgentServerManager; import org.benchmarker.bmcontroller.common.controller.annotation.GlobalControllerModel; +import org.benchmarker.bmcontroller.common.error.ErrorCode; +import org.benchmarker.bmcontroller.common.error.GlobalException; import org.benchmarker.bmcontroller.preftest.service.PerftestService; import org.benchmarker.bmcontroller.template.service.ITestTemplateService; import org.benchmarker.bmcontroller.user.service.UserContext; @@ -31,6 +34,7 @@ public class PerftestController { private final ITestTemplateService testTemplateService; private final PerftestService perftestService; private final UserContext userContext; + private final AgentServerManager agentServerManager; private final String agentUrl = "http://localhost:8081"; @GetMapping("/groups/{group_id}/templates/{template_id}") @@ -54,15 +58,31 @@ public ResponseEntity send(@PathVariable("group_id") String groupId, @RequestParam(value = "action") String action) throws Exception { log.info("Send action: {}", action); String userId = userContext.getCurrentUser().getId(); + String serverUrl = ""; + if (action.equals("stop")){ + + serverUrl = agentServerManager.getAgentMapped().get(Long.valueOf(templateId)); + agentServerManager.removeTemplateRunnerAgent(Long.valueOf(templateId)); + log.info("stop to " + serverUrl); + }else{ + + serverUrl = agentServerManager.getReadyAgent().orElseThrow(() -> + new GlobalException(ErrorCode.INTERNAL_SERVER_ERROR)).getServerUrl(); + agentServerManager.addTemplateRunnerAgent(Long.valueOf(templateId), serverUrl); + log.info("send to " + serverUrl); + } + WebClient webClient = WebClient.create(serverUrl); - WebClient webClient = WebClient.create(agentUrl); TemplateInfo templateInfo = testTemplateService.getTemplateInfo(userId, templateId); + Flux> eventStream = perftestService.executePerformanceTest( - templateId, action, webClient, templateInfo); + templateId, groupId, action, webClient, templateInfo); + perftestService.saveRunning(groupId, templateId); eventStream .doOnComplete(() -> { + perftestService.removeRunning(groupId,templateId); // TODO : CommonTestResult 저장 logic 구현 필요 // 코드 한줄 if (action.equals("stop")) { diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/service/PerftestService.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/service/PerftestService.java index 96aa397d..2445dd4e 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/service/PerftestService.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/service/PerftestService.java @@ -1,5 +1,9 @@ package org.benchmarker.bmcontroller.preftest.service; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.benchmarker.bmcommon.dto.CommonTestResult; import org.benchmarker.bmcommon.dto.TemplateInfo; @@ -10,9 +14,35 @@ import reactor.core.publisher.Flux; @Service +@Getter @Slf4j public class PerftestService { + private ConcurrentHashMap> runningTemplates = new ConcurrentHashMap<>(); + + public void saveRunning(String groupId, Integer templateId) { + Set templates = runningTemplates.get(groupId); + if (templates != null) { + templates.add(templateId); + } else { + HashSet temp = new HashSet(); + temp.add(templateId); + runningTemplates.put(groupId, temp); + } + log.info(runningTemplates.toString()); + } + + public void removeRunning(String groupId, Integer templateId) { + Set templates = runningTemplates.get(groupId); + if (templates != null) { + templates.remove(templateId); + if (templates.size()==0){ + runningTemplates.remove(groupId); + } + } + ; + } + /** * Execute a performance test request to the bm-agent API and receive intermediate results via * Server-Sent Events (SSE). @@ -24,12 +54,14 @@ public class PerftestService { * @return Flux {@link ServerSentEvent} {@link CommonTestResult} */ public Flux> executePerformanceTest(Integer templateId, + String groupId, String action, WebClient webClient, TemplateInfo templateInfo) { ParameterizedTypeReference> typeReference = new ParameterizedTypeReference>() { }; return webClient.post() - .uri("/api/templates/{templateId}?action={action}", templateId, action) + .uri("/api/groups/{groupId}/templates/{templateId}?action={action}", groupId, + templateId, action) .bodyValue(templateInfo) .retrieve() .bodyToFlux(typeReference) diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/prerun/DataLoader.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/prerun/DataLoader.java index e4b48c31..e8f4da48 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/prerun/DataLoader.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/prerun/DataLoader.java @@ -1,25 +1,39 @@ package org.benchmarker.bmcontroller.prerun; +import static org.benchmarker.bmcontroller.common.util.NoOp.noOp; +import static org.benchmarker.bmcontroller.user.constant.UserConsts.USER_GROUP_DEFAULT_ID; +import static org.benchmarker.bmcontroller.user.constant.UserConsts.USER_GROUP_DEFAULT_NAME; + import jakarta.transaction.Transactional; +import java.util.Iterator; import java.util.List; +import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; +import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.benchmarker.bmcontroller.user.model.enums.GroupRole; -import org.benchmarker.bmcontroller.user.model.enums.Role; +import lombok.extern.slf4j.Slf4j; +import org.benchmarker.bmagent.AgentInfo; +import org.benchmarker.bmcontroller.agent.AgentServerManager; +import org.benchmarker.bmcontroller.scheduler.ScheduledTaskService; import org.benchmarker.bmcontroller.user.model.User; import org.benchmarker.bmcontroller.user.model.UserGroup; import org.benchmarker.bmcontroller.user.model.UserGroupJoin; +import org.benchmarker.bmcontroller.user.model.enums.GroupRole; +import org.benchmarker.bmcontroller.user.model.enums.Role; import org.benchmarker.bmcontroller.user.repository.UserGroupJoinRepository; import org.benchmarker.bmcontroller.user.repository.UserGroupRepository; import org.benchmarker.bmcontroller.user.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; - -import static org.benchmarker.bmcontroller.common.util.NoOp.noOp; -import static org.benchmarker.bmcontroller.user.constant.UserConsts.USER_GROUP_DEFAULT_ID; -import static org.benchmarker.bmcontroller.user.constant.UserConsts.USER_GROUP_DEFAULT_NAME; +import org.springframework.web.reactive.function.client.WebClient; /** * After the application starts, this class will be executed to add the default user to the @@ -28,17 +42,23 @@ * @see org.springframework.boot.CommandLineRunner */ @Component +@Slf4j @RequiredArgsConstructor public class DataLoader implements CommandLineRunner { - + private final SimpMessagingTemplate messagingTemplate; private final UserRepository userRepository; private final UserGroupRepository userGroupRepository; private final UserGroupJoinRepository userGroupJoinRepository; private final PasswordEncoder passwordEncoder; + private final ScheduledTaskService scheduledTaskService; + private final AgentServerManager agentServerManager; + @Value("${admin.id}") private String adminId; @Value("${admin.password}") private String adminPassword; + @Autowired + private DiscoveryClient discoveryClient; @Override @Transactional @@ -67,6 +87,57 @@ public void run(String... args) throws Exception { .build() )); } + + // remove & add agent in every seconds + scheduledTaskService.start(-100L, () -> { + // agent health check + Iterator> iterator = agentServerManager.getAgentsUrl() + .entrySet().iterator(); + while (iterator.hasNext()) { + Entry next = iterator.next(); + try { + ResponseEntity agentInfo = WebClient.create(next.getKey()) + .get() + .uri("/api/status") + .retrieve() + .toEntity(AgentInfo.class) + .block(); + + assert agentInfo != null; + if (agentInfo.getStatusCode().is2xxSuccessful()) { + next.setValue(Objects.requireNonNull(agentInfo.getBody())); + } else { + iterator.remove(); + } + } catch (Exception e) { + iterator.remove(); + } + } + + // get current agent from eureka discovery + List instances = discoveryClient.getInstances("bm-agent"); + // 각 인스턴스의 URL을 사용하여 요청을 보냄 + for (ServiceInstance instance : instances) { + try{ + String serverUrl = instance.getUri().toString(); + AgentInfo agentInfo = WebClient.create(serverUrl) + .get() + .uri("/api/status") + .retrieve() + .bodyToMono(AgentInfo.class) + .block(); + + agentInfo.setServerUrl(serverUrl); + agentServerManager.add(serverUrl, agentInfo); + }catch (Exception e){ + noOp(); + } + } + messagingTemplate.convertAndSend("/topic/server", + agentServerManager.getAgentsUrl().values()); + }, 0, 500, TimeUnit.MILLISECONDS); + + } private UserGroup defaultAdminGroup() { diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/AbstractScheduledTaskService.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/AbstractScheduledTaskService.java new file mode 100644 index 00000000..a2f10963 --- /dev/null +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/AbstractScheduledTaskService.java @@ -0,0 +1,17 @@ +package org.benchmarker.bmcontroller.scheduler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Abstract class for the scheduled task service + */ +public abstract class AbstractScheduledTaskService implements IScheduledTaskService { + + /** + * Map of scheduler id to the scheduler + */ + protected final Map schedulers = new ConcurrentHashMap<>(); + protected final Map> schedulerChild = new ConcurrentHashMap<>(); +} diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/IScheduledTaskService.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/IScheduledTaskService.java new file mode 100644 index 00000000..abd1b6ab --- /dev/null +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/IScheduledTaskService.java @@ -0,0 +1,60 @@ +package org.benchmarker.bmcontroller.scheduler; + +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * This service allows you to schedule and manage tasks to be executed at specific intervals. + * + *

+ * Each task is identified by a unique ID, enabling you to easily find, start, and shutdown + * scheduled tasks. It is crucial to ensure that IDs are unique across all scheduled + * tasks. + * + * + *

+ */ +public interface IScheduledTaskService { + + /** + * Shutdowns the scheduler associated with the given ID. + * + *

+ * The ID serves as the key to identify the scheduler to be shutdown. + *

+ * + * @param id Unique identifier of the scheduler. + */ + void shutdown(Long id); + + /** + * Checks if a child scheduler exists for the given ID. + * + * @param id The ID to check for the existence of a child scheduler. + * @return true if a child scheduler exists, otherwise false. + */ + void start(Long id, Runnable runnable, long delay, long period, TimeUnit timeUnit); + + /** + * Get all schedulers associated with the given ID + * + * @param id + * @return Map + */ + Map getSchedulers(Long id); + + /** + * Starts a child scheduler with an additional name for the given ID. + * + * @param id The ID associated with the parent scheduler. + * @param schedulerName Name of the child scheduler. + * @param runnable Task to be executed. + * @param delay Delay before the first execution. + * @param period Period between successive executions. + * @param timeUnit TimeUnit for the delay and period. + */ + void startChild(Long id, String schedulerName, Runnable runnable, long delay, long period, + TimeUnit timeUnit); + +} diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/ScheduledTaskService.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/ScheduledTaskService.java new file mode 100644 index 00000000..85ad10bd --- /dev/null +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/scheduler/ScheduledTaskService.java @@ -0,0 +1,70 @@ +package org.benchmarker.bmcontroller.scheduler; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ScheduledTaskService extends AbstractScheduledTaskService { + + @Override + public void shutdown(Long id) { + ScheduledExecutorService scheduler = schedulers.get(id); + if (scheduler != null) { + log.info("[id:{}] scheduler shutdown", id); + if (schedulerChild.get(id) != null) { + for (ScheduledExecutorService child : schedulerChild.get(id).values()) { + child.shutdown(); + } + } + scheduler.shutdown(); + } + schedulers.remove(id); + schedulerChild.remove(id); + } + + @Override + public void start(Long id, Runnable runnable, long delay, long period, TimeUnit timeUnit) { + if (schedulers.get(id) != null) { + ScheduledExecutorService a = schedulers.get(id); + String msg = "already id: " + id + " is assigned in major scheduler"; + log.error(msg); + throw new IllegalThreadStateException(msg); + } + ; + log.info("[id:{}] scheduler start", id); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + schedulers.put(id, scheduler); + scheduler.scheduleAtFixedRate(runnable, delay, period, timeUnit); + } + + @Override + public Map getSchedulers(Long id) { + ScheduledExecutorService majorScheduler = schedulers.get(id); + Map schedulerMap = schedulerChild.get( + id); + HashMap newMap = new HashMap<>(); + if (schedulerMap != null) { + newMap.putAll(schedulerMap); + } + if (majorScheduler != null) { + newMap.put("major", majorScheduler); + } + return newMap; + } + + @Override + public void startChild(Long id, String schedulerName, Runnable runnable, long delay, + long period, TimeUnit timeUnit) { + log.info("[id:{}] child scheduler start", id); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + schedulerChild.put(id, Map.of(schedulerName, scheduler)); + scheduler.scheduleAtFixedRate(runnable, delay, period, timeUnit); + } + +} diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/BMUserDetailsService.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/BMUserDetailsService.java index 5b2a4672..fb868364 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/BMUserDetailsService.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/BMUserDetailsService.java @@ -21,7 +21,6 @@ public BMUserDetailsService(UserRepository userRepository) { @Override public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { - log.info("loadByUsername : {}", userId); User user = userRepository.findById(userId) .orElseThrow( () -> new UsernameNotFoundException("User not found with userId: " + userId)); diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/JwtAuthFilter.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/JwtAuthFilter.java index b1d7a9ef..23517655 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/JwtAuthFilter.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/security/JwtAuthFilter.java @@ -50,9 +50,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); - - log.info("SecurityContextHolder.getContext().getAuthentication() : {}", - SecurityContextHolder.getContext().getAuthentication()); } } catch (UsernameNotFoundException ex) { Cookie cookie = new Cookie(TokenConsts.ACCESS_TOKEN_COOKIE_NAME, null); diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/GroupController.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/GroupController.java index bd168393..5583999c 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/GroupController.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/GroupController.java @@ -10,7 +10,6 @@ import org.benchmarker.bmcontroller.user.service.GroupService; import org.benchmarker.bmcontroller.user.service.UserContext; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; @@ -19,7 +18,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; @Slf4j @Controller @@ -56,7 +54,7 @@ public String createGroup( */ @GetMapping("/groups/{group_id}") @PreAuthorize("hasAnyRole('USER')") - public String groupGet(@PathVariable String group_id, Model model) { + public String groupGet(@PathVariable("group_id") String group_id, Model model) { GroupInfo groupInfo = null; if (userContext.getCurrentUser().getRole().isAdmin()) { groupInfo = groupService.getGroupInfoAdmin(group_id); @@ -70,12 +68,10 @@ public String groupGet(@PathVariable String group_id, Model model) { @GetMapping("/groups") @PreAuthorize("hasAnyRole('USER')") - public String groups( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, Model model) { + public String groups(Pageable pageable, Model model) { List groupInfo = Arrays.asList(); - Pageable pageable = PageRequest.of(page, size); + Page groupInfoPage; if (userContext.getCurrentUser().getRole().isAdmin()) { groupInfoPage = groupService.getAllGroupInfoAdmin(pageable); @@ -83,7 +79,7 @@ public String groups( groupInfoPage = groupService.getAllGroupInfo(userContext.getCurrentUser().getId(), pageable); } model.addAttribute("groupInfo", groupInfoPage.getContent()); - model.addAttribute("currentPage", page); + model.addAttribute("currentPage", pageable.getPageNumber()); model.addAttribute("totalPages", groupInfoPage.getTotalPages()); return "group/list"; } diff --git a/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/UserController.java b/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/UserController.java index a28fb236..02f676eb 100644 --- a/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/UserController.java +++ b/bm-controller/src/main/java/org/benchmarker/bmcontroller/user/controller/UserController.java @@ -40,7 +40,7 @@ public String user(@ModelAttribute("userRegisterDto") UserRegisterDto userRegist @GetMapping({"/users/{user_id}"}) @PreAuthorize("hasAnyRole('USER')") public String getUser( - @PathVariable(required = false) String user_id, Model model) { + @PathVariable(value = "user_id", required = false) String user_id, Model model) { User currentUser = userContext.getCurrentUser(); UserInfo userInfo = null; if (user_id == null) { diff --git a/bm-controller/src/main/resources/application.yaml b/bm-controller/src/main/resources/application.yaml index 57c62139..7eb19480 100644 --- a/bm-controller/src/main/resources/application.yaml +++ b/bm-controller/src/main/resources/application.yaml @@ -9,8 +9,6 @@ benchmark: description: 'Benchmark application' contacts: Gyumin Hwangbo;ghkdqhrbals@gmail.com,JeongGi Lee;apple4rhk@naver.com - - # admin user admin: id: 'admin' @@ -52,4 +50,33 @@ logging: level: org: springframework: - security: DEBUG \ No newline at end of file + security: INFO + +eureka: + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + +management: + endpoints: + web: + exposure: + include: "*" # 모든 엔드포인트를 노출합니다. 필요에 따라 특정 엔드포인트만 선택할 수도 있습니다. + endpoint: + health: + show-details: always # Health 엔드포인트에서 상세 정보를 항상 표시합니다. + health: + groups: + readiness: + include: "*" # 모든 상태를 readiness 그룹에 포함합니다. + liveness: + include: "*" # 모든 상태를 liveness 그룹에 포함합니다. + info: + app: + name: "BM-Controller" # 애플리케이션 이름을 설정합니다. + version: "1.0.0" # 애플리케이션 버전을 설정합니다. + contact: + name: "gyumin hwangbo" # 연락처 이름을 설정합니다. + email: "ghkdqhrbals@gmail.com" # 이메일 주소를 설정합니다. \ No newline at end of file diff --git a/bm-controller/src/main/resources/static/css/styles.css b/bm-controller/src/main/resources/static/css/styles.css index d348b5b7..ccb5ff7d 100644 --- a/bm-controller/src/main/resources/static/css/styles.css +++ b/bm-controller/src/main/resources/static/css/styles.css @@ -4,10 +4,6 @@ table { border-collapse: collapse; width: 100%; } -/* 스타일을 적용할 div 요소에 대한 CSS */ -div { - margin-bottom: 15px; /* 다른 요소들과의 간격 조정 */ -} /* label에 대한 CSS */ label { @@ -188,4 +184,41 @@ button[type="submit"]:hover { #deleteUserGroupButton:hover { background-color: #d9573b; margin-bottom: 10px; +} + +#agentInfoContainer { + display: flex; + flex-wrap: wrap; /* 요소들이 넘칠 경우 줄 바꿈 */ + background-color: #f8f9fa; /* 배경색 설정 */ + padding: 10px; /* 내부 여백 설정 */ + align-items: center; /* 요소들을 수직 가운데 정렬 */ + align-self: center; + border-radius: 5px; /* 테두리 모서리를 둥글게 만듦 */ + margin-bottom: 0px; + border: 1px solid #ced4da; /* 테두리 설정 */ + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 그림자 효과 추가 */ +} + +.agent-info { + flex: 0 0 calc(20% - 6px); /* 최대 5개씩 나열되도록 20%의 너비 할당 */ + margin-right: 10px; /* 요소들 간의 간격을 설정 */ + margin-bottom: 10px; /* 아래쪽 간격 설정 */ +} + +.agent-info { + background-color: #f5f5f5; /* 배경색 설정 */ + border-radius: 10px; /* 테두리 모서리를 둥글게 만듦 */ + padding: 10px; /* 내부 여백 설정 */ + margin-bottom: 10px; /* 아래쪽 간격 설정 */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 그림자 효과 추가 */ +} + +.agent-info p { + margin: 0; /* 단락의 기본 마진 제거 */ +} + +.status-circle { + width: 20px; /* 원의 크기 늘리기 */ + height: 20px; + margin-left: 15px; /* 원과 텍스트 간격 늘리기 */ } \ No newline at end of file diff --git a/bm-controller/src/main/resources/static/js/connectStomp.js b/bm-controller/src/main/resources/static/js/connectStomp.js index c58d5aaf..cb16272a 100644 --- a/bm-controller/src/main/resources/static/js/connectStomp.js +++ b/bm-controller/src/main/resources/static/js/connectStomp.js @@ -1,4 +1,2 @@ // common.js -var initialized = false; -var stompClient = null; - +var stompClient = null; \ No newline at end of file diff --git a/bm-controller/src/main/resources/templates/fragments/agentStatus.html b/bm-controller/src/main/resources/templates/fragments/agentStatus.html new file mode 100644 index 00000000..fcdc426f --- /dev/null +++ b/bm-controller/src/main/resources/templates/fragments/agentStatus.html @@ -0,0 +1,95 @@ + + + + + + + + + + +
+ + +
+ + + + + + diff --git a/bm-controller/src/main/resources/templates/fragments/commonHeader.html b/bm-controller/src/main/resources/templates/fragments/commonHeader.html new file mode 100644 index 00000000..329ffbf9 --- /dev/null +++ b/bm-controller/src/main/resources/templates/fragments/commonHeader.html @@ -0,0 +1,15 @@ + + + + + TITLE + + + + + + + + + + \ No newline at end of file diff --git a/bm-controller/src/main/resources/templates/group/info.html b/bm-controller/src/main/resources/templates/group/info.html index 574958c1..023d7708 100644 --- a/bm-controller/src/main/resources/templates/group/info.html +++ b/bm-controller/src/main/resources/templates/group/info.html @@ -1,13 +1,13 @@ - - - Group Information - - - + +
+ +
+
+

Information about Group

@@ -61,7 +61,7 @@

Test Templates

- + @@ -134,6 +134,8 @@

Add Template

- - - - - - Test Results - -
+
@@ -175,43 +109,30 @@

Test Results

- diff --git a/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentListenerTest.java b/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentListenerTest.java index cde899f9..7b61b961 100644 --- a/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentListenerTest.java +++ b/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentListenerTest.java @@ -1,8 +1,13 @@ package org.benchmarker.bmcontroller.agent; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.time.ZonedDateTime; +import org.benchmarker.bmagent.AgentInfo; +import org.benchmarker.bmagent.AgentStatus; import org.benchmarker.bmcontroller.security.JwtTokenProvider; import org.benchmarker.bmcontroller.user.model.enums.Role; import org.junit.jupiter.api.BeforeEach; @@ -12,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; import org.util.annotations.RestDocsTest; @@ -47,10 +53,12 @@ public void setUp() { @DisplayName("Agent listener 로컬 테스트") void test() throws Exception { String test = jwtTokenProvider.createAccessToken("test", Role.ROLE_USER); - - mockMvc.perform(get("/api/endpoint") - .header("Authorization", "Bearer " + test)) - .andDo(response ->{ + AgentInfo agentInfo = new AgentInfo(); + mockMvc.perform(post("/api/agent/register") + .header("Authorization", "Bearer " + test) + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(agentInfo))) + .andDo(response -> { System.out.println(response.getResponse().getContentAsString()); }); } @@ -58,9 +66,14 @@ void test() throws Exception { @Test @DisplayName("Agent listener 잘못된 토큰 401 반환 테스트") void test2() throws Exception { - mockMvc.perform(get("/api/endpoint") - .header("Authorization", "Bearer " + "wrong token")) - .andDo(response ->{ + AgentInfo agentInfo = AgentInfo.builder().status(AgentStatus.READY).serverUrl("") + .cpuUsage(1D).memoryUsage(1D).startedAt( + ZonedDateTime.now()).build(); + mockMvc.perform(post("/api/agent/register") + .header("Authorization", "Bearer " + "wrong token") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(agentInfo))) + .andDo(response -> { MockHttpServletResponse resp = response.getResponse(); assertThat(resp.getStatus()).isEqualTo(401); }); @@ -69,8 +82,13 @@ void test2() throws Exception { @Test @DisplayName("Agent listener header 없을 때 401 반환 테스트") void test3() throws Exception { - mockMvc.perform(get("/api/endpoint")) - .andDo(response ->{ + AgentInfo agentInfo = AgentInfo.builder().status(AgentStatus.READY).serverUrl("") + .cpuUsage(1D).memoryUsage(1D).startedAt( + ZonedDateTime.now()).build(); + mockMvc.perform(post("/api/agent/register") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(agentInfo))) + .andDo(response -> { MockHttpServletResponse resp = response.getResponse(); assertThat(resp.getStatus()).isEqualTo(401); }); diff --git a/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentServerManagerTest.java b/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentServerManagerTest.java index 52e66c77..52a8b29f 100644 --- a/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentServerManagerTest.java +++ b/bm-controller/src/test/java/org/benchmarker/bmcontroller/agent/AgentServerManagerTest.java @@ -1,11 +1,5 @@ package org.benchmarker.bmcontroller.agent; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.concurrent.ConcurrentHashMap; -import org.benchmarker.bmagent.AgentStatus; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -16,34 +10,5 @@ class AgentServerManagerTest { private AgentServerManager agentServerManager; private String url = "http://localhost:8080"; - @Test - @DisplayName("AgentServerManager 테스트") - void test() { - assertThat(agentServerManager.getAllAgents()).isEmpty(); - - // Add agent - agentServerManager.add(url); - assertThat(agentServerManager.isAgentExist(url)).isTrue(); - assertThat(agentServerManager.getStatus(url)).isEqualTo(AgentStatus.READY); - ConcurrentHashMap allAgents = agentServerManager.getAllAgents(); - assertThat(allAgents.size()).isEqualTo(1); - assertThat(allAgents.containsKey(url)).isTrue(); - assertThat(allAgents.get(url)).isEqualTo(AgentStatus.READY); - - // Update agent status - agentServerManager.updateStatus(url, AgentStatus.TESTING); - assertThat(agentServerManager.getStatus(url)).isEqualTo(AgentStatus.TESTING); - - // Remove agent - agentServerManager.remove(url); - assertThat(agentServerManager.isAgentExist(url)).isFalse(); - assertThat(agentServerManager.getStatus(url)).isNull(); - - // Check getAllAgents - allAgents = agentServerManager.getAllAgents(); - assertThat(allAgents.size()).isEqualTo(0); - assertThat(allAgents.containsKey(url)).isFalse(); - assertThat(allAgents.get(url)).isNull(); - } } \ No newline at end of file diff --git a/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/controller/PerftestControllerTest.java b/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/controller/PerftestControllerTest.java index 19628224..5c96f08b 100644 --- a/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/controller/PerftestControllerTest.java +++ b/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/controller/PerftestControllerTest.java @@ -11,9 +11,12 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.benchmarker.bmagent.AgentInfo; import org.benchmarker.bmcommon.dto.CommonTestResult; import org.benchmarker.bmcommon.dto.TemplateInfo; import org.benchmarker.bmcommon.util.RandomUtils; +import org.benchmarker.bmcontroller.agent.AgentServerManager; import org.benchmarker.bmcontroller.preftest.service.PerftestService; import org.benchmarker.bmcontroller.template.service.ITestTemplateService; import org.benchmarker.bmcontroller.user.controller.constant.TestUserConsts; @@ -54,6 +57,9 @@ public class PerftestControllerTest { @Mock private UserContext userContext; + @Mock + private AgentServerManager agentServerManager; + private MockMvc mockMvc; @BeforeEach @@ -85,8 +91,9 @@ public void testSend_Success() throws Exception { randomResult).build(); Flux> eventStream = Flux.just(resultStub); - when(perftestService.executePerformanceTest(eq(templateId), eq(action), any(), + when(perftestService.executePerformanceTest(eq(templateId), eq(groupId), eq(action), any(), eq(templateInfo))).thenReturn(eventStream); + when(agentServerManager.getReadyAgent()).thenReturn(Optional.of(new AgentInfo())); // when mockMvc.perform( @@ -116,7 +123,6 @@ public void testGetTest() throws Exception { .andExpect(model().attributeExists("groupId", "templateId", "template")); } - // HERE!! @Test void testExecutePerformanceTest() { @@ -134,12 +140,12 @@ void testExecutePerformanceTest() { Flux> mockFlux = Flux.fromIterable(mockEvents); - when(perftestService.executePerformanceTest(Mockito.anyInt(), Mockito.anyString(), + when(perftestService.executePerformanceTest(Mockito.anyInt(), Mockito.anyString(), Mockito.anyString(), any(), any())).thenReturn(mockFlux); // when - Flux> eventStream = perftestService.executePerformanceTest( - 2, "start", WebClient.create(), templateInfo); + Flux> eventStream = perftestService.executePerformanceTest(1, + "2", "start", WebClient.create(), templateInfo); // then // Verify that the mock event is received diff --git a/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/service/PerftestServiceTest.java b/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/service/PerftestServiceTest.java index 11b994ea..c802ab4f 100644 --- a/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/service/PerftestServiceTest.java +++ b/bm-controller/src/test/java/org/benchmarker/bmcontroller/preftest/service/PerftestServiceTest.java @@ -28,7 +28,7 @@ void testExecutePerformanceTest() { .build(); Flux> eventStream = perftestService.executePerformanceTest(1, - "start", webClient, templateInfo); + "groupId","start", webClient, templateInfo); StepVerifier.create(eventStream) .expectNextMatches(event -> { diff --git a/bm-controller/src/test/java/org/benchmarker/bmcontroller/prerun/DataLoaderTest.java b/bm-controller/src/test/java/org/benchmarker/bmcontroller/prerun/DataLoaderTest.java new file mode 100644 index 00000000..604595e3 --- /dev/null +++ b/bm-controller/src/test/java/org/benchmarker/bmcontroller/prerun/DataLoaderTest.java @@ -0,0 +1,7 @@ +package org.benchmarker.bmcontroller.prerun; + +import static org.junit.jupiter.api.Assertions.*; + +class DataLoaderTest { + +} \ No newline at end of file diff --git a/bm-controller/src/test/resources/application.yaml b/bm-controller/src/test/resources/application.yaml index 8c91a36f..3b7b84fc 100644 --- a/bm-controller/src/test/resources/application.yaml +++ b/bm-controller/src/test/resources/application.yaml @@ -16,6 +16,9 @@ admin: password: 'admin' spring: + cloud: + discovery: + enabled: false profiles: active: test datasource: diff --git a/build.gradle b/build.gradle index 16a43279..1e83699c 100644 --- a/build.gradle +++ b/build.gradle @@ -1 +1 @@ -version = '0.0.1' \ No newline at end of file +version = '1.0.0' \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 564d4b0c..66d6318f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,4 +13,49 @@ services: - ./backups:/home/backups command: -c wal_level=logical -p 5433 restart: always - + eureka: + build: + context: ./eureka + dockerfile: Dockerfile + environment: + - server_port=8761 + bm-controller: + container_name: bm-controller + build: + context: ./bm-controller + dockerfile: Dockerfile + environment: + - server_port=8080 + - spring_datasource_url=jdbc:postgresql://test-db:5433/test + - eureka_client_serviceUrl_defaultZone=http://eureka:8761/eureka/ + - POSTGRES_DB=test + ports: + - "8080:8080" + restart: always + depends_on: + - eureka + bm-agent-1: + build: + context: ./bm-agent + dockerfile: Dockerfile + environment: + - controller_url=http://bm-controller:8080/api/agent/register + - eureka_client_serviceUrl_defaultZone=http://eureka:8761/eureka/ + ports: + - "8081:8081" + restart: always + depends_on: + - eureka + - bm-controller + bm-agent-2: + build: + context: ./bm-agent + dockerfile: Dockerfile + environment: + - controller_url=http://bm-controller:8080/api/agent/register + - eureka_client_serviceUrl_defaultZone=http://eureka:8761/eureka/ + ports: + - "8082:8081" + depends_on: + - eureka + - bm-controller diff --git a/eureka/.gitignore b/eureka/.gitignore new file mode 100644 index 00000000..c2065bc2 --- /dev/null +++ b/eureka/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/eureka/Dockerfile b/eureka/Dockerfile new file mode 100644 index 00000000..5074ae6c --- /dev/null +++ b/eureka/Dockerfile @@ -0,0 +1,7 @@ +FROM amazoncorretto:17-alpine3.16-jdk + +WORKDIR /app + +COPY /build/libs/eureka-1.0.0.jar /app/app.jar + +ENTRYPOINT java -jar app.jar diff --git a/eureka/build.gradle b/eureka/build.gradle new file mode 100644 index 00000000..92caea58 --- /dev/null +++ b/eureka/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.4' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'org.benchmarker' +version = '1.0.0' + +java { + sourceCompatibility = '17' +} + +repositories { + mavenCentral() +} + +ext { + set('springCloudVersion', "2023.0.1") +} + +dependencies { + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/eureka/gradle/wrapper/gradle-wrapper.jar b/eureka/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e6441136 Binary files /dev/null and b/eureka/gradle/wrapper/gradle-wrapper.jar differ diff --git a/eureka/gradle/wrapper/gradle-wrapper.properties b/eureka/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b82aa23a --- /dev/null +++ b/eureka/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/eureka/gradlew b/eureka/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/eureka/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/eureka/gradlew.bat b/eureka/gradlew.bat new file mode 100644 index 00000000..25da30db --- /dev/null +++ b/eureka/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/eureka/settings.gradle b/eureka/settings.gradle new file mode 100644 index 00000000..b12c2b71 --- /dev/null +++ b/eureka/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'eureka' diff --git a/eureka/src/main/java/org/benchmarker/eureka/EurekaApplication.java b/eureka/src/main/java/org/benchmarker/eureka/EurekaApplication.java new file mode 100644 index 00000000..1d6cdf12 --- /dev/null +++ b/eureka/src/main/java/org/benchmarker/eureka/EurekaApplication.java @@ -0,0 +1,15 @@ +package org.benchmarker.eureka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + +@SpringBootApplication +@EnableEurekaServer +public class EurekaApplication { + + public static void main(String[] args) { + SpringApplication.run(EurekaApplication.class, args); + } + +} diff --git a/eureka/src/main/resources/application.yaml b/eureka/src/main/resources/application.yaml new file mode 100644 index 00000000..f35f622a --- /dev/null +++ b/eureka/src/main/resources/application.yaml @@ -0,0 +1,7 @@ +server: + port: 8761 + +eureka: + client: + register-with-eureka: false + fetch-registry: false \ No newline at end of file diff --git a/eureka/src/test/java/org/benchmarker/eureka/EurekaApplicationTests.java b/eureka/src/test/java/org/benchmarker/eureka/EurekaApplicationTests.java new file mode 100644 index 00000000..bc4a7026 --- /dev/null +++ b/eureka/src/test/java/org/benchmarker/eureka/EurekaApplicationTests.java @@ -0,0 +1,13 @@ +package org.benchmarker.eureka; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EurekaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/settings.gradle b/settings.gradle index 35095b87..f06a0482 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ rootProject.name = 'benchmark' include ':bm-controller' include ':bm-agent' -include ':bm-common' \ No newline at end of file +include ':bm-common' +include ':eureka' \ No newline at end of file