Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ private AlertType(short type, String name, boolean isDefault) {
public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true);
public static final AlertType EVENT_USER_SESSION_BLOCK = new AlertType((short)33, "USER.SESSION.BLOCK", true);
public static final AlertType ALERT_TYPE_LOGIN = new AlertType((short)32, "ALERT.LOGIN", true);
public static final AlertType ALERT_TYPE_WALL_RULE = new AlertType((short)90, "ALERT.WALL.RULE", true);


public short getType() {
return type;
Expand Down
3 changes: 3 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ public class ApiConstants {
public static final String ENABLED = "enabled";
public static final String SERVICE_NAME = "servicename";
public static final String DHCP_RANGE = "dhcprange";
public static final String UID = "uid";
public static final String UUID = "uuid";
public static final String SECURITY_GROUP_EANBLED = "securitygroupenabled";
public static final String LOCAL_STORAGE_ENABLED = "localstorageenabled";
Expand Down Expand Up @@ -1323,6 +1324,8 @@ public class ApiConstants {
public static final String WWPN = "wwpn";
public static final String VHBA_NAME = "vhbaname";

public static final String OPERATOR = "operator";

/**
* This enum specifies IO Drivers, each option controls specific policies on I/O.
* Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).
Expand Down
5 changes: 5 additions & 0 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,11 @@
<artifactId>cloud-plugin-integrations-disaster-recovery</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-integrations-wall-alerts</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-shutdown</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ public interface AlertManager extends Manager, AlertService {

void sendAlert(AlertType alertType, long dataCenterId, Long podId, String subject, String body);

void sendPersistentAlert(AlertType alertType, long dataCenterId, Long podId, String subject, String body);
void sendPersistentAlert(AlertType alertType, long dataCenterId, Long podId, Long clusterId, String subject, String body);

}
53 changes: 53 additions & 0 deletions plugins/integrations/wall-alerts/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-integrations-wall-alerts</artifactId>
<name>Apache CloudStack Plugin - Wall Alerts</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.21.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-engine-schema</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Licensed to the ASF under one or more contributor license agreements...
package org.apache.cloudstack.api.command.admin.wall.alerts;

import com.cloud.user.Account;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.WallSilenceResponse;
import org.apache.cloudstack.wallAlerts.service.WallAlertsService;

import javax.inject.Inject;
import java.util.Map;

@APICommand(
name = CreateWallAlertSilenceCmd.APINAME,
description = "Creates an Alertmanager silence from given labels and duration",
responseObject = WallSilenceResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
authorized = { RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User }
)
public class CreateWallAlertSilenceCmd extends BaseCmd {
public static final String APINAME = "createWallAlertSilence";

@Inject
private WallAlertsService wallAlertsService;

/**
* 필수: 라벨 MAP
*/
@Parameter(name = "labels", type = CommandType.MAP, required = true,
description = "Alert/Rule label map (name=value). UID/alertname/instance etc.")
private Map<String, String> labels;

/**
* 권장: 분 단위(숫자) — 표준 키 (!! required=false 로 완화 !!)
* - CloudStack 라우터가 'required=true' 를 강제하면 별칭으로만 보냈을 때 사전에 튕깁니다.
* - required=false 로 두고, execute() 단계에서 유효성 검사합니다.
*/
@Parameter(name = "durationMinutes", type = CommandType.LONG, required = false,
description = "Silence duration in minutes from now or startsAt")
private Long durationMinutes;

/**
* 별칭1: 소문자 키로만 오는 환경 대비
*/
@Parameter(name = "durationminutes", type = CommandType.LONG, required = false,
description = "Alias of durationMinutes for clients that lowercase params")
private Long durationminutes;

/**
* 별칭2: 문자열 표기(예: 30m, 1h, 2d, 1w, 1M=30days)
* - 같은 이름으로 LONG/STRING을 중복 선언하면 리플렉션이 꼬입니다. (기존 코드의 중복 원인 제거)
*/
@Parameter(name = "duration", type = CommandType.STRING, required = false,
description = "Optional duration string (e.g., 30m, 1h, 2d, 1w, 1M=30 days)")
private String durationText;

@Parameter(name = "comment", type = CommandType.STRING,
description = "Optional comment for auditing")
private String comment;

@Parameter(name = "startsAt", type = CommandType.STRING,
description = "Optional ISO-8601 start time (e.g., 2025-10-20T01:23:45Z). If absent, now()")
private String startsAt;

// ---------- Getters ----------
public Map<String, String> getLabels() { return labels; }
public String getComment() { return comment; }
public String getStartsAt() { return startsAt; }
public String getDuration() { return durationText; }

/**
* 최종 분 단위로 해석된 duration.
* - 표준(durationMinutes) 우선
* - 없으면 별칭(durationminutes)
* - 없으면 문자열(duration) 파싱
*/
public Long getDurationMinutes() {
if (durationMinutes != null) return durationMinutes;
if (durationminutes != null) return durationminutes;
final Long parsed = parseDurationTextToMinutes(durationText);
return parsed;
}

// ---------- Execution ----------
@Override
public void execute() {
try {
final Long mins = getDurationMinutes();
if (mins == null || mins <= 0) {
throw new IllegalArgumentException("Missing or invalid duration: supply 'durationMinutes' (minutes) "
+ "or 'duration' like 30m/1h/1d/1w/1M.");
}
final WallSilenceResponse resp = wallAlertsService.createWallAlertSilence(this);
resp.setResponseName(getCommandName());
setResponseObject(resp);
} catch (IllegalArgumentException iae) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, iae.getMessage());
} catch (RuntimeException re) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, re.getMessage());
}
}

@Override
public String getCommandName() {
return APINAME.toLowerCase() + "response";
}

@Override
public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; }

// ---------- Helpers ----------
private static Long parseDurationTextToMinutes(final String s) {
if (s == null) return null;
final String str = s.trim();
if (str.isEmpty()) return null;

// 지원: m(분), h(시간), d(일), w(주=7일), M(월=30일)
final java.util.regex.Matcher m = java.util.regex.Pattern
.compile("^(\\d+)\\s*([mhdwM]?)$")
.matcher(str);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid duration format: " + s);
}
final long n = Long.parseLong(m.group(1));
final String u = m.group(2).isEmpty() ? "m" : m.group(2);
long minutes;
switch (u) {
case "m": minutes = n; break;
case "h": minutes = n * 60L; break;
case "d": minutes = n * 60L * 24L; break;
case "w": minutes = n * 60L * 24L * 7L; break;
case "M": minutes = n * 60L * 24L * 30L; break;
default: throw new IllegalArgumentException("Unsupported duration unit: " + u);
}
return minutes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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.

package org.apache.cloudstack.api.command.admin.wall.alerts;

import javax.inject.Inject;

import com.cloud.user.Account;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.wallAlerts.service.WallAlertsService;

@APICommand(name = ExpireWallAlertSilenceCmd.APINAME,
description = "Expires (deletes) a specific Alertmanager silence by ID",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
authorized = { RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin })
public class ExpireWallAlertSilenceCmd extends BaseCmd {
public static final String APINAME = "expireWallAlertSilence";

@Inject
private WallAlertsService wallAlertsService;

// ---------- Parameter ----------
@Parameter(name = ApiConstants.ID, type = CommandType.STRING, required = true,
description = "Silence ID to expire")
private String id;

// ---------- Getter ----------
public String getId() {
return id;
}

// ---------- Execution ----------
@Override
public void execute() {
try {
final SuccessResponse response = wallAlertsService.expireWallAlertSilence(this);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (IllegalArgumentException iae) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, iae.getMessage());
} catch (RuntimeException re) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, re.getMessage());
}
}

@Override
public String getCommandName() {
return APINAME.toLowerCase() + "response";
}

@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
}
Loading
Loading