diff --git a/.env b/.env index 16152ac..14df861 100644 --- a/.env +++ b/.env @@ -14,7 +14,17 @@ MAIL_DB_URL=jdbc:mariadb://host.docker.internal:3306/mailing?autoReconnect=true& MAIL_DB_USERNM=mcmpcostopti MAIL_DB_PW=0000 # CostCollector -COST_COLLECT_CRON_SCHEDULE=0 30 0 * * ? +COST_COLLECT_UNUSED_CRON_SCHEDULE=0 30 0 * * ? +COST_COLLECT_CUR_CRON_SCHEDULE=0 0 0,6 * * ? +AWS_CUR_EXPORT_NAME=MCMP-CostOpti +AWS_CUR_EXPORT_PATH_PREFIX=mcmp-costopti +AWS_ACCESS_KEY_ID=accesskey +AWS_SECRET_ACCESS_KEY=secretkey # CostProcessor -COST_PROCESS_CRON_SCHEDULE=0 45 * * * ? +COST_PROCESS_UNUSED_CRON_SCHEDULE=0 45 * * * ? +COST_PROCESS_ABNORMAL_CRON_SCHEDULE=0 0 1,7 * * ? COST_SELECTOR_URL=http://costselector:8083 +ALARM_URL=http://localhost:9000 +# AssetCollector +ASSET_MONITORING_SERVER=http://asset.mornitor.server:8080 +ASSET_COLLECT_BATCH_CRON_SCHEDULE=0 0 0 * * ? diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/common/CommonController.java b/AlarmService/src/main/java/com/mcmp/slack_demo/common/CommonController.java index b3e5e4b..d2f04c0 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/common/CommonController.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/common/CommonController.java @@ -4,6 +4,7 @@ import com.mcmp.slack_demo.common.model.costOpti.CostOptiAlarmReqModel; import com.mcmp.slack_demo.mail.model.MailMessage; import com.mcmp.slack_demo.mail.service.MailService; +import com.mcmp.slack_demo.slack.api_client.SlackACService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; @@ -19,6 +20,9 @@ public class CommonController { @Autowired private MailService mailService; + @Autowired + private SlackACService slackService; + @PostMapping("/sendOptiAlarmMail") public ResponseEntity sendOptiAlarmMail(@RequestBody CostOptiAlarmReqModel reqModel){ CommonResultModel result = new CommonResultModel(); @@ -29,6 +33,7 @@ public ResponseEntity sendOptiAlarmMail(@RequestBody CostOpti mailService.sendEmail(reqModel, null); break; case "slack": + slackService.sendSlack(reqModel); break; } } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/common/dao/CommonDao.java b/AlarmService/src/main/java/com/mcmp/slack_demo/common/dao/CommonDao.java index 5527d7a..0687711 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/common/dao/CommonDao.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/common/dao/CommonDao.java @@ -1,6 +1,8 @@ package com.mcmp.slack_demo.common.dao; +import com.mcmp.slack_demo.common.model.costOpti.CostOptiAlarmReqModel; import com.mcmp.slack_demo.mail.model.SendMailFormModel; +import com.mcmp.slack_demo.slack.model.SendSlackFormModel; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -15,12 +17,23 @@ public class CommonDao { @Qualifier("sqlSessionTemplateHistory") private SqlSessionTemplate sqlSession; + public int getAlertDuplicate(SendMailFormModel model){ + return sqlSession.selectOne("HistorySql.getAlertDuplicate", model); + } + public void insertAlarmHistory(SendMailFormModel model){ sqlSession.insert("HistorySql.insertAlertHistory", model); } - public List getAlarmMailReceivers(Map param){ + public List getAlarmMailReceivers(CostOptiAlarmReqModel param){ return sqlSession.selectList("HistorySql.getAlarmMailReceivers", param); } + public int getSlackDuplicate(SendSlackFormModel model){ + return sqlSession.selectOne("HistorySql.getAlertDuplicate", model); + } + + public void insertSlackHistory(SendSlackFormModel model){ + sqlSession.insert("HistorySql.insertAlertHistory", model); + } } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/common/model/costOpti/CostOptiAlarmModel.java b/AlarmService/src/main/java/com/mcmp/slack_demo/common/model/costOpti/CostOptiAlarmModel.java index 39d3d1a..c7c393a 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/common/model/costOpti/CostOptiAlarmModel.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/common/model/costOpti/CostOptiAlarmModel.java @@ -10,8 +10,11 @@ public class CostOptiAlarmModel { private String resource_id; private String resource_type; private LocalDateTime occure_time; + private String csp_type; private String account_id; private String urgency; + private String plan; private String note; + private String project_cd; } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/common/service/CommonService.java b/AlarmService/src/main/java/com/mcmp/slack_demo/common/service/CommonService.java index 3720271..87a93f4 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/common/service/CommonService.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/common/service/CommonService.java @@ -1,7 +1,9 @@ package com.mcmp.slack_demo.common.service; import com.mcmp.slack_demo.common.dao.CommonDao; +import com.mcmp.slack_demo.common.model.costOpti.CostOptiAlarmReqModel; import com.mcmp.slack_demo.mail.model.SendMailFormModel; +import com.mcmp.slack_demo.slack.model.SendSlackFormModel; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -15,11 +17,23 @@ public class CommonService { @Autowired private CommonDao commonDao; + public int getAlertDuplicate(SendMailFormModel model){ + return commonDao.getAlertDuplicate(model); + } + public void insertAlarmHistory(SendMailFormModel model){ commonDao.insertAlarmHistory(model); } - public List getAlarmMailReceivers(Map param){ + public List getAlarmMailReceivers(CostOptiAlarmReqModel param){ return commonDao.getAlarmMailReceivers(param); } + + public int getSlackDuplicate(SendSlackFormModel model){ + return commonDao.getSlackDuplicate(model); + } + + public void insertSlackHistory(SendSlackFormModel model){ + commonDao.insertSlackHistory(model); + } } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/mail/model/SendMailFormModel.java b/AlarmService/src/main/java/com/mcmp/slack_demo/mail/model/SendMailFormModel.java index 0c5c44c..150d14a 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/mail/model/SendMailFormModel.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/mail/model/SendMailFormModel.java @@ -10,4 +10,5 @@ public class SendMailFormModel extends CostOptiAlarmReqModel { private List to; private String subject; private String message; + private String alarm_impl; } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/mail/service/MailService.java b/AlarmService/src/main/java/com/mcmp/slack_demo/mail/service/MailService.java index 3d957b0..4e19d23 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/mail/service/MailService.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/mail/service/MailService.java @@ -51,14 +51,22 @@ public class MailService { public void sendEmail(CostOptiAlarmReqModel optiAlarmReqModel, ClassPathResource file){ SendMailFormModel mailFormModel = new SendMailFormModel(); BeanUtils.copyProperties(optiAlarmReqModel, mailFormModel); - mailFormModel.setOccure_time(ZonedDateTime.now(ZoneId.of("UTC")).toLocalDateTime()); + mailFormModel.setOccure_time(ZonedDateTime.now().toLocalDateTime()); + mailFormModel.setAlarm_impl("mail"); + + int checkDuplicateMail = commonService.getAlertDuplicate(mailFormModel); + if(checkDuplicateMail >= 1){ + log.info("############Send OptiAlertEmail Duplicate : {} - {} - {}############", mailFormModel.getEvent_type(), mailFormModel.getResource_id() + , (mailFormModel.getAccount_id() != null ? mailFormModel.getAccount_id() : mailFormModel.getProject_cd())); + return; + } try { JavaMailSender emailSender = mailConfig.getJavaMailSender(); MimeMessage mimeMessage = emailSender.createMimeMessage(); // find mail receiver - mailFormModel.setTo(getAlarmMailReceivers(optiAlarmReqModel.getResource_id(), optiAlarmReqModel.getAccount_id())); + mailFormModel.setTo(getAlarmMailReceivers(optiAlarmReqModel)); String mailMessage = "[MCMP-Notice] Cost Alarm occurred"; switch (optiAlarmReqModel.getEvent_type()){ @@ -66,22 +74,29 @@ public void sendEmail(CostOptiAlarmReqModel optiAlarmReqModel, ClassPathResource mailFormModel.setSubject("[MCMP-Notice] Cost Alarm occurred : Caution Unused Resources"); mailMessage = "MCMP Cost에서 미사용 자원 주의 알람이 발생했습니다." + "

" + - "계정 : " + mailFormModel.getAccount_id() + "
" + - "리소스 ID : " + mailFormModel.getResource_id(); + "CSP : " + mailFormModel.getCsp_type() + "
" + + "리소스 ID : " + mailFormModel.getResource_id() + "
" + + "리소스 Type : " + mailFormModel.getResource_type() + "
" + + "해당 자원이 미사용 자원으로 의심됩니다."; break; case "Abnormal": mailFormModel.setSubject("[MCMP-Notice] Cost Alarm occurred : Warning Abnormal Cost"); mailMessage = "MCMP Cost에서 이상 비용 경고 알람이 발생했습니다." + "

" + - "계정 : " + mailFormModel.getAccount_id() + "
" + - "리소스 ID : " + mailFormModel.getResource_id(); + "CSP : " + mailFormModel.getCsp_type() + "
" + + "제품군 : " + mailFormModel.getResource_type() + "
" + + "이상비용 등급 : " + mailFormModel.getPlan() + "
" + + "이상비용이 발생했습니다. " + mailFormModel.getNote(); break; case "Resize": - mailFormModel.setSubject("[MCMP-Notice] Cost Alarm occurred : Advise Resizing Resources"); + mailFormModel.setSubject("[MCMP-Notice] Cost Alarm occurred : Advise Right Size Resources"); mailMessage = "MCMP Cost에서 자원 최적화 권고 알람이 발생했습니다." + "

" + - "계정 : " + mailFormModel.getAccount_id() + "
" + - "리소스 ID : " + mailFormModel.getResource_id(); + "CSP : " + mailFormModel.getCsp_type() + "
" + + "리소스 ID : " + mailFormModel.getResource_id() + "
" + + "리소스 Type : " + mailFormModel.getResource_type() + "
" + + "추천 Plan : " + mailFormModel.getPlan() + "
" + + mailFormModel.getNote(); break; } @@ -155,14 +170,8 @@ public MailingInfoModel getMailingInfo(){ return mailingDao.getMailingInfo(); } - private List getAlarmMailReceivers(String resource_id, String account_id){ - Map param = Map.of( - "resource_id", resource_id, - "account_id", account_id - ); - + private List getAlarmMailReceivers(CostOptiAlarmReqModel param){ return commonService.getAlarmMailReceivers(param); - } } diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/slack/api_client/SlackACService.java b/AlarmService/src/main/java/com/mcmp/slack_demo/slack/api_client/SlackACService.java index a190c7b..92bd2bb 100644 --- a/AlarmService/src/main/java/com/mcmp/slack_demo/slack/api_client/SlackACService.java +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/slack/api_client/SlackACService.java @@ -1,18 +1,27 @@ package com.mcmp.slack_demo.slack.api_client; +import com.mcmp.slack_demo.common.model.costOpti.CostOptiAlarmReqModel; +import com.mcmp.slack_demo.common.service.CommonService; import com.mcmp.slack_demo.slack.encryto.TokenService; +import com.mcmp.slack_demo.slack.model.SendSlackFormModel; import com.slack.api.Slack; import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import com.slack.api.methods.response.chat.ChatPostMessageResponse; +import com.slack.api.model.Attachment; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; import javax.naming.AuthenticationException; import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.Collections; import java.util.Map; @Service +@Slf4j public class SlackACService { private final TokenService tokenService; @@ -20,6 +29,86 @@ public class SlackACService { public SlackACService(TokenService tokenService) { this.tokenService = tokenService; } + @Autowired + private CommonService commonService; + + public void sendSlack(CostOptiAlarmReqModel costOptiAlarmReqModel) throws SlackApiException, IOException { + SendSlackFormModel slackFormModel = new SendSlackFormModel(); + BeanUtils.copyProperties(costOptiAlarmReqModel, slackFormModel); + slackFormModel.setOccure_time(ZonedDateTime.now().toLocalDateTime()); + slackFormModel.setAlarm_impl("slack"); + + int checkDuplicateMail = commonService.getSlackDuplicate(slackFormModel); + if(checkDuplicateMail >= 1){ + log.info("############Send OptiAlertSlack Duplicate : {} - {} - {}############", slackFormModel.getEvent_type(), slackFormModel.getResource_id() + , (slackFormModel.getAccount_id() != null ? slackFormModel.getAccount_id() : slackFormModel.getProject_cd())); + return; + } + + Slack slack = Slack.getInstance(); + String message = "[MCMP-Notice] Cost Alarm occurred"; + switch (costOptiAlarmReqModel.getEvent_type()){ + case "Unused": + slackFormModel.setTitle("[MCMP-Notice] Cost Alarm occurred : Caution Unused Resources"); + message = "MCMP Cost에서 미사용 자원 주의 알람이 발생했습니다." + + "\n\n" + + "CSP : " + slackFormModel.getCsp_type() + "\n" + + "리소스 ID : " + slackFormModel.getResource_id() + "\n" + + "리소스 Type : " + slackFormModel.getResource_type() + "\n" + + "해당 자원이 미사용 자원으로 의심됩니다."; + break; + case "Abnormal": + slackFormModel.setTitle("[MCMP-Notice] Cost Alarm occurred : Warning Abnormal Cost"); + message = "MCMP Cost에서 이상 비용 경고 알람이 발생했습니다." + + "\n\n" + + "CSP : " + slackFormModel.getCsp_type() + "\n" + + "제품군 : " + slackFormModel.getResource_type() + "\n" + + "이상비용 등급 : " + slackFormModel.getPlan() + "\n" + + "이상비용이 발생했습니다. " + slackFormModel.getNote(); + break; + case "Resize": + slackFormModel.setTitle("[MCMP-Notice] Cost Alarm occurred : Advise Right Size Resources"); + message = "MCMP Cost에서 자원 최적화 권고 알람이 발생했습니다." + + "\n\n" + + "CSP : " + slackFormModel.getCsp_type() + "\n" + + "리소스 ID : " + slackFormModel.getResource_id() + "\n" + + "리소스 Type : " + slackFormModel.getResource_type() + "\n" + + "추천 Plan : " + slackFormModel.getPlan() + "\n" + + slackFormModel.getNote(); + break; + } + try { +// Map result = tokenService.retrieveToken(slackFormModel.getAccount_id()); + Map result = tokenService.retrieveToken("test"); + Attachment attachment = Attachment.builder() + .title(slackFormModel.getTitle()) + .text(message) + .color("#36a64f") + .build(); + + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(result.get("channel")) + .attachments(Collections.singletonList(attachment)) + .build(); + ChatPostMessageResponse response = slack.methods(result.get("token")).chatPostMessage(request); + if (!response.isOk()) { + switch (response.getError()) { + case "invalid_auth": + throw new AuthenticationException("Invalid authentication credentials for Slack."); + case "channel_not_found": + throw new Exception("The specified channel was not found."); + default: + throw new Exception("An error occurred with Slack API: " + response.getError()); + } + } + } catch (IOException | SlackApiException e) { + e.printStackTrace(); + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + commonService.insertSlackHistory(slackFormModel); + } public void sendMessage(String userId, String message, String linkUrl, String linkText) throws SlackApiException, IOException { Slack slack = Slack.getInstance(); diff --git a/AlarmService/src/main/java/com/mcmp/slack_demo/slack/model/SendSlackFormModel.java b/AlarmService/src/main/java/com/mcmp/slack_demo/slack/model/SendSlackFormModel.java new file mode 100644 index 0000000..8903832 --- /dev/null +++ b/AlarmService/src/main/java/com/mcmp/slack_demo/slack/model/SendSlackFormModel.java @@ -0,0 +1,11 @@ +package com.mcmp.slack_demo.slack.model; + +import com.mcmp.slack_demo.common.model.costOpti.CostOptiAlarmReqModel; +import lombok.Data; + +@Data +public class SendSlackFormModel extends CostOptiAlarmReqModel { + private String title; + private String message; + private String alarm_impl; +} diff --git a/AlarmService/src/main/resources/mapper/history/History_SQL.xml b/AlarmService/src/main/resources/mapper/history/History_SQL.xml index 2ef5833..5b89d5f 100644 --- a/AlarmService/src/main/resources/mapper/history/History_SQL.xml +++ b/AlarmService/src/main/resources/mapper/history/History_SQL.xml @@ -1,21 +1,94 @@ + + - INSERT INTO alarm_history(event_type, resource_id, resource_type, occure_time, account_id, urgency, note) - VALUES (#{event_type}, #{resource_id}, #{resource_type}, #{occure_time}, #{account_id}, #{urgency}, #{note}) + INSERT INTO alarm_history(event_type, csp_type, resource_id, resource_type, occure_dt, account_id, urgency, plan, note, occure_date, alarm_impl, project_cd) + VALUES (#{event_type} + , #{csp_type} + , #{resource_id} + , #{resource_type} + , #{occure_time} + , #{account_id} + , #{urgency} + , #{plan} + , #{note} + , date(#{occure_time}) + , #{alarm_impl} + , COALESCE(#{project_cd}, 'BLANK_00_')) + ON DUPLICATE KEY UPDATE occure_dt = #{occure_time} + , account_id = #{account_id} + , urgency = #{urgency} + , plan = #{plan} + , note = #{note} - SELECT tcumr.mcmp_mail_receiver AS receiver FROM servicegroup_meta sm JOIN temp_cmp_user_info tcui - ON tcui.mcmp_user_account = sm.csp_account + ON tcui.csp_account_id = sm.csp_account JOIN temp_cmp_user_mail_receiver tcumr ON tcumr.mcmp_user_id = tcui.mcmp_user_id WHERE 1=1 - AND sm.csp_instanceid = #{resource_id} - AND sm.csp_account = #{account_id} + + + AND sm.csp_instanceid = #{resource_id} + + + AND sm.service_cd = #{project_cd} + group by tcumr.mcmp_mail_receiver + + + + + + INSERT INTO alarm_history(event_type, csp_type, resource_id, resource_type, occure_dt, account_id, urgency, plan, note, occure_date, alarm_impl, project_cd) + VALUES (#{event_type} + , #{csp_type} + , #{resource_id} + , #{resource_type} + , #{occure_time} + , #{account_id} + , #{urgency} + , #{plan} + , #{note} + , date(#{occure_time}) + , #{alarm_impl} + , COALESCE(#{project_cd}, 'BLANK_00_')) + ON DUPLICATE KEY UPDATE occure_dt = #{occure_time} + , account_id = #{account_id} + , urgency = #{urgency} + , plan = #{plan} + , note = #{note} + diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/AlarmController.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/AlarmController.java new file mode 100644 index 0000000..4544702 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/AlarmController.java @@ -0,0 +1,46 @@ +package com.mcmp.costbe.alarm; + +import com.mcmp.costbe.alarm.model.AlarmHistoryReqModel; +import com.mcmp.costbe.alarm.model.AlarmHistoryRstModel; +import com.mcmp.costbe.alarm.service.AlarmHistoryService; +import com.mcmp.costbe.common.model.ResultModel; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(path = "/api/v2/alarm") +@Tag(name = "Cost Alarm", description = "Cost Alarm API") +public class AlarmController { + + @Autowired + private AlarmHistoryService alarmHistoryService; + + @PostMapping(path = "/history") + @Operation(summary = "알람 발생 내역 조회", description = "최근 7일간 발생한 최적화 알람을 조회한다.") + @ApiResponses(value={ + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = AlarmHistoryRstModel.class))}), + @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) + }) + public ResponseEntity getAlarmHistory(@RequestBody AlarmHistoryReqModel req){ + ResultModel result = new ResultModel(); + try { + AlarmHistoryRstModel data = alarmHistoryService.getAlarmHistory(req); + result.setData(data); + } catch (Exception e){ + e.printStackTrace(); + result.setError(500, "Failed to get Alarm History Info"); + } + return ResponseEntity.ok(result); + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/dao/AlarmDao.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/dao/AlarmDao.java new file mode 100644 index 0000000..2d70adf --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/dao/AlarmDao.java @@ -0,0 +1,20 @@ +package com.mcmp.costbe.alarm.dao; + +import com.mcmp.costbe.alarm.model.AlarmHistoryItemModel; +import com.mcmp.costbe.alarm.model.AlarmHistoryReqModel; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; +import java.util.List; + +@Repository +public class AlarmDao { + + @Resource(name="sqlSessionTemplateBill") + private SqlSessionTemplate sqlSession; + + public List getAlarmHistory(AlarmHistoryReqModel req){ + return sqlSession.selectList("alarm.getAlarmHistory", req); + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryItemModel.java new file mode 100644 index 0000000..395c179 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryItemModel.java @@ -0,0 +1,21 @@ +package com.mcmp.costbe.alarm.model; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class AlarmHistoryItemModel { + + private String event_type; + private String resource_id; + private String resource_type; + private LocalDateTime occure_time; + private String csp_type; + private String account_id; + private String urgency; + private String plan; + private String note; + private String project_cd; + +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryReqModel.java new file mode 100644 index 0000000..4f4ee9f --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryReqModel.java @@ -0,0 +1,21 @@ +package com.mcmp.costbe.alarm.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +public class AlarmHistoryReqModel { + + @Schema(description = "선택 csp", example = "[\"AWS\"]", required = true) + private List selectedCsps; + @Schema(description = "선택 워크스페이스", example = "workspaceCode1", required = false, deprecated = true) + private String selectedWorkspace; + @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]", required = true) + private List selectedProjects; + + @Schema(required = false) + private LocalDate curDate; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryRstModel.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryRstModel.java new file mode 100644 index 0000000..6614ed5 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/model/AlarmHistoryRstModel.java @@ -0,0 +1,13 @@ +package com.mcmp.costbe.alarm.model; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +public class AlarmHistoryRstModel extends AlarmHistoryReqModel{ + + private List alarmHistory; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/alarm/service/AlarmHistoryService.java b/BackEnd/src/main/java/com/mcmp/costbe/alarm/service/AlarmHistoryService.java new file mode 100644 index 0000000..2a2ce14 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/alarm/service/AlarmHistoryService.java @@ -0,0 +1,36 @@ +package com.mcmp.costbe.alarm.service; + +import com.mcmp.costbe.alarm.dao.AlarmDao; +import com.mcmp.costbe.alarm.model.AlarmHistoryReqModel; +import com.mcmp.costbe.alarm.model.AlarmHistoryRstModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; + +@Service +@Slf4j +public class AlarmHistoryService { + + @Autowired + private AlarmDao alarmDao; + + public AlarmHistoryRstModel getAlarmHistory(AlarmHistoryReqModel req) { + try{ + AlarmHistoryRstModel result = new AlarmHistoryRstModel(); + req.setCurDate(LocalDate.now()); + + result.setCurDate(req.getCurDate()); + result.setAlarmHistory(alarmDao.getAlarmHistory(req)); + result.setSelectedCsps(req.getSelectedCsps()); + result.setSelectedProjects(req.getSelectedProjects()); + + return result; + } catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + } + + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/common/model/LocalDateModel.java b/BackEnd/src/main/java/com/mcmp/costbe/common/model/LocalDateModel.java new file mode 100644 index 0000000..9013349 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/common/model/LocalDateModel.java @@ -0,0 +1,10 @@ +package com.mcmp.costbe.common.model; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class LocalDateModel { + private LocalDate date; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/common/model/PrevMonthsModel.java b/BackEnd/src/main/java/com/mcmp/costbe/common/model/PrevMonthsModel.java new file mode 100644 index 0000000..b8ebe08 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/common/model/PrevMonthsModel.java @@ -0,0 +1,10 @@ +package com.mcmp.costbe.common.model; + +import lombok.Data; + +import java.util.List; + +@Data +public class PrevMonthsModel { + private List prevMonths; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/common/service/DateCalculator.java b/BackEnd/src/main/java/com/mcmp/costbe/common/service/DateCalculator.java index 7c66236..d37f9cb 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/common/service/DateCalculator.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/common/service/DateCalculator.java @@ -19,7 +19,7 @@ public DateRangeModel dateRangeCalculator(String now) { YearMonth yearMonth = YearMonth.of(date.getYear(), date.getMonth()); LocalDateTime firstDayOfMonth = yearMonth.atDay(1).atStartOfDay(); - LocalDateTime lastDayOfMonth = yearMonth.atEndOfMonth().atTime(23, 59, 59); + LocalDateTime lastDayOfMonth = yearMonth.plusMonths(1).atDay(1).atStartOfDay(); DateRangeModel result = new DateRangeModel(); result.setStartDate(firstDayOfMonth); @@ -43,6 +43,18 @@ public String curMonthDate(String now) { return date.format(formatter); } + + public List getLast12Months(String now){ + LocalDate curDate = LocalDate.parse(now, DateTimeFormatter.ofPattern("yyyyMMdd")); + + List last12Months = new ArrayList<>(); + for(int i=0; i<12; i++){ + String yearMonth = curDate.minusMonths(i).format(DateTimeFormatter.ofPattern("yyyyMM")); + last12Months.add(yearMonth); + } + + return last12Months; + } public List calculatePeriodDates(String Date, String periodType) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); @@ -95,4 +107,9 @@ public LocalDate curUTCLocalDate(){ return ZonedDateTime.now(ZoneId.of("UTC")).toLocalDate(); } + public LocalDate curLocalDate(){ + return ZonedDateTime.now().toLocalDate(); + } + + } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/common/service/ExceptionService.java b/BackEnd/src/main/java/com/mcmp/costbe/common/service/ExceptionService.java new file mode 100644 index 0000000..58f0948 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/common/service/ExceptionService.java @@ -0,0 +1,15 @@ +package com.mcmp.costbe.common.service; + +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.stereotype.Service; + +@Service +public class ExceptionService { + + public boolean isTableNotFound(BadSqlGrammarException ex){ + String msg = ex.getCause().getMessage(); + + return msg != null && msg.contains("Table") && (msg.contains("doesn't exist") || msg.contains("not found") || + msg.contains("does not exist") || msg.contains("ORA-00942")); + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/BillingInvoiceController.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/BillingInvoiceController.java index abd7c9f..f46ee63 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/BillingInvoiceController.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/BillingInvoiceController.java @@ -38,7 +38,7 @@ public class BillingInvoiceController { public ResponseEntity getBillingBaseInfo(@RequestBody BillingInvoiceBaseInfoReqModel req) throws IOException { ResultModel result = new ResultModel(); try { - List data = billingInvoiceService.getCurMonthBill(req); + List data = billingInvoiceService.getBaseInfo(req); result.setData(data); } catch (Exception e){ e.printStackTrace(); diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/InvoiceController.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/InvoiceController.java index 9300bce..86c0eef 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/InvoiceController.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/InvoiceController.java @@ -31,7 +31,7 @@ public class InvoiceController { private InvoiceService invoiceService; @PostMapping(path = "/getSummary") - @Operation(summary = "빌링 인보이스 날짜별 조회", description = "CSP별 빌링 인보이스 비용을 날짜별로 확인한다.") + @Operation(summary = "빌링 인보이스 월별 조회", description = "CSP별 빌링 인보이스 비용을 월별로 확인한다.") @ApiResponses(value={ @ApiResponse(responseCode = "200", description = "성공", content = {@Content(schema = @Schema(implementation = SummaryResModel.class))}), @@ -40,25 +40,15 @@ public class InvoiceController { public ResponseEntity getSummary(@RequestBody SummaryReqModel req){ ResultModel result = new ResultModel(); try{ - List calPeriodDated = dateCalculator.calculatePeriodDates(req.getToday(), req.getSelectedPeriod()); - + req.setPrevMonths(dateCalculator.getLast12Months(req.getToday())); SummaryResModel resultData = new SummaryResModel(); resultData.setCurYear(req.getToday().substring(0, 4)); resultData.setCurMonth(req.getToday().substring(4, 6)); - resultData.setCurDay(req.getToday().substring(6, 8)); - resultData.setDates(dateCalculator.LocalDateTimeToString(calPeriodDated)); + resultData.setYearMonths(req.getPrevMonths()); resultData.setSelectedCsps(req.getSelectedCsps()); resultData.setSelectedProjects(req.getSelectedProjects()); - resultData.setSelectedPeriod(req.getSelectedPeriod()); - List summaryBillResult = new ArrayList<>(); - summaryBillResult.add(invoiceService.getAWSSummary(req, calPeriodDated)); - summaryBillResult.add(invoiceService.getGCPSummary()); - summaryBillResult.add(invoiceService.getAzureSummary()); - summaryBillResult.add(invoiceService.getNcpSummary()); - SummaryBillItemModel totalSummary = invoiceService.getTotalSummary(summaryBillResult); - summaryBillResult.add(totalSummary); - + List summaryBillResult = invoiceService.getInvoiceSummaryBill(req); resultData.setSummaryBill(summaryBillResult); result.setData(resultData); } catch (Exception e){ diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/dao/InvoiceDao.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/dao/InvoiceDao.java index 3618ef0..8e8d574 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/dao/InvoiceDao.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/dao/InvoiceDao.java @@ -2,6 +2,7 @@ import com.mcmp.costbe.invoice.model.InvoiceItemModel; import com.mcmp.costbe.invoice.model.InvoiceReqModel; +import com.mcmp.costbe.invoice.model.SummaryBillItemModel; import com.mcmp.costbe.invoice.model.SummaryReqModel; import com.mcmp.costbe.usage.model.bill.BillingWidgetModel; import com.mcmp.costbe.usage.model.bill.BillingWidgetReqModel; @@ -16,8 +17,8 @@ public class InvoiceDao { @Resource(name="sqlSessionTemplateBill") private SqlSessionTemplate sqlSession; - public Double getSummary(SummaryReqModel req){ - return sqlSession.selectOne("invoice.getAWSSummaryBill", req); + public List getSummaryBill(SummaryReqModel req){ + return sqlSession.selectList("invoice.getSummaryBill", req); } public List getAWSInvoice(InvoiceReqModel req){ diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/BillingInvoiceBaseInfoReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/BillingInvoiceBaseInfoReqModel.java index dde91b6..cc4f9a1 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/BillingInvoiceBaseInfoReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/BillingInvoiceBaseInfoReqModel.java @@ -1,5 +1,6 @@ package com.mcmp.costbe.invoice.model; +import com.mcmp.costbe.usage.model.bill.YearMonthModel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -8,7 +9,7 @@ @Data @Schema(description = "빌링 인보이스 요약 조회 요청 모델") -public class BillingInvoiceBaseInfoReqModel { +public class BillingInvoiceBaseInfoReqModel extends YearMonthModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @Schema(required = false) @@ -17,7 +18,7 @@ public class BillingInvoiceBaseInfoReqModel { private LocalDateTime curMonthEndDate; @Schema(description = "CSP", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(description = "프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/InvoiceReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/InvoiceReqModel.java index 6365bde..35e80a1 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/InvoiceReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/InvoiceReqModel.java @@ -1,5 +1,6 @@ package com.mcmp.costbe.invoice.model; +import com.mcmp.costbe.usage.model.bill.YearMonthModel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -8,14 +9,14 @@ @Data @Schema(description = "이번달 빌링 인보이스 조회 요청 모델") -public class InvoiceReqModel { +public class InvoiceReqModel extends YearMonthModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @Schema(description = "프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; @Schema(description = "CSP", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(required = false) diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemModel.java index 88efe26..f026604 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemModel.java @@ -7,9 +7,11 @@ @Data public class SummaryBillItemModel { - @Schema(description = "CSP", example = "AWS") + private String csp; + @Schema(description = "연도월", example = "202406") + private String yearMonth; @Schema(description = "비용") - private List bill; + private Double bill; } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemsModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemsModel.java new file mode 100644 index 0000000..0b41a82 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryBillItemsModel.java @@ -0,0 +1,20 @@ +package com.mcmp.costbe.invoice.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +public class SummaryBillItemsModel { + @Schema(description = "CSP", example = "AWS") + private String csp; + @Schema(description = "해당 CSP에 대한 비용 월별 목록") + private List monthlyBill; + + public SummaryBillItemsModel(String csp, List monthlyBill){ + this.csp = csp; + this.monthlyBill = monthlyBill; + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryReqModel.java index 3026b20..e6144fd 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryReqModel.java @@ -1,5 +1,6 @@ package com.mcmp.costbe.invoice.model; +import com.mcmp.costbe.common.model.PrevMonthsModel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.apache.tomcat.jni.Local; @@ -9,17 +10,15 @@ @Data @Schema(description = "빌링 인보이스 날짜별 조회 요청 모델") -public class SummaryReqModel { +public class SummaryReqModel extends PrevMonthsModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @Schema(description = "프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; - @Schema(description = "CSP", example = "[\"AWS\"]", required = true) + @Schema(description = "CSP", example = "[\"AWS\"]", required = false) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; - @Schema(description = "기간 선택 (7days, 3months, 30days)", example = "7days", required = true) - private String selectedPeriod; @Schema(required = false) private List summaryPeriod; diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryResModel.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryResModel.java index 4db39bb..fcf7e5c 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryResModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/model/SummaryResModel.java @@ -12,14 +12,10 @@ public class SummaryResModel { private String curMonth; @Schema(description = "년도", example = "2024") private String curYear; - @Schema(description = "일", example = "20") - private String curDay; @Schema(description = "csp 별 비용") - private List summaryBill; - @Schema(description = "선택 날짜 목록", example = "[\"2024-06-13\",\"2024-06-14\",\"2024-06-15\",\"2024-06-16\",\"2024-06-17\",\"2024-06-18\",\"2024-06-19\"]") - private List dates; - @Schema(description = "선택 기간", example = "7days") - private String selectedPeriod; + private List summaryBill; + @Schema(description = "지난 12달 목록", example = "[\"202406\",\"202405\",\"202404\", ... ,\"202307\"]") + private List yearMonths; @Schema(description = "선택 프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]") private List selectedProjects; @Schema(description = "선택 CSP", example = "[\"AWS\"]") diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/BillingInvoiceService.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/BillingInvoiceService.java index 05fc474..602f214 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/BillingInvoiceService.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/BillingInvoiceService.java @@ -2,16 +2,20 @@ import com.mcmp.costbe.common.model.DateRangeModel; import com.mcmp.costbe.common.service.DateCalculator; +import com.mcmp.costbe.common.service.ExceptionService; import com.mcmp.costbe.invoice.dao.BillingInvoiceDao; import com.mcmp.costbe.invoice.model.BillingInvoiceBaseInfoModel; import com.mcmp.costbe.invoice.model.BillingInvoiceBaseInfoReqModel; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service +@Slf4j public class BillingInvoiceService { @Autowired @@ -20,38 +24,52 @@ public class BillingInvoiceService { @Autowired private DateCalculator dateCalculator; - public List getCurMonthBill(BillingInvoiceBaseInfoReqModel req){ + @Autowired + private ExceptionService exceptionService; + + public List getBaseInfo(BillingInvoiceBaseInfoReqModel req){ String curMonth = dateCalculator.curMonthDate(req.getToday()); DateRangeModel curMonthRange = dateCalculator.dateRangeCalculator(curMonth); req.setCurMonthStartDate(curMonthRange.getStartDate()); req.setCurMonthEndDate(curMonthRange.getEndDate()); + req.setYear_month(req.getToday().substring(0, 6)); - BillingInvoiceBaseInfoModel aws_result = billingInvoiceDao.getCurMonthBill(req); - aws_result.setCsp("AWS"); - aws_result.setColorClass("bg-google"); + List result = new ArrayList<>(); + try { + BillingInvoiceBaseInfoModel aws_result = billingInvoiceDao.getCurMonthBill(req); + aws_result.setCsp("AWS"); + aws_result.setColorClass("bg-google"); - BillingInvoiceBaseInfoModel gcp_result = new BillingInvoiceBaseInfoModel(); - gcp_result.setCsp("GCP"); - gcp_result.setCost(0); - gcp_result.setColorClass("bg-facebook"); + BillingInvoiceBaseInfoModel gcp_result = new BillingInvoiceBaseInfoModel(); + gcp_result.setCsp("GCP"); + gcp_result.setCost(0); + gcp_result.setColorClass("bg-facebook"); - BillingInvoiceBaseInfoModel azure_result = new BillingInvoiceBaseInfoModel(); - azure_result.setCsp("AZURE"); - azure_result.setCost(0); - azure_result.setColorClass("bg-red"); + BillingInvoiceBaseInfoModel azure_result = new BillingInvoiceBaseInfoModel(); + azure_result.setCsp("AZURE"); + azure_result.setCost(0); + azure_result.setColorClass("bg-red"); - BillingInvoiceBaseInfoModel ncp_result = new BillingInvoiceBaseInfoModel(); - ncp_result.setCsp("NCP"); - ncp_result.setCost(0); - ncp_result.setColorClass("bg-green"); + BillingInvoiceBaseInfoModel ncp_result = new BillingInvoiceBaseInfoModel(); + ncp_result.setCsp("NCP"); + ncp_result.setCost(0); + ncp_result.setColorClass("bg-green"); - List result = new ArrayList<>(); - result.add(aws_result); - result.add(gcp_result); - result.add(azure_result); - result.add(ncp_result); + result.add(aws_result); + result.add(gcp_result); + result.add(azure_result); + result.add(ncp_result); + + } catch (BadSqlGrammarException ex){ + if(exceptionService.isTableNotFound(ex)){ + log.warn("[BaseInfo Widget Log] NotFoundTable : {}", ex.getMessage()); + } else { + ex.printStackTrace(); + throw new RuntimeException(); + } + } return result; } } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/InvoiceService.java b/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/InvoiceService.java index 15e5255..f0cd565 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/InvoiceService.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/invoice/service/InvoiceService.java @@ -2,15 +2,19 @@ import com.mcmp.costbe.common.model.DateRangeModel; import com.mcmp.costbe.common.service.DateCalculator; +import com.mcmp.costbe.common.service.ExceptionService; import com.mcmp.costbe.invoice.dao.InvoiceDao; import com.mcmp.costbe.invoice.model.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @Slf4j @@ -22,73 +26,19 @@ public class InvoiceService { @Autowired private InvoiceDao invoiceDao; - public SummaryBillItemModel getAWSSummary(SummaryReqModel req, List calPeriodDated ){ - - - req.setSummaryPeriod(calPeriodDated); - - List bill = new ArrayList<>(); - for(int i = 0; i bill = List.of(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - - SummaryBillItemModel summaryGCPItem = new SummaryBillItemModel(); - summaryGCPItem.setBill(bill); - summaryGCPItem.setCsp("GCP"); - - - return summaryGCPItem; - } - - public SummaryBillItemModel getAzureSummary(){ - List bill = List.of(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - - SummaryBillItemModel summaryAzureItem = new SummaryBillItemModel(); - summaryAzureItem.setBill(bill); - summaryAzureItem.setCsp("AZURE"); - - - return summaryAzureItem; - } - - public SummaryBillItemModel getNcpSummary(){ - List bill = List.of(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - - SummaryBillItemModel summaryNcpItem = new SummaryBillItemModel(); - summaryNcpItem.setBill(bill); - summaryNcpItem.setCsp("NCP"); - - - return summaryNcpItem; - } - - public SummaryBillItemModel getTotalSummary(List summaryBill){ - SummaryBillItemModel summaryTotalItem = new SummaryBillItemModel(); - List totalBill = new ArrayList<>(); + @Autowired + private ExceptionService exceptionService; - for(int i=0; i getInvoiceSummaryBill(SummaryReqModel req){ + List summary = invoiceDao.getSummaryBill(req); + Map> groupedByCsp = summary.stream() + .collect(Collectors.groupingBy(SummaryBillItemModel::getCsp)); - summaryTotalItem.setBill(totalBill); - summaryTotalItem.setCsp("Total"); + List result = groupedByCsp.entrySet().stream() + .map(item -> new SummaryBillItemsModel(item.getKey(), item.getValue())) + .collect(Collectors.toList()); - return summaryTotalItem; + return result; } public List getAWSInvoice(InvoiceReqModel req){ @@ -96,6 +46,19 @@ public List getAWSInvoice(InvoiceReqModel req){ req.setCurMonthStartDate(dateRange.getStartDate()); req.setCurMonthEndDate(dateRange.getEndDate()); - return invoiceDao.getAWSInvoice(req); + req.setYear_month(req.getToday().substring(0, 6)); + + try { + return invoiceDao.getAWSInvoice(req); + } catch (BadSqlGrammarException ex){ + if(exceptionService.isTableNotFound(ex)){ + log.warn("[Invoice Widget Log] NotFoundTable : {}", ex.getMessage()); + } else { + ex.printStackTrace(); + throw new RuntimeException(); + } + } + + return null; } } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/OptiController.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/OptiController.java index 1082826..81bb4dc 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/opti/OptiController.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/OptiController.java @@ -3,10 +3,7 @@ import com.mcmp.costbe.common.model.ResultModel; import com.mcmp.costbe.invoice.model.BillingInvoiceBaseInfoModel; import com.mcmp.costbe.invoice.model.BillingInvoiceBaseInfoReqModel; -import com.mcmp.costbe.opti.model.UnusedQueryParamModel; -import com.mcmp.costbe.opti.model.UnusedQueryRstModel; -import com.mcmp.costbe.opti.model.UnusedReqModel; -import com.mcmp.costbe.opti.model.UnusedRstModel; +import com.mcmp.costbe.opti.model.*; import com.mcmp.costbe.opti.service.OptiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -33,14 +30,14 @@ public class OptiController { @Autowired private OptiService optiService; - @PostMapping(path = "/unusedRec") - @Operation(summary = "미사용 자원(추천) 조회", description = "이번달 CSP별 요약된 빌링 인보이스를 확인한다.") + @PostMapping(path = "/unusedRcmd") + @Operation(summary = "미사용 자원(추천) 조회", description = "최근 24시간동안 과금이 발생한 리소스에 대하여 미사용 자원을 추천한다.") @ApiResponses(value={ @ApiResponse(responseCode = "200", description = "성공", - content = {@Content(array = @ArraySchema(schema = @Schema(implementation = UnusedQueryRstModel.class)))}), + content = {@Content(schema = @Schema(implementation = UnusedQueryRstModel.class))}), @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) }) - public ResponseEntity getUnusedRec(@RequestBody UnusedReqModel req){ + public ResponseEntity getUnusedRcmd(@RequestBody UnusedReqModel req){ ResultModel result = new ResultModel(); try { UnusedRstModel data = optiService.getOptiUnused(req); @@ -52,4 +49,42 @@ public ResponseEntity getUnusedRec(@RequestBody UnusedReqModel req){ return ResponseEntity.ok(result); } + @PostMapping(path = "/abnormalRcmd") + @Operation(summary = "이상 비용 조회", description = "최근 24시간동안 과금이 발생한 서비스들의 이상 비용 여부를 확인한다.") + @ApiResponses(value={ + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = AbnormalRstModel.class))}), + @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) + }) + public ResponseEntity getAbrnormalRcmd(@RequestBody AbnormalReqModel req){ + ResultModel result = new ResultModel(); + try { + AbnormalRstModel data = optiService.getOptiAbrnomal(req); + result.setData(data); + } catch (Exception e){ + e.printStackTrace(); + result.setError(500, "Failed to get Abnormal Service Info"); + } + return ResponseEntity.ok(result); + } + + @PostMapping(path = "/instOptiSizeRcmd") + @Operation(summary = "인스턴스 사이즈 추천 조회", description = "사용중인 인스턴스의 추천 사이즈를 확인한다.") + @ApiResponses(value={ + @ApiResponse(responseCode = "200", description = "성공", + content = {@Content(schema = @Schema(implementation = InstOptiSizeRstModel.class))}), + @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) + }) + public ResponseEntity getInstOptiSizeRcmd(@RequestBody InstOptiSizeReqModel req){ + ResultModel result = new ResultModel(); + try { + InstOptiSizeRstModel data = optiService.getInstOptiSizeRcmd(req); + result.setData(data); + } catch (Exception e){ + e.printStackTrace(); + result.setError(500, "Failed to get OptiSize Recommend Service Info"); + } + return ResponseEntity.ok(result); + } + } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/dao/OptiDao.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/dao/OptiDao.java index 65f6cd1..29a991d 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/opti/dao/OptiDao.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/dao/OptiDao.java @@ -1,7 +1,6 @@ package com.mcmp.costbe.opti.dao; -import com.mcmp.costbe.opti.model.UnusedQueryParamModel; -import com.mcmp.costbe.opti.model.UnusedQueryRstModel; +import com.mcmp.costbe.opti.model.*; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.stereotype.Repository; @@ -18,4 +17,12 @@ public List getOptiUnused(UnusedQueryParamModel param){ return sqlSession.selectList("opti.getOptiUnused", param); } + public List getOptiAbnormal(AbnormalReqModel param){ + return sqlSession.selectList("opti.getOptiAbnormal", param); + } + + public List getInstOptiSize(InstOptiSizeReqModel param){ + return sqlSession.selectList("opti.getInstOptiSize", param); + } + } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnoramlItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnoramlItemModel.java new file mode 100644 index 0000000..8bdabab --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnoramlItemModel.java @@ -0,0 +1,26 @@ +package com.mcmp.costbe.opti.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class AbnoramlItemModel { + @Schema(description = "서비스 코드", example = "AmazonEC2") + private String product_cd; + + @Schema(description = "이상비용 위험도", example = "Alarm") + private String abnormal_rating; + + @Schema(description = "지난달 사용 차이률", example = "200%") + private double percentage_point; + + @Schema(description = "오늘 과금 비용(USD)", example = "20.0") + private double standard_cost; + + @Schema(description = "지난달 사용 과금 비용 평균(USD)", example = "10.0") + private double subject_cost; + + @Schema(description = "csp 종류", example = "AWS") + private String csp_type; + +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalReqModel.java new file mode 100644 index 0000000..cfc8db8 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalReqModel.java @@ -0,0 +1,23 @@ +package com.mcmp.costbe.opti.model; + +import com.mcmp.costbe.common.model.LocalDateModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "이상 비용 조회 요청 모델") +public class AbnormalReqModel extends LocalDateModel { + @Schema(description = "오늘 날짜", example = "20240608", required = true) + private String today; + + @Schema(description = "선택 워크스페이스", example = "workspaceCode1", required = false, deprecated = true) + private String selectedWorkspace; + + @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]", required = true) + private List selectedProjects; + + @Schema(description = "선택 csp", example = "[\"AWS\"]", required = false) + private List selectedCsps; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalRstModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalRstModel.java new file mode 100644 index 0000000..006e1ae --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/AbnormalRstModel.java @@ -0,0 +1,26 @@ +package com.mcmp.costbe.opti.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +public class AbnormalRstModel { + + @Schema(description = "오늘 날짜", example = "20240608") + private String today; + + @Schema(description = "이상비용 목록") + private List abnoramlItems; + + + @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]") + private List selectedProjects; + + @Schema(description = "선택 csp", example = "[\"AWS\"]") + private List selectedCsps; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeItemModel.java new file mode 100644 index 0000000..acfb207 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeItemModel.java @@ -0,0 +1,24 @@ +package com.mcmp.costbe.opti.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class InstOptiSizeItemModel { + @Schema(description = "리소스 ID", example = "i-02eeeeeeee1234") + private String resource_id; + @Schema(description = "csp 종류", example = "AWS") + private String csp_type; + @Schema(description = "계정", example = "111111111111") + private String account; + @Schema(description = "기존타입", example = "t2.small") + private String origin_type; + @Schema(description = "추천타입", example = "t2.micro") + private String rcmd_type; + @Schema(description = "추천 방향", example = "Down") + private String plan_type; + @Schema(description = "기존타입 비용", example = "0.0288") + private double origin_usd; + @Schema(description = "추천타입 비용", example = "0.0144") + private double rcmd_usd; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeReqModel.java new file mode 100644 index 0000000..c25a0e1 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeReqModel.java @@ -0,0 +1,23 @@ +package com.mcmp.costbe.opti.model; + +import com.mcmp.costbe.common.model.LocalDateModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "인스턴스 사이즈 추천 조회") +public class InstOptiSizeReqModel extends LocalDateModel { + @Schema(description = "오늘 날짜", example = "20240608", required = true) + private String today; + + @Schema(description = "선택 워크스페이스", example = "workspaceCode1", required = false, deprecated = true) + private String selectedWorkspace; + + @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]", required = true) + private List selectedProjects; + + @Schema(description = "선택 csp", example = "[\"AWS\"]", required = false) + private List selectedCsps; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeRstModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeRstModel.java new file mode 100644 index 0000000..570a731 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/InstOptiSizeRstModel.java @@ -0,0 +1,24 @@ +package com.mcmp.costbe.opti.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class InstOptiSizeRstModel { + @Schema(description = "오늘 날짜", example = "20240608") + private String today; + + @Schema(description = "인스턴스 추천 사이즈 목록") + private List optiSizeItems; + + + @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]") + private List selectedProjects; + + @Schema(description = "선택 csp", example = "[\"AWS\"]") + private List selectedCsps; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedQueryParamModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedQueryParamModel.java index a55ed05..3ac5080 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedQueryParamModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedQueryParamModel.java @@ -7,4 +7,5 @@ @Data public class UnusedQueryParamModel extends UnusedReqModel { private LocalDate curDate; + private String lastYearMonth; } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedReqModel.java index 915c78a..c4b2497 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/model/UnusedReqModel.java @@ -9,7 +9,7 @@ public class UnusedReqModel { @Schema(description = "선택 csp", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "선택 워크스페이스", example = "workspaceCode1") + @Schema(description = "선택 워크스페이스", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(description = "선택 프로젝트", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; diff --git a/BackEnd/src/main/java/com/mcmp/costbe/opti/service/OptiService.java b/BackEnd/src/main/java/com/mcmp/costbe/opti/service/OptiService.java index 3218e7f..c6f012e 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/opti/service/OptiService.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/opti/service/OptiService.java @@ -2,15 +2,13 @@ import com.mcmp.costbe.common.service.DateCalculator; import com.mcmp.costbe.opti.dao.OptiDao; -import com.mcmp.costbe.opti.model.UnusedQueryParamModel; -import com.mcmp.costbe.opti.model.UnusedQueryRstModel; -import com.mcmp.costbe.opti.model.UnusedReqModel; -import com.mcmp.costbe.opti.model.UnusedRstModel; +import com.mcmp.costbe.opti.model.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; @Service @@ -25,19 +23,19 @@ public class OptiService { public UnusedRstModel getOptiUnused(UnusedReqModel req){ try { UnusedRstModel rst = new UnusedRstModel(); - LocalDate curDate = dateCalculator.curUTCLocalDate(); + LocalDate curDate = dateCalculator.curLocalDate(); + LocalDate lastYM = curDate.minusMonths(1); UnusedQueryParamModel queryParam = new UnusedQueryParamModel(); queryParam.setCurDate(curDate); + queryParam.setLastYearMonth(lastYM.format(DateTimeFormatter.ofPattern("yyyyMM"))); queryParam.setSelectedCsps(req.getSelectedCsps()); queryParam.setSelectedProjects(req.getSelectedProjects()); - queryParam.setSelectedWorkspace(req.getSelectedWorkspace()); List queryRst = optiDao.getOptiUnused(queryParam); rst.setCurDate(curDate); rst.setSelectedCsps(req.getSelectedCsps()); - rst.setSelectedWorkspace(req.getSelectedWorkspace()); rst.setSelectedProjects(req.getSelectedProjects()); rst.setUnusedRec(queryRst); @@ -48,4 +46,44 @@ public UnusedRstModel getOptiUnused(UnusedReqModel req){ throw new RuntimeException(); } } + + public AbnormalRstModel getOptiAbrnomal(AbnormalReqModel req){ + try{ + LocalDate date = LocalDate.now(); + req.setDate(date); + + AbnormalRstModel result = AbnormalRstModel.builder() + .today(date.toString()) + .abnoramlItems(optiDao.getOptiAbnormal(req)) + .selectedProjects(req.getSelectedProjects()) + .selectedCsps(req.getSelectedCsps()) + .build(); + + return result; + + } catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + } + } + + public InstOptiSizeRstModel getInstOptiSizeRcmd(InstOptiSizeReqModel req){ + try{ + LocalDate date = LocalDate.now(); + req.setDate(date); + + InstOptiSizeRstModel result = InstOptiSizeRstModel.builder() + .today(date.toString()) + .optiSizeItems(optiDao.getInstOptiSize(req)) + .selectedProjects(req.getSelectedProjects()) + .selectedCsps(req.getSelectedCsps()) + .build(); + + return result; + + } catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + } + } } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/TumblebugController.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/TumblebugController.java new file mode 100644 index 0000000..d75b253 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/TumblebugController.java @@ -0,0 +1,30 @@ +package com.mcmp.costbe.tumblebugMeta; + +import com.mcmp.costbe.common.model.ResultModel; +import com.mcmp.costbe.tumblebugMeta.service.VMMetaService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping(path = "/api/v2") +public class TumblebugController { + + @Autowired + private VMMetaService vmMetaService; + + @GetMapping(path = "/updateRscMeta") + public ResponseEntity updateTBBRscMeta(){ + ResultModel result = new ResultModel(); + try{ + vmMetaService.getTBBResourceMetaInfo(); + } catch (Exception e){ + e.printStackTrace(); + result.setError(500, "Fail to Update (Tumblebug) ResourceMeta"); + } + return ResponseEntity.ok(result); + } +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/dao/TBBDao.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/dao/TBBDao.java new file mode 100644 index 0000000..1e90608 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/dao/TBBDao.java @@ -0,0 +1,21 @@ +package com.mcmp.costbe.tumblebugMeta.dao; + +import com.mcmp.costbe.tumblebugMeta.model.ResourcegroupMetaModel; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; +import java.util.List; + +@Repository +public class TBBDao { + + @Resource(name="sqlSessionTemplateBill") + private SqlSessionTemplate sqlSession; + + public void insertTBBServicegroupMeta(List rscMetas){ + sqlSession.insert("tbb.insertTBBServicegroupMeta", rscMetas); + } + + +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ResourcegroupMetaModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ResourcegroupMetaModel.java new file mode 100644 index 0000000..dc8a5bd --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ResourcegroupMetaModel.java @@ -0,0 +1,22 @@ +package com.mcmp.costbe.tumblebugMeta.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ResourcegroupMetaModel { + private String cspType; + private String cspAccount; + private String cspInstanceid; + private String serviceCd; + private String serviceNm; + private String serviceUid; + private String vmId; + private String vmUid; + private String vmNm; + private String mciId; + private String mciUid; + private String mciNm; + private String instanceRunningStatus; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIItemModel.java new file mode 100644 index 0000000..611d0c6 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIItemModel.java @@ -0,0 +1,29 @@ +package com.mcmp.costbe.tumblebugMeta.model.mci; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TBBMCIItemModel { + private String configureCloudAdaptiveNetwork; + private String description; + private String id; + private String installMonAgent; + private Map label; + private String name; + private List newVmList; + private String placementAlgo; + private String resourceType; + private String status; + private Object statusCount; + private String systemLabel; + private String systemMessage; + private String targetAction; + private String targetStatus; + private String uid; + private List vm; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIModel.java new file mode 100644 index 0000000..a6be090 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TBBMCIModel.java @@ -0,0 +1,10 @@ +package com.mcmp.costbe.tumblebugMeta.model.mci; + +import lombok.Data; + +import java.util.List; + +@Data +public class TBBMCIModel { + private List mci; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TbVmInfoModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TbVmInfoModel.java new file mode 100644 index 0000000..3d6c63c --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/mci/TbVmInfoModel.java @@ -0,0 +1,51 @@ +package com.mcmp.costbe.tumblebugMeta.model.mci; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TbVmInfoModel { + private Map addtionalDetails; + private Object connectionConfig; + private String connectionName; + private String createdTime; + private String cspImageName; + private String cspResourceId; + private String cspResourceName; + private String cspSpecName; + private String cspSshKeyId; + private String cspSubnetId; + private String cspVNetId; + private List dataDiskIds; + private String description; + private String id; + private String imageId; + private Map label; + private Object location; + private String monAgentStatus; + private String name; + private String networkAgentStatus; + private String networkInterface; + private String privateDNS; + private String privateIP; + private String publicDNS; + private String publicIP; + private Object region; + private String resourceType; + private String specId; + private String sshKeyId; + private String status; + private String subGroupId; + private String subnetId; + private String targetAction; + private String targetStatus; + private String uid; + private String vNetId; + private String vmUserName; + private String vmUserPassword; + +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSItemModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSItemModel.java new file mode 100644 index 0000000..29dcdf5 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSItemModel.java @@ -0,0 +1,12 @@ +package com.mcmp.costbe.tumblebugMeta.model.ns; + +import lombok.Data; + +@Data +public class TBBNSItemModel { + + private String id; + private String name; + private String resourceType; + private String uid; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSModel.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSModel.java new file mode 100644 index 0000000..37fa090 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/model/ns/TBBNSModel.java @@ -0,0 +1,13 @@ +package com.mcmp.costbe.tumblebugMeta.model.ns; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TBBNSModel { + private List ns; + private List output; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/service/VMMetaService.java b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/service/VMMetaService.java new file mode 100644 index 0000000..5a56d48 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/tumblebugMeta/service/VMMetaService.java @@ -0,0 +1,175 @@ +package com.mcmp.costbe.tumblebugMeta.service; + +import com.mcmp.costbe.tumblebugMeta.dao.TBBDao; +import com.mcmp.costbe.tumblebugMeta.model.ResourcegroupMetaModel; +import com.mcmp.costbe.tumblebugMeta.model.mci.TBBMCIItemModel; +import com.mcmp.costbe.tumblebugMeta.model.mci.TBBMCIModel; +import com.mcmp.costbe.tumblebugMeta.model.mci.TbVmInfoModel; +import com.mcmp.costbe.tumblebugMeta.model.ns.TBBNSItemModel; +import com.mcmp.costbe.tumblebugMeta.model.ns.TBBNSModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Service +@Slf4j +public class VMMetaService { + + @Value("${tumblebug.url}") + public String tumblebugUrl; + + @Value("${tumblebug.username}") + public String tumblebugUserNM; + + @Value("${tumblebug.password}") + public String tumblebugPW; + + @Autowired + private TBBDao tbbDao; + + + public List getTbbNS(){ + + String apiUrl = String.format("%s/ns", tumblebugUrl); + RestTemplate restTemplate = new RestTemplate(); + + String auth = tumblebugUserNM + ":" + tumblebugPW; + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); + String authHeader = "Basic " + new String(encodedAuth); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set("Authorization", authHeader); + HttpEntity httpEntity = new HttpEntity<>(httpHeaders); + + try{ + ResponseEntity responseEntity = restTemplate.exchange(apiUrl, HttpMethod.GET, httpEntity, TBBNSModel.class); + TBBNSModel response = responseEntity.getBody(); + + if(response.getNs() != null && !response.getNs().isEmpty()){ + return response.getNs(); + }else{ + log.warn("TUMBLEBUG META - NS IS EMPTY => response : {}", response); + return new ArrayList<>(); + } + } catch (HttpClientErrorException | HttpServerErrorException clientError) { + HttpStatus statusCode = clientError.getStatusCode(); + log.error("FAIL TO GET TUMBLEBUG META - NS : " + statusCode); + throw new RuntimeException(); + } catch (Exception e){ + log.error("FAIL TO GET TUMBLEBUG META - NS : " +e.getMessage()); + throw new RuntimeException(); + } + } + + public List getTBBMCI(TBBNSItemModel item){ + + if(item != null){ + String apiUrl = String.format("%s/ns/%s/mci", tumblebugUrl, item.getId()); + RestTemplate restTemplate = new RestTemplate(); + + String auth = tumblebugUserNM + ":" + tumblebugPW; + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); + String authHeader = "Basic " + new String(encodedAuth); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set("Authorization", authHeader); + HttpEntity httpEntity = new HttpEntity<>(httpHeaders); + + try{ + ResponseEntity responseEntity = restTemplate.exchange(apiUrl, HttpMethod.GET, httpEntity, TBBMCIModel.class); + TBBMCIModel response = responseEntity.getBody(); + + if(response.getMci() != null && !response.getMci().isEmpty()){ + return response.getMci(); + }else{ + log.warn("TUMBLEBUG META - MCI => MCI IS EMPTY => response : {}", response); + return new ArrayList<>(); + } + } catch (HttpClientErrorException | HttpServerErrorException clientError) { + HttpStatus statusCode = clientError.getStatusCode(); + log.error("FAIL TO GET TUMBLEBUG META - MCI => NS ID : {}, error code : {}", item.getId(), statusCode); + throw new RuntimeException(); + } catch (Exception e){ + log.error("FAIL TO GET TUMBLEBUG META - MCI => NS ID : {}", item.getId()); + throw new RuntimeException(); + } + + } else { + log.error("[ERROR] : GET TUMBLEBUG META - MCI => NS IS EMPTY"); + return new ArrayList<>(); + } + } + + public void getTBBResourceMetaInfo() throws InterruptedException { + List nsList = getTbbNS(); + + for(TBBNSItemModel ns : nsList){ + + Thread.sleep(2000); + List mciList = getTBBMCI(ns); + + for(TBBMCIItemModel mci : mciList){ + if("mci".equals(mci.getResourceType())){ + try{ + List resourcegroupMetaList = new ArrayList<>(); + + List vmList = mci.getVm(); + for(TbVmInfoModel vm : vmList){ + String vmStatus; + if(!vm.getStatus().isEmpty()){ + vmStatus = switch (vm.getStatus()){ + case "Running" -> "Y"; + case "Failed" -> "N"; + default -> "Y"; + }; + }else { + vmStatus = "Y"; + } + + if(vm.getCspResourceId() != null){ + ResourcegroupMetaModel vmInfo = ResourcegroupMetaModel.builder() + .cspType("AWS") + .cspAccount("mcmpcostopti") + .cspInstanceid(vm.getCspResourceId()) + .serviceCd(ns.getId()) + .serviceNm(ns.getName()) + .serviceUid(ns.getUid()) + .vmId(vm.getId()) + .vmUid(vm.getUid()) + .vmNm(vm.getName()) + .mciId(mci.getId()) + .mciUid(mci.getUid()) + .mciNm(mci.getName()) + .instanceRunningStatus(vmStatus) + .build(); + + resourcegroupMetaList.add(vmInfo); + } + + } + + if(resourcegroupMetaList.size() >= 1){ + tbbDao.insertTBBServicegroupMeta(resourcegroupMetaList); + } + + } catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + } + + } + } + } + } + +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/UsageController.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/UsageController.java index 70bc5a3..5e52395 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/usage/UsageController.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/UsageController.java @@ -25,9 +25,10 @@ public class UsageController { @Autowired private UsageService usageService; + @Deprecated @GetMapping(path = "/getWorkspaces") @Tag(name = "User", description = "User API") - @Operation(summary = "워크스페이스 목록 조회", description = "워크스페이스 목록을 조회합니다.") + @Operation(summary = "워크스페이스 목록 조회", description = "워크스페이스 목록을 조회합니다.", deprecated = true) @ApiResponses(value={ @ApiResponse(responseCode = "200", description = "성공"), @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) @@ -44,9 +45,10 @@ public ResponseEntity getWorkspaces(){ return ResponseEntity.ok(result); } + @Deprecated @GetMapping(path = "/getProjects") @Tag(name = "User", description = "User API") - @Operation(summary = "프로젝트 목록 조회", description = "워크스페이스에 속한 프로젝트 목록을 조회합니다.") + @Operation(summary = "프로젝트 목록 조회", description = "워크스페이스에 속한 프로젝트 목록을 조회합니다.", deprecated = true) @ApiResponses(value={ @ApiResponse(responseCode = "200", description = "성공"), @ApiResponse(responseCode = "500", description = "서버 오류", content = {@Content(examples = {})}) @@ -76,7 +78,7 @@ public ResponseEntity getProjects( public ResponseEntity getCurMonthBill(@RequestBody BillingWidgetReqModel req) throws IOException { ResultModel result = new ResultModel(); try{ - BillingWidgetModel data = usageService.getBillingWidget(req); + BillingWidgetModel data = usageService.getBillingMonthlyWidget(req); result.setData(data); } catch (Exception e){ e.printStackTrace(); @@ -107,7 +109,7 @@ public ResponseEntity getTop5Bill(@RequestBody Top5WidgetReqModel req) throws IO @PostMapping(path = "/getBillAsset") @Tag(name = "Cost Dashboard", description = "Cost Dashboard overview API") - @Operation(summary = "이번달 리소스 사용량 및 비용 조회", description = "이번달 사용한 리소스의 unit과 비용을 확인합니다.") + @Operation(summary = "이번달 사용 서비스별 비용 조회", description = "이번달 사용한 서비스(VM, DB 등) 단위의 비용을 확인합니다.") @ApiResponses(value={ @ApiResponse(responseCode = "200", description = "성공", content = {@Content(schema = @Schema(implementation = BillingAssetWidgetModel.class))}), diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingAssetReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingAssetReqModel.java index 1242737..76151b9 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingAssetReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingAssetReqModel.java @@ -8,14 +8,14 @@ @Data @Schema(description = "이번달 리소스 사용량 및 비용 조회 요청 모델") -public class BillingAssetReqModel { +public class BillingAssetReqModel extends YearMonthModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @Schema(description = "프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; @Schema(description = "CSP", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(required = false) private LocalDateTime curMonthStartDate; diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingWidgetReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingWidgetReqModel.java index 3e2bebd..01bd724 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingWidgetReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/BillingWidgetReqModel.java @@ -1,5 +1,6 @@ package com.mcmp.costbe.usage.model.bill; +import com.mcmp.costbe.common.model.PrevMonthsModel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -8,7 +9,7 @@ @Data @Schema(description = "이번달 비용 조회 요청 모델") -public class BillingWidgetReqModel { +public class BillingWidgetReqModel extends PrevMonthsModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @@ -19,7 +20,7 @@ public class BillingWidgetReqModel { @Schema(description = "CSP", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(required = false) @@ -30,4 +31,10 @@ public class BillingWidgetReqModel { private LocalDateTime prevMonthStartDate; @Schema(required = false) private LocalDateTime prevMonthEndDate; + + @Schema(required = false) + private String curYearMonth; + + @Schema(required = false) + private String prevYearMonth; } diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/Top5WidgetReqModel.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/Top5WidgetReqModel.java index 725ee17..3ef364e 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/Top5WidgetReqModel.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/Top5WidgetReqModel.java @@ -8,14 +8,14 @@ @Data @Schema(description = "이번달 상위 5개 리소스 비용 조회 요청 모델") -public class Top5WidgetReqModel { +public class Top5WidgetReqModel extends YearMonthModel { @Schema(description = "오늘 날짜", example = "20240620", required = true) private String today; @Schema(description = "프로젝트 코드", example = "[\"projectCode1\", \"projectCode2\"]", required = true) private List selectedProjects; @Schema(description = "CSP", example = "[\"AWS\"]", required = true) private List selectedCsps; - @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = true) + @Schema(description = "워크스페이스 코드", example = "workspaceCode1", required = false, deprecated = true) private String selectedWorkspace; @Schema(required = false) diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/YearMonthModel.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/YearMonthModel.java new file mode 100644 index 0000000..6cdd014 --- /dev/null +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/model/bill/YearMonthModel.java @@ -0,0 +1,8 @@ +package com.mcmp.costbe.usage.model.bill; + +import lombok.Data; + +@Data +public class YearMonthModel { + private String year_month; +} diff --git a/BackEnd/src/main/java/com/mcmp/costbe/usage/service/UsageService.java b/BackEnd/src/main/java/com/mcmp/costbe/usage/service/UsageService.java index 5668637..2a93c92 100644 --- a/BackEnd/src/main/java/com/mcmp/costbe/usage/service/UsageService.java +++ b/BackEnd/src/main/java/com/mcmp/costbe/usage/service/UsageService.java @@ -2,6 +2,7 @@ import com.mcmp.costbe.common.model.DateRangeModel; import com.mcmp.costbe.common.service.DateCalculator; +import com.mcmp.costbe.common.service.ExceptionService; import com.mcmp.costbe.resourceMapping.aws.AWSResourceMapping; import com.mcmp.costbe.usage.dao.BillDao; import com.mcmp.costbe.usage.dao.FilterDao; @@ -11,6 +12,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -26,6 +28,9 @@ public class UsageService { @Autowired private BillDao billDao; + @Autowired + private ExceptionService exceptionService; + @Autowired private DateCalculator dateCalculator; @@ -37,15 +42,11 @@ public List getProjects(String workspaceCD){ return filterDao.getProjects(workspaceCD); } - public BillingWidgetModel getBillingWidget(BillingWidgetReqModel req){ + public BillingWidgetModel getBillingMonthlyWidget(BillingWidgetReqModel req){ String prevMonth = dateCalculator.prevMonthdate(req.getToday()); - DateRangeModel curMonthRange = dateCalculator.dateRangeCalculator(req.getToday()); - DateRangeModel prevMonthRange = dateCalculator.dateRangeCalculator(prevMonth); - - req.setCurMonthStartDate(curMonthRange.getStartDate()); - req.setCurMonthEndDate(curMonthRange.getEndDate()); - req.setPrevMonthStartDate(prevMonthRange.getStartDate()); - req.setPrevMonthEndDate(prevMonthRange.getEndDate()); + req.setCurYearMonth(req.getToday().substring(0, 6)); + req.setPrevYearMonth(prevMonth.substring(0, 6)); + req.setPrevMonths(dateCalculator.getLast12Months(req.getToday())); BillingWidgetModel result = billDao.getCurPrevMonthBill(req); result.setCurYear(req.getToday().substring(0, 4)); @@ -65,34 +66,44 @@ public BillingWidgetModel getBillingWidget(BillingWidgetReqModel req){ return result; } - public Top5WidgetModel getTop5Bill(Top5WidgetReqModel req){ + public Top5WidgetModel getTop5Bill(Top5WidgetReqModel req) { DateRangeModel curMonthRange = dateCalculator.dateRangeCalculator(req.getToday()); req.setCurMonthStartDate(curMonthRange.getStartDate()); req.setCurMonthEndDate(curMonthRange.getEndDate()); + req.setYear_month(req.getToday().substring(0,6)); Top5WidgetModel result = new Top5WidgetModel(); - List top5bill = billDao.getTop5Bill(req); - - for(Top5BillModel item : top5bill){ - if("others".equals(item.getResourceNm())){ - item.setIsOthers(true); - }else{ - item.setIsOthers(false); - } - } + try{ + List top5bill = billDao.getTop5Bill(req); + + for(Top5BillModel item : top5bill){ + if("others".equals(item.getResourceNm())){ + item.setIsOthers(true); + }else{ + item.setIsOthers(false); + } + } + + if(top5bill.isEmpty()){ + Top5BillModel temp = new Top5BillModel(); + temp.setBill(0.0); + temp.setCsp("Null"); + temp.setIsOthers(false); + temp.setResourceNm("Null"); - System.out.println(top5bill.size()); - if(top5bill.isEmpty()){ - Top5BillModel temp = new Top5BillModel(); - temp.setBill(0.0); - temp.setCsp("Null"); - temp.setIsOthers(false); - temp.setResourceNm("Null"); + top5bill.add(temp); + } - top5bill.add(temp); + result.setTop5bill(top5bill); + } catch (BadSqlGrammarException ex){ + if(exceptionService.isTableNotFound(ex)){ + log.warn("[Top5 Widget Log]NotFoundTable : {}", ex.getMessage()); + }else { + ex.printStackTrace(); + throw new RuntimeException(); + } } - result.setTop5bill(top5bill); result.setSelectedProjects(req.getSelectedProjects()); result.setSelectedCsps(req.getSelectedCsps()); result.setCurYear(req.getToday().substring(0, 4)); @@ -107,29 +118,39 @@ public BillingAssetWidgetModel getBillAsset(BillingAssetReqModel req){ DateRangeModel curMonthRange = dateCalculator.dateRangeCalculator(req.getToday()); req.setCurMonthStartDate(curMonthRange.getStartDate()); req.setCurMonthEndDate(curMonthRange.getEndDate()); + req.setYear_month(req.getToday().substring(0, 6)); List familyCode = List.of("Virtual Machine", "Storage", "Database", "LB"); List billingAsset = new ArrayList<>(); - for(String item : familyCode){ - BillingAssetModel familyItem = new BillingAssetModel(); - List childProducts = AWSResourceMapping.getData(item); - req.setAWSChildProducts(childProducts); - - List childItem = billDao.getBillAssetChild(req); - double childsTotalBill = 0.0; - Integer childsTotalUnit = 0; - for(BillingAssetChildModel child : childItem){ - childsTotalUnit += child.getUnit(); - childsTotalBill += child.getBill(); + try{ + for(String item : familyCode){ + BillingAssetModel familyItem = new BillingAssetModel(); + List childProducts = AWSResourceMapping.getData(item); + req.setAWSChildProducts(childProducts); + + List childItem = billDao.getBillAssetChild(req); + double childsTotalBill = 0.0; + Integer childsTotalUnit = 0; + for(BillingAssetChildModel child : childItem){ + childsTotalUnit += child.getUnit(); + childsTotalBill += child.getBill(); + } + + familyItem.setChildProductCode(childItem); + familyItem.setTotalCost(childsTotalBill); + familyItem.setTotalUnit(childsTotalUnit); + familyItem.setFamilyProductCode(item); + + billingAsset.add(familyItem); + } + } catch (BadSqlGrammarException ex){ + if(exceptionService.isTableNotFound(ex)){ + log.warn("[BillAsset Widget Log]NotFoundTable : {}", ex.getMessage()); + } else { + ex.printStackTrace(); + throw new RuntimeException(); } - - familyItem.setChildProductCode(childItem); - familyItem.setTotalCost(childsTotalBill); - familyItem.setTotalUnit(childsTotalUnit); - familyItem.setFamilyProductCode(item); - - billingAsset.add(familyItem); } result.setSelectedProjects(req.getSelectedProjects()); diff --git a/BackEnd/src/main/resources/application.properties b/BackEnd/src/main/resources/application.properties index 7cc492b..ad52c4f 100644 --- a/BackEnd/src/main/resources/application.properties +++ b/BackEnd/src/main/resources/application.properties @@ -27,3 +27,7 @@ springdoc.swagger-ui.tags-sorter=alpha springdoc.swagger-ui.operations-sorter=alpha springdoc.default-consumes-media-type=application/json;charset=UTF-8 springdoc.default-produces-media-type=application/json;charset=UTF-8 + +tumblebug.url=https://local.tumblebug.com/tumblebug +tumblebug.username=default +tumblebug.password=default diff --git a/BackEnd/src/main/resources/mapper/bill/alarm_SQL.xml b/BackEnd/src/main/resources/mapper/bill/alarm_SQL.xml new file mode 100644 index 0000000..2fed44a --- /dev/null +++ b/BackEnd/src/main/resources/mapper/bill/alarm_SQL.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/BackEnd/src/main/resources/mapper/bill/bill_SQL.xml b/BackEnd/src/main/resources/mapper/bill/bill_SQL.xml index f62ba30..58aebdd 100644 --- a/BackEnd/src/main/resources/mapper/bill/bill_SQL.xml +++ b/BackEnd/src/main/resources/mapper/bill/bill_SQL.xml @@ -21,96 +21,68 @@ - SELECT COALESCE(sum(co.lineitem_unblendedcost),0) - FROM cur_origin co - LEFT JOIN servicegroup_meta sm - ON co.lineitem_resourceid = sm.csp_instanceid - WHERE 1=1 - - AND sm.csp_type IN - - #{selectedCsp} + + + + + diff --git a/BackEnd/src/main/resources/mapper/bill/tbb_SQL.xml b/BackEnd/src/main/resources/mapper/bill/tbb_SQL.xml new file mode 100644 index 0000000..463fa0e --- /dev/null +++ b/BackEnd/src/main/resources/mapper/bill/tbb_SQL.xml @@ -0,0 +1,28 @@ + + + + + INSERT INTO servicegroup_meta(csp_type, csp_account, csp_instanceid, service_cd, service_nm, service_uid + , vm_id, vm_uid, vm_nm, mci_id, mci_uid, mci_nm, instance_running_status) + VALUES + + (#{item.cspType} + , #{item.cspAccount} + , #{item.cspInstanceid} + , #{item.serviceCd} + , #{item.serviceNm} + , #{item.serviceUid} + , #{item.vmId} + , #{item.vmUid} + , #{item.vmNm} + , #{item.mciId} + , #{item.mciUid} + , #{item.mciNm} + , #{item.instanceRunningStatus}) + + ON DUPLICATE KEY UPDATE service_nm = values(service_nm) + , vm_nm = values(vm_nm) + , mci_nm = values(mci_nm) + , instance_running_status = values(instance_running_status) + + diff --git a/BackEnd/src/test/java/com/mcmp/costbe/dateRangeTest.java b/BackEnd/src/test/java/com/mcmp/costbe/dateRangeTest.java index 3c9dd13..85a2648 100644 --- a/BackEnd/src/test/java/com/mcmp/costbe/dateRangeTest.java +++ b/BackEnd/src/test/java/com/mcmp/costbe/dateRangeTest.java @@ -25,10 +25,8 @@ public class dateRangeTest { @Test public void testCal(){ - List result = dateCalculator.calculatePeriodDates("20240321", "30days"); +// List result = dateCalculator.calculatePeriodDates("20240321", "30days"); // System.out.println(result); -// List result = AWSResourceMapping.getData("Database"); - System.out.println(result); } } diff --git a/assetCollector/.gitignore b/assetCollector/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/assetCollector/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/assetCollector/.mvn/wrapper/maven-wrapper.properties b/assetCollector/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/assetCollector/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/assetCollector/Dockerfile b/assetCollector/Dockerfile new file mode 100644 index 0000000..e2ab61e --- /dev/null +++ b/assetCollector/Dockerfile @@ -0,0 +1,13 @@ +# Build stage +FROM maven:3.8.5-openjdk-17-slim AS build +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + +# Run stage +FROM openjdk:17-jdk-slim +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar +EXPOSE 8091 +ENTRYPOINT ["java","-jar","app.jar"] diff --git a/assetCollector/mvnw b/assetCollector/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/assetCollector/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/assetCollector/mvnw.cmd b/assetCollector/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/assetCollector/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/assetCollector/pom.xml b/assetCollector/pom.xml new file mode 100644 index 0000000..740455d --- /dev/null +++ b/assetCollector/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.10 + + + com.mcmp + assetCollector + 0.0.1-SNAPSHOT + assetCollector + assetCollector + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mybatis + mybatis-spring + 3.0.0 + + + org.mybatis + mybatis + 3.5.11 + + + org.springframework + spring-jdbc + 6.1.5 + + + org.mariadb.jdbc + mariadb-java-client + 3.1.4 + runtime + + + org.springframework.boot + spring-boot-starter-batch + + + org.springframework.boot + spring-boot-starter-quartz + + + org.springframework.batch + spring-batch-integration + + + org.springframework.batch + spring-batch-test + test + + + com.integralblue + log4jdbc-spring-boot-starter + 2.0.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/AssetCollectorApplication.java b/assetCollector/src/main/java/com/mcmp/assetcollector/AssetCollectorApplication.java new file mode 100644 index 0000000..5263015 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/AssetCollectorApplication.java @@ -0,0 +1,13 @@ +package com.mcmp.assetcollector; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AssetCollectorApplication { + + public static void main(String[] args) { + SpringApplication.run(AssetCollectorApplication.class, args); + } + +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/batch/AssetCollect.java b/assetCollector/src/main/java/com/mcmp/assetcollector/batch/AssetCollect.java new file mode 100644 index 0000000..8f800af --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/batch/AssetCollect.java @@ -0,0 +1,149 @@ +package com.mcmp.assetcollector.batch; + + +import com.mcmp.assetcollector.model.batch.RSRCAssetComputeMetricModel; +import com.mcmp.assetcollector.model.batch.RunningInstanceModel; +import com.mcmp.assetcollector.model.service.RSRCAssetUsageItemModel; +import com.mcmp.assetcollector.model.service.RSRCAssetUsageModel; +import com.mcmp.assetcollector.service.AssetCollectService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.batch.MyBatisBatchItemWriter; +import org.mybatis.spring.batch.MyBatisPagingItemReader; +import org.mybatis.spring.batch.builder.MyBatisBatchItemWriterBuilder; +import org.mybatis.spring.batch.builder.MyBatisPagingItemReaderBuilder; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemWriter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.transaction.PlatformTransactionManager; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.PrimitiveIterator; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class AssetCollect { + + @Autowired + @Qualifier("sqlSessionBatch") + private SqlSessionFactory sqlSessionFactory; + + @Autowired + private AssetCollectService assetCollectService; + + @Bean + public SkipLogListener skipLogListener(){ + return new SkipLogListener(); + } + + @Bean + public Job assetCollectJob(JobRepository jobRepository, Step assetCollectStep) { + return new JobBuilder("assetCollectJob", jobRepository) + .start(assetCollectStep) + .build(); + } + + @Bean + @JobScope + public Step assetCollectStep(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager){ + return new StepBuilder("assetCollectStep", jobRepository) + .>chunk(1, platformTransactionManager) + .reader(reader()) + .processor(processor()) + .writer(writerListItems(writerItems())) + .startLimit(2) + .faultTolerant() + .retry(Exception.class) + .retryLimit(2) + .skip(Exception.class) + .skipPolicy(new AlwaysSkipItemSkipPolicy()) + .listener(skipLogListener()) + .build(); + } + + @Bean + @StepScope + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory) + .queryId("asset.getAssetRunningInstance") + .pageSize(1) + .build(); + } + + @StepScope + public ItemProcessor> processor() { + return item -> { + + Thread.sleep(5000); + List data = new ArrayList<>(); + try{ + List tempData = assetCollectService.getRSRCCpuUsageHistory(item.getNsID(), item.getMciID(), item.getVmID()); + + if(!tempData.isEmpty()){ + for(RSRCAssetUsageItemModel tempDataItem : tempData){ + try { + Double amount = Double.parseDouble(tempDataItem.getValue()); + + RSRCAssetComputeMetricModel dataItem = RSRCAssetComputeMetricModel.builder() + .cspType(item.getCspType()) + .cspAccount(item.getCspAccount()) + .cspInstanceid(item.getInstanceID()) + .collectDt(Timestamp.from(tempDataItem.getTimestamp())) + .metricType("cpu") + .metricAmount(amount) + .resourceStatus("running") + .resourceSpotYn("N") + .build(); + + data.add(dataItem); + } catch (NumberFormatException e){ + log.warn("Invalid number format for NS ID: {}, MIC ID: {}, VM ID: {}, item: {}", item.getNsID(), item.getMciID(), item.getVmID(), item); + } + } + } else { + return null; + } + + }catch (Exception ex){ + ex.printStackTrace(); + return null; + } + return data; + }; + } + + @Bean + public ItemWriter> writerListItems(MyBatisBatchItemWriter writerItems){ + return chunks -> { + for(List chunk : chunks){ + writerItems.write(new Chunk<>(chunk)); + } + }; + } + + @Bean + public MyBatisBatchItemWriter writerItems() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory) + .statementId("asset.insertRSRCComputeMetric") + .build(); + } +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/batch/SkipLogListener.java b/assetCollector/src/main/java/com/mcmp/assetcollector/batch/SkipLogListener.java new file mode 100644 index 0000000..984f300 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/batch/SkipLogListener.java @@ -0,0 +1,22 @@ +package com.mcmp.assetcollector.batch; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.SkipListener; + +@Slf4j +public class SkipLogListener implements SkipListener { + @Override + public void onSkipInRead(Throwable t) { + log.warn("Skipped during read due to: {}", t.getMessage(), t); + } + + @Override + public void onSkipInWrite(Object item, Throwable t) { + log.warn("Skipped during write for item {} due to: {}", item, t.getMessage(), t); + } + + @Override + public void onSkipInProcess(Object item, Throwable t) { + log.warn("Skipped during process for item {} due to: {}", item, t.getMessage(), t); + } +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/config/DatabaseConfig.java b/assetCollector/src/main/java/com/mcmp/assetcollector/config/DatabaseConfig.java new file mode 100644 index 0000000..6791949 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/config/DatabaseConfig.java @@ -0,0 +1,57 @@ +package com.mcmp.assetcollector.config; + +import com.zaxxer.hikari.HikariDataSource; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; + +@Configuration +public class DatabaseConfig { + @Primary + @Bean(name = "dataSource") + @ConfigurationProperties(prefix = "spring.datasource.hikari.batch") + public DataSource dataSourceBatch() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); + } + + @Primary + @Bean(name = "transactionManager") + public PlatformTransactionManager transactionManagerBatch() { + return new DataSourceTransactionManager(dataSourceBatch()); + } + + @Primary + @Bean(name = "sqlSessionBatch") + public SqlSessionFactory sqlSessionBatch() throws Exception { + SqlSessionFactoryBean sqlSession = new SqlSessionFactoryBean(); + sqlSession.setDataSource(dataSourceBatch()); + sqlSession.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(ResourcePatternResolver.CLASSPATH_URL_PREFIX + "/mapper/batch/*_SQL.xml")); + return sqlSession.getObject(); + } + + @Bean(name = "sqlSessionSimple") + public SqlSessionFactory sqlSessionSimple() throws Exception { + SqlSessionFactoryBean sqlSession = new SqlSessionFactoryBean(); + sqlSession.setDataSource(dataSourceBatch()); + sqlSession.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(ResourcePatternResolver.CLASSPATH_URL_PREFIX + "/mapper/simple/*_SQL.xml")); + return sqlSession.getObject(); + } + + @Primary + @Bean(name = "sqlSessionTemplateSimple") + public SqlSessionTemplate sqlSessionTemplateSimple() throws Exception { + return new SqlSessionTemplate(sqlSessionSimple(), ExecutorType.SIMPLE); + } +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RSRCAssetComputeMetricModel.java b/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RSRCAssetComputeMetricModel.java new file mode 100644 index 0000000..e5ee026 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RSRCAssetComputeMetricModel.java @@ -0,0 +1,19 @@ +package com.mcmp.assetcollector.model.batch; + +import lombok.Builder; +import lombok.Data; + +import java.sql.Timestamp; + +@Data +@Builder +public class RSRCAssetComputeMetricModel { + private String cspType; + private String cspAccount; + private String cspInstanceid; + private Timestamp collectDt; + private String metricType; + private double metricAmount; + private String resourceStatus; + private String resourceSpotYn; +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RunningInstanceModel.java b/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RunningInstanceModel.java new file mode 100644 index 0000000..5a2c0ce --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/model/batch/RunningInstanceModel.java @@ -0,0 +1,17 @@ +package com.mcmp.assetcollector.model.batch; + +import lombok.Data; + +@Data +public class RunningInstanceModel { + private String cspType; + private String cspAccount; + private String instanceID; + private String nsID; + private String nsUID; + private String vmID; + private String vmUID; + private String mciID; + private String mciUID; + private String instanceRunningStatus; +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageDataModel.java b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageDataModel.java new file mode 100644 index 0000000..038554e --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageDataModel.java @@ -0,0 +1,14 @@ +package com.mcmp.assetcollector.model.service; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class RSRCAssetUsageDataModel { + private String metricName; + private String metricUnit; + private List timestampValues; +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageItemModel.java b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageItemModel.java new file mode 100644 index 0000000..e486f30 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageItemModel.java @@ -0,0 +1,13 @@ +package com.mcmp.assetcollector.model.service; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.time.Instant; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class RSRCAssetUsageItemModel { + private Instant timestamp; + private String value; +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageModel.java b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageModel.java new file mode 100644 index 0000000..e4a1112 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/model/service/RSRCAssetUsageModel.java @@ -0,0 +1,13 @@ +package com.mcmp.assetcollector.model.service; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class RSRCAssetUsageModel { + private RSRCAssetUsageDataModel data; + private String rsCode; + private String rsMsg; + private String errorMessage; +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/AssetCltJob.java b/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/AssetCltJob.java new file mode 100644 index 0000000..0021e5d --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/AssetCltJob.java @@ -0,0 +1,43 @@ +package com.mcmp.assetcollector.schedule; + +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AssetCltJob extends QuartzJobBean { + + @Autowired + private JobLauncher jobLauncher; + + @Qualifier("assetCollectJob") + @Autowired + private Job assetCollectJob; + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + log.info("Starting AssetCollect Quartz Job"); + + try { + JobParameters jobParameter = new JobParametersBuilder() + .addLong("createTime", System.currentTimeMillis()) + .toJobParameters(); + + jobLauncher.run(assetCollectJob, jobParameter); + } catch (Exception e) { + log.error("Error executing Spring Batch AssetCollect Job", e); + throw new JobExecutionException("Failed to execute Spring AssetCollect Batch Job", e); + } + + log.info("AssetCollect Quartz Job completed"); + } +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/ScheduleConfig.java b/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/ScheduleConfig.java new file mode 100644 index 0000000..799ea0e --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/schedule/ScheduleConfig.java @@ -0,0 +1,34 @@ +package com.mcmp.assetcollector.schedule; + +import org.quartz.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ScheduleConfig { + + @Value("${assetCollectBatchCronSchedule}") + private String assetSchedule; + + @Bean + public JobDetail assetJobDetail() { + return JobBuilder.newJob().ofType(AssetCltJob.class) + .storeDurably() + .withIdentity("assetCollectJob", "assetCollect") + .withDescription("Execute Spring Batch AssetCollect Batch Job with Quartz") + .build(); + } + + @Bean + public Trigger assetJobTrigger(JobDetail assetJobDetail) { + CronScheduleBuilder cronSchedule = CronScheduleBuilder.cronSchedule(assetSchedule); + + return TriggerBuilder.newTrigger().forJob(assetJobDetail) + .withIdentity("assetCollectJobTrigger1", "assetCollect") + .withDescription("AssetCollect Job Trigger") + .withSchedule(cronSchedule) + .build(); + } + +} diff --git a/assetCollector/src/main/java/com/mcmp/assetcollector/service/AssetCollectService.java b/assetCollector/src/main/java/com/mcmp/assetcollector/service/AssetCollectService.java new file mode 100644 index 0000000..7ad0080 --- /dev/null +++ b/assetCollector/src/main/java/com/mcmp/assetcollector/service/AssetCollectService.java @@ -0,0 +1,55 @@ +package com.mcmp.assetcollector.service; + +import com.mcmp.assetcollector.model.service.RSRCAssetUsageDataModel; +import com.mcmp.assetcollector.model.service.RSRCAssetUsageItemModel; +import com.mcmp.assetcollector.model.service.RSRCAssetUsageModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Service +@Slf4j +public class AssetCollectService { + + @Value("${asset.collect.url}") + private String assetCollectUrl; + + public List getRSRCCpuUsageHistory(String nsID, String mciID, String vmID){ + String apiUrl = String.format("%s/api/o11y/monitoring/%s/%s/target/%s/csp/cpu_usage", assetCollectUrl, nsID, mciID, vmID); + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders httpHeaders = new HttpHeaders(); + HttpEntity httpEntity = new HttpEntity<>(httpHeaders); + + try{ + ResponseEntity responseEntity = restTemplate.exchange(apiUrl, HttpMethod.GET, httpEntity, RSRCAssetUsageModel.class); + RSRCAssetUsageModel response = responseEntity.getBody(); + RSRCAssetUsageDataModel data = response.getData(); + + if(data == null || data.getTimestampValues() == null || data.getTimestampValues().isEmpty()){ + log.warn("RSRC Asset Usage - {} => NS ID : {}, MIC ID : {}, VM ID : {} => response : {}", (data == null ? "DATA IS EMPTY" : "TimestampValues IS EMPTY"), nsID, mciID, vmID, response); + return new ArrayList<>(); + } + return data.getTimestampValues(); + + } catch (HttpClientErrorException | HttpServerErrorException clientError) { + HttpStatusCode statusCode = clientError.getStatusCode(); + log.error("FAIL TO GET RSRC Asset Usage - NS ID : {}, MIC ID : {}, VM ID : {} => {}", nsID, mciID, vmID, statusCode); + return new ArrayList<>(); + } catch (Exception e){ + log.error("FAIL TO GET RSRC Asset Usage - NS ID : {}, MIC ID : {}, VM ID : {}", nsID, mciID, vmID); + e.printStackTrace(); + return new ArrayList<>(); + } + + } +} diff --git a/assetCollector/src/main/resources/application.properties b/assetCollector/src/main/resources/application.properties new file mode 100644 index 0000000..f610443 --- /dev/null +++ b/assetCollector/src/main/resources/application.properties @@ -0,0 +1,22 @@ +spring.application.name=assetCollector +server.port=8091 + +spring.batch.job.enabled=false + +spring.datasource.hikari.batch.driver-class-name=org.mariadb.jdbc.Driver +spring.datasource.hikari.batch.jdbc-url=jdbc:mariadb://localhost:3306/cost?autoReconnect=true&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true +spring.datasource.hikari.batch.username=mcmpcostopti +spring.datasource.hikari.batch.password=0000 +spring.datasource.hikari.batch.initial-size=10 +spring.datasource.hikari.batch.max-total=10 +spring.datasource.hikari.batch.max-idle=10 +spring.datasource.hikari.batch.max-wait-millis=30000 +spring.datasource.hikari.batch.remove-abandoned-on-borrow=true +spring.datasource.hikari.batch.remove-abandoned-timeout=30 + +logging.level.org.springframework.batch=DEBUG +logging.level.org.mybatis=DEBUG + +asset.collect.url=http://monitoring.server:8080 + +assetCollectBatchCronSchedule=0 0 0 * * ? diff --git a/assetCollector/src/main/resources/logback-spring.xml b/assetCollector/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8abbfc6 --- /dev/null +++ b/assetCollector/src/main/resources/logback-spring.xml @@ -0,0 +1,24 @@ + + + + + UTF-8 + %d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight(%5level) %cyan(%logger) - %msg%n + + + + + + + + + + + + + + + + + + diff --git a/assetCollector/src/main/resources/mapper/batch/asset_SQL.xml b/assetCollector/src/main/resources/mapper/batch/asset_SQL.xml new file mode 100644 index 0000000..e9683e4 --- /dev/null +++ b/assetCollector/src/main/resources/mapper/batch/asset_SQL.xml @@ -0,0 +1,34 @@ + + + + + + + INSERT INTO asset_compute_metric(csp_type, csp_account, csp_instanceid, collect_dt, metric_type, metric_amount, resource_status, resource_spot_yn) + VALUES (#{cspType} + , #{cspAccount} + , #{cspInstanceid} + , #{collectDt} + , #{metricType} + , #{metricAmount} + , #{resourceStatus} + , #{resourceSpotYn}) + ON DUPLICATE KEY UPDATE metric_amount = values(metric_amount) + , resource_status = values(resource_status) + , resource_spot_yn = values(resource_spot_yn) + + diff --git a/assetCollector/src/main/resources/mapper/simple/simple_SQL.xml b/assetCollector/src/main/resources/mapper/simple/simple_SQL.xml new file mode 100644 index 0000000..f9fc451 --- /dev/null +++ b/assetCollector/src/main/resources/mapper/simple/simple_SQL.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/assetCollector/src/test/java/com/mcmp/assetcollector/AssetCollectorApplicationTests.java b/assetCollector/src/test/java/com/mcmp/assetcollector/AssetCollectorApplicationTests.java new file mode 100644 index 0000000..f9b40ce --- /dev/null +++ b/assetCollector/src/test/java/com/mcmp/assetcollector/AssetCollectorApplicationTests.java @@ -0,0 +1,13 @@ +package com.mcmp.assetcollector; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AssetCollectorApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/cost-fe/package.json b/cost-fe/package.json index 019b60f..5094182 100644 --- a/cost-fe/package.json +++ b/cost-fe/package.json @@ -8,11 +8,13 @@ "lint": "vue-cli-service lint" }, "dependencies": { - "apexcharts": "^3.49.1", + "apexcharts": "^3.53.0", "axios": "^1.7.2", "bootstrap": "^5.3.3", "core-js": "^3.8.3", "pinia": "^2.1.7", + "primeicons": "^7.0.0", + "primevue": "^3.29.2", "sass": "^1.77.7", "sass-loader": "^14.2.1", "tabulator-tables": "^6.2.1", diff --git a/cost-fe/public/index.html b/cost-fe/public/index.html index 0e501b9..0e883f3 100644 --- a/cost-fe/public/index.html +++ b/cost-fe/public/index.html @@ -1,5 +1,6 @@ + diff --git a/cost-fe/src/App.vue b/cost-fe/src/App.vue index 9e29c40..e027f80 100644 --- a/cost-fe/src/App.vue +++ b/cost-fe/src/App.vue @@ -1,13 +1,51 @@ diff --git a/cost-fe/src/api/Endpoints.js b/cost-fe/src/api/Endpoints.js index b776061..f2213ec 100644 --- a/cost-fe/src/api/Endpoints.js +++ b/cost-fe/src/api/Endpoints.js @@ -1,12 +1,15 @@ const MainDomain = location.hostname console.log('도메인 확인' + MainDomain) -// const isNumericAndDotsOnly = /^[0-9.]+$/.test(MainDomain); +const isNumericAndDotsOnly = /^[0-9.]+$/.test(MainDomain); let API_BE_URL = ''; let API_ALARM_URL = ''; if(MainDomain.includes('localhost')){ API_BE_URL = 'http://' + MainDomain + ':9090'; - API_ALARM_URL = 'https://' + MainDomain + ':9000'; + API_ALARM_URL = 'http://' + MainDomain + ':9000'; +} else if(isNumericAndDotsOnly) { + API_BE_URL = 'http://' + MainDomain + ':9090'; + API_ALARM_URL = 'http://' + MainDomain + ':9000'; } else { API_BE_URL = 'https://' + MainDomain; API_ALARM_URL = 'https://' + MainDomain; diff --git a/cost-fe/src/components/pages/billinginvoice/BillingInvoiceLayout.vue b/cost-fe/src/components/pages/billinginvoice/BillingInvoiceLayout.vue index 72025ca..93a14d8 100644 --- a/cost-fe/src/components/pages/billinginvoice/BillingInvoiceLayout.vue +++ b/cost-fe/src/components/pages/billinginvoice/BillingInvoiceLayout.vue @@ -7,7 +7,7 @@
- +
@@ -20,7 +20,7 @@ import DashboardSelectbox from '../dashboard/dashboard-selectbox/DashboardSelectbox.vue' import DashboardHeader from '../dashboard/dashboard-header/DashboardHeader.vue' import BaseInfo from './base-info/BaseInfo.vue' -import BillingSummary from './billing-summary/BillingSummary.vue' +import MonthlyOverview from './billing-summary/monthlyOverview.vue' import InvoiceTable from './invoice-table/InvoiceTable.vue' export default { @@ -29,7 +29,7 @@ export default { DashboardHeader, DashboardSelectbox, BaseInfo, - BillingSummary, + MonthlyOverview, InvoiceTable }, data() { @@ -40,7 +40,6 @@ export default { diff --git a/cost-fe/src/components/pages/billinginvoice/billing-summary/BillingSummary.vue b/cost-fe/src/components/pages/billinginvoice/billing-summary/BillingSummary.vue deleted file mode 100644 index 12a2fbe..0000000 --- a/cost-fe/src/components/pages/billinginvoice/billing-summary/BillingSummary.vue +++ /dev/null @@ -1,181 +0,0 @@ - - - - - diff --git a/cost-fe/src/components/pages/billinginvoice/billing-summary/monthlyOverview.vue b/cost-fe/src/components/pages/billinginvoice/billing-summary/monthlyOverview.vue new file mode 100644 index 0000000..dd4a103 --- /dev/null +++ b/cost-fe/src/components/pages/billinginvoice/billing-summary/monthlyOverview.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/cost-fe/src/components/pages/billinginvoice/invoice-table/InvoiceTable.vue b/cost-fe/src/components/pages/billinginvoice/invoice-table/InvoiceTable.vue index 11439d0..06511d1 100644 --- a/cost-fe/src/components/pages/billinginvoice/invoice-table/InvoiceTable.vue +++ b/cost-fe/src/components/pages/billinginvoice/invoice-table/InvoiceTable.vue @@ -4,28 +4,9 @@

Invoices

- -