feature: webhooks (#8674)

* api,server,ui: weebhoks feature

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* registry of message busses

* test bus

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* refactor

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* test

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix and refactor

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes for webhook dispatch history

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes, initial ui

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* improvements

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes for account webhook cleanup

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix remaining event bus usage

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes for testing webhook dispatch

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* wip

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix test

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* make element

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* missing

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* buid fix

* fix lint

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes for project delete check

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add collapse in create

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui fix and refactor for eventditributor publish

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* update org.json and add json validation

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* schema fixes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* wordings

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui: improve progress button

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui improvements

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* remove unrelated change

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* search and count

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add payloadurl in info

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* positive progress

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix hmac key

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* create webhook form fixes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* refactor, address feedback

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* indentation

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix filters

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* remove test eventbus

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* default scope be Local

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add lifecycle smoke test

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add test for webhook deliveries

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* refactor

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix lint

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* refactor - losgs and others

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* unit tests

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix lint

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* build fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* smoke test fix, log refactor

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* get bean from all components

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui: missing label

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* address review comments

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add some more tests

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* lint

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* rename setting

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* upgrade: move 4.19.0->4.20.0 to 4.19.1->4.20.0

* fix test delivery layout

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix webhook secret display

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add http to payloadurl when no scheme

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* allow removing secretkey for webhook

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix update sslverification

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* disallow same payload url for same account

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix delivery with url w/o scheme

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* api: listApis should return params based on caller

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* wip changes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* Update engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql

* remove unique constraint for now

Constraint is present in Java code validations

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fixes

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui: add option to delete multiple deliveries

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* add filter for deliveries, delete api start/endtime support

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* do not throw error when no deliveries removed

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* ui: fix deliveries table column sorting, time filter cancel

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* remove isDebugEnabled wrapping

* merge fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: Daan Hoogland <daan@onecht.net>
Co-authored-by: Wei Zhou <weizhou@apache.org>
This commit is contained in:
Abhishek Kumar 2024-06-10 10:40:12 +05:30 committed by GitHub
parent 2542582c1e
commit be552fdce9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
102 changed files with 8739 additions and 329 deletions

View File

@ -86,7 +86,8 @@ jobs:
smoke/test_migration smoke/test_migration
smoke/test_multipleips_per_nic smoke/test_multipleips_per_nic
smoke/test_nested_virtualization smoke/test_nested_virtualization
smoke/test_set_sourcenat", smoke/test_set_sourcenat
smoke/test_webhook_lifecycle",
"smoke/test_network "smoke/test_network
smoke/test_network_acl smoke/test_network_acl
smoke/test_network_ipv6 smoke/test_network_ipv6

View File

@ -175,6 +175,8 @@ public class ApiConstants {
public static final String END_IPV6 = "endipv6"; public static final String END_IPV6 = "endipv6";
public static final String END_PORT = "endport"; public static final String END_PORT = "endport";
public static final String ENTRY_TIME = "entrytime"; public static final String ENTRY_TIME = "entrytime";
public static final String EVENT_ID = "eventid";
public static final String EVENT_TYPE = "eventtype";
public static final String EXPIRES = "expires"; public static final String EXPIRES = "expires";
public static final String EXTRA_CONFIG = "extraconfig"; public static final String EXTRA_CONFIG = "extraconfig";
public static final String EXTRA_DHCP_OPTION = "extradhcpoption"; public static final String EXTRA_DHCP_OPTION = "extradhcpoption";
@ -209,6 +211,7 @@ public class ApiConstants {
public static final String HA_PROVIDER = "haprovider"; public static final String HA_PROVIDER = "haprovider";
public static final String HA_STATE = "hastate"; public static final String HA_STATE = "hastate";
public static final String HEALTH = "health"; public static final String HEALTH = "health";
public static final String HEADERS = "headers";
public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage"; public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage";
public static final String HOST_ID = "hostid"; public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids"; public static final String HOST_IDS = "hostids";
@ -281,6 +284,7 @@ public class ApiConstants {
public static final String JOB_STATUS = "jobstatus"; public static final String JOB_STATUS = "jobstatus";
public static final String KEEPALIVE_ENABLED = "keepaliveenabled"; public static final String KEEPALIVE_ENABLED = "keepaliveenabled";
public static final String KERNEL_VERSION = "kernelversion"; public static final String KERNEL_VERSION = "kernelversion";
public static final String KEY = "key";
public static final String LABEL = "label"; public static final String LABEL = "label";
public static final String LASTNAME = "lastname"; public static final String LASTNAME = "lastname";
public static final String LAST_BOOT = "lastboottime"; public static final String LAST_BOOT = "lastboottime";
@ -355,6 +359,7 @@ public class ApiConstants {
public static final String SSHKEY_ENABLED = "sshkeyenabled"; public static final String SSHKEY_ENABLED = "sshkeyenabled";
public static final String PATH = "path"; public static final String PATH = "path";
public static final String PAYLOAD = "payload"; public static final String PAYLOAD = "payload";
public static final String PAYLOAD_URL = "payloadurl";
public static final String POD_ID = "podid"; public static final String POD_ID = "podid";
public static final String POD_NAME = "podname"; public static final String POD_NAME = "podname";
public static final String POD_IDS = "podids"; public static final String POD_IDS = "podids";
@ -400,11 +405,9 @@ public class ApiConstants {
public static final String QUERY_FILTER = "queryfilter"; public static final String QUERY_FILTER = "queryfilter";
public static final String SCHEDULE = "schedule"; public static final String SCHEDULE = "schedule";
public static final String SCOPE = "scope"; public static final String SCOPE = "scope";
public static final String SECRET_KEY = "usersecretkey";
public static final String SECONDARY_IP = "secondaryip";
public static final String SINCE = "since";
public static final String KEY = "key";
public static final String SEARCH_BASE = "searchbase"; public static final String SEARCH_BASE = "searchbase";
public static final String SECONDARY_IP = "secondaryip";
public static final String SECRET_KEY = "secretkey";
public static final String SECURITY_GROUP_IDS = "securitygroupids"; public static final String SECURITY_GROUP_IDS = "securitygroupids";
public static final String SECURITY_GROUP_NAMES = "securitygroupnames"; public static final String SECURITY_GROUP_NAMES = "securitygroupnames";
public static final String SECURITY_GROUP_NAME = "securitygroupname"; public static final String SECURITY_GROUP_NAME = "securitygroupname";
@ -422,6 +425,7 @@ public class ApiConstants {
public static final String SHOW_UNIQUE = "showunique"; public static final String SHOW_UNIQUE = "showunique";
public static final String SIGNATURE = "signature"; public static final String SIGNATURE = "signature";
public static final String SIGNATURE_VERSION = "signatureversion"; public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SINCE = "since";
public static final String SIZE = "size"; public static final String SIZE = "size";
public static final String SNAPSHOT = "snapshot"; public static final String SNAPSHOT = "snapshot";
public static final String SNAPSHOT_ID = "snapshotid"; public static final String SNAPSHOT_ID = "snapshotid";
@ -429,8 +433,7 @@ public class ApiConstants {
public static final String SNAPSHOT_TYPE = "snapshottype"; public static final String SNAPSHOT_TYPE = "snapshottype";
public static final String SNAPSHOT_QUIESCEVM = "quiescevm"; public static final String SNAPSHOT_QUIESCEVM = "quiescevm";
public static final String SOURCE_ZONE_ID = "sourcezoneid"; public static final String SOURCE_ZONE_ID = "sourcezoneid";
public static final String SUITABLE_FOR_VM = "suitableforvirtualmachine"; public static final String SSL_VERIFICATION = "sslverification";
public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot";
public static final String START_DATE = "startdate"; public static final String START_DATE = "startdate";
public static final String START_ID = "startid"; public static final String START_ID = "startid";
public static final String START_IP = "startip"; public static final String START_IP = "startip";
@ -449,6 +452,9 @@ public class ApiConstants {
public static final String SYSTEM_VM_TYPE = "systemvmtype"; public static final String SYSTEM_VM_TYPE = "systemvmtype";
public static final String TAGS = "tags"; public static final String TAGS = "tags";
public static final String STORAGE_TAGS = "storagetags"; public static final String STORAGE_TAGS = "storagetags";
public static final String SUCCESS = "success";
public static final String SUITABLE_FOR_VM = "suitableforvirtualmachine";
public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot";
public static final String TARGET_IQN = "targetiqn"; public static final String TARGET_IQN = "targetiqn";
public static final String TEMPLATE_FILTER = "templatefilter"; public static final String TEMPLATE_FILTER = "templatefilter";
public static final String TEMPLATE_ID = "templateid"; public static final String TEMPLATE_ID = "templateid";
@ -482,6 +488,7 @@ public class ApiConstants {
public static final String USERNAME = "username"; public static final String USERNAME = "username";
public static final String USER_CONFIGURABLE = "userconfigurable"; public static final String USER_CONFIGURABLE = "userconfigurable";
public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist"; public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork"; public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
public static final String UPDATE_IN_SEQUENCE = "updateinsequence"; public static final String UPDATE_IN_SEQUENCE = "updateinsequence";
public static final String VALUE = "value"; public static final String VALUE = "value";
@ -561,6 +568,7 @@ public class ApiConstants {
public static final String ALLOCATION_STATE = "allocationstate"; public static final String ALLOCATION_STATE = "allocationstate";
public static final String MANAGED_STATE = "managedstate"; public static final String MANAGED_STATE = "managedstate";
public static final String MANAGEMENT_SERVER_ID = "managementserverid"; public static final String MANAGEMENT_SERVER_ID = "managementserverid";
public static final String MANAGEMENT_SERVER_NAME = "managementservername";
public static final String STORAGE = "storage"; public static final String STORAGE = "storage";
public static final String STORAGE_ID = "storageid"; public static final String STORAGE_ID = "storageid";
public static final String PING_STORAGE_SERVER_IP = "pingstorageserverip"; public static final String PING_STORAGE_SERVER_IP = "pingstorageserverip";
@ -1121,6 +1129,9 @@ public class ApiConstants {
public static final String PARAMETER_DESCRIPTION_IS_TAG_A_RULE = "Whether the informed tag is a JS interpretable rule or not."; public static final String PARAMETER_DESCRIPTION_IS_TAG_A_RULE = "Whether the informed tag is a JS interpretable rule or not.";
public static final String WEBHOOK_ID = "webhookid";
public static final String WEBHOOK_NAME = "webhookname";
/** /**
* This enum specifies IO Drivers, each option controls specific policies on I/O. * 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). * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).

View File

@ -66,7 +66,7 @@ public class UpdateUserCmd extends BaseCmd {
@Parameter(name = ApiConstants.CURRENT_PASSWORD, type = CommandType.STRING, description = "Current password that was being used by the user. You must inform the current password when updating the password.", acceptedOnAdminPort = false) @Parameter(name = ApiConstants.CURRENT_PASSWORD, type = CommandType.STRING, description = "Current password that was being used by the user. You must inform the current password when updating the password.", acceptedOnAdminPort = false)
private String currentPassword; private String currentPassword;
@Parameter(name = ApiConstants.SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey") @Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey")
private String secretKey; private String secretKey;
@Parameter(name = ApiConstants.TIMEZONE, @Parameter(name = ApiConstants.TIMEZONE,

View File

@ -98,7 +98,7 @@ public class BucketResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "Bucket Access Key") @Param(description = "Bucket Access Key")
private String accessKey; private String accessKey;
@SerializedName(ApiConstants.SECRET_KEY) @SerializedName(ApiConstants.USER_SECRET_KEY)
@Param(description = "Bucket Secret Key") @Param(description = "Bucket Secret Key")
private String secretKey; private String secretKey;

View File

@ -438,6 +438,11 @@
<artifactId>cloud-mom-kafka</artifactId> <artifactId>cloud-mom-kafka</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-mom-webhook</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.cloudstack</groupId> <groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-agent-lb</artifactId> <artifactId>cloud-framework-agent-lb</artifactId>

View File

@ -288,7 +288,7 @@
</bean> </bean>
<bean id="hypervisorGurusRegistry" <bean id="hypervisorGurusRegistry"
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
<property name="excludeKey" value="hypervisor.gurus.exclude" /> <property name="excludeKey" value="hypervisor.gurus.exclude" />
</bean> </bean>
@ -358,4 +358,9 @@
</list> </list>
</property> </property>
</bean> </bean>
<bean id="eventBusRegistry"
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
<property name="excludeKey" value="event.buses.exclude" />
</bean>
</beans> </beans>

View File

@ -0,0 +1,21 @@
#
# 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.
#
name=event
parent=core

View File

@ -0,0 +1,31 @@
<!--
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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
<property name="registry" ref="eventBusRegistry" />
<property name="typeClass" value="org.apache.cloudstack.framework.events.EventBus" />
</bean>
</beans>

View File

@ -25,15 +25,14 @@ import java.util.Map;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.Event; import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventBus;
import org.apache.cloudstack.framework.events.EventBusException; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.dc.DataCenterVO; import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.DataCenterDao;
@ -50,6 +49,7 @@ public class UsageEventUtils {
protected static Logger LOGGER = LogManager.getLogger(UsageEventUtils.class); protected static Logger LOGGER = LogManager.getLogger(UsageEventUtils.class);
protected static EventBus s_eventBus = null; protected static EventBus s_eventBus = null;
protected static ConfigurationDao s_configDao; protected static ConfigurationDao s_configDao;
private static EventDistributor eventDistributor;
@Inject @Inject
UsageEventDao usageEventDao; UsageEventDao usageEventDao;
@ -207,9 +207,9 @@ public class UsageEventUtils {
if( !configValue) if( !configValue)
return; return;
try { try {
s_eventBus = ComponentContext.getComponent(EventBus.class); eventDistributor = ComponentContext.getComponent(EventDistributor.class);
} catch (NoSuchBeanDefinitionException nbe) { } catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return return; // no provider is configured to provide events distributor, so just return
} }
Account account = s_accountDao.findById(accountId); Account account = s_accountDao.findById(accountId);
@ -238,11 +238,7 @@ public class UsageEventUtils {
event.setDescription(eventDescription); event.setDescription(eventDescription);
try { eventDistributor.publish(event);
s_eventBus.publish(event);
} catch (EventBusException e) {
LOGGER.warn("Failed to publish usage event on the event bus.");
}
} }
static final String Name = "management-server"; static final String Name = "management-server";

View File

@ -25,11 +25,9 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.apache.logging.log4j.Logger;
import com.cloud.event.EventCategory; import com.cloud.event.EventCategory;
import com.cloud.network.Network.Event; import com.cloud.network.Network.Event;
@ -43,7 +41,7 @@ public class NetworkStateListener implements StateListener<State, Event, Network
@Inject @Inject
private ConfigurationDao _configDao; private ConfigurationDao _configDao;
private static EventBus s_eventBus = null; private EventDistributor eventDistributor;
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
@ -51,6 +49,10 @@ public class NetworkStateListener implements StateListener<State, Event, Network
_configDao = configDao; _configDao = configDao;
} }
public void setEventDistributor(EventDistributor eventDistributor) {
this.eventDistributor = eventDistributor;
}
@Override @Override
public boolean preStateTransitionEvent(State oldState, Event event, State newState, Network vo, boolean status, Object opaque) { public boolean preStateTransitionEvent(State oldState, Event event, State newState, Network vo, boolean status, Object opaque) {
pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState); pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState);
@ -66,23 +68,20 @@ public class NetworkStateListener implements StateListener<State, Event, Network
return true; return true;
} }
private void pubishOnEventBus(String event, String status, Network vo, State oldState, State newState) { private void pubishOnEventBus(String event, String status, Network vo, State oldState, State newState) {
String configKey = "publish.resource.state.events"; String configKey = "publish.resource.state.events";
String value = _configDao.getValue(configKey); String value = _configDao.getValue(configKey);
boolean configValue = Boolean.parseBoolean(value); boolean configValue = Boolean.parseBoolean(value);
if(!configValue) if(!configValue)
return; return;
try { if (eventDistributor == null) {
s_eventBus = ComponentContext.getComponent(EventBus.class); setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
} catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return
} }
String resourceName = getEntityFromClassName(Network.class.getName()); String resourceName = getEntityFromClassName(Network.class.getName());
org.apache.cloudstack.framework.events.Event eventMsg = org.apache.cloudstack.framework.events.Event eventMsg =
new org.apache.cloudstack.framework.events.Event("management-server", EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName, vo.getUuid()); new org.apache.cloudstack.framework.events.Event("management-server", EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName, vo.getUuid());
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<>();
eventDescription.put("resource", resourceName); eventDescription.put("resource", resourceName);
eventDescription.put("id", vo.getUuid()); eventDescription.put("id", vo.getUuid());
eventDescription.put("old-state", oldState.name()); eventDescription.put("old-state", oldState.name());
@ -92,11 +91,8 @@ public class NetworkStateListener implements StateListener<State, Event, Network
eventDescription.put("eventDateTime", eventDate); eventDescription.put("eventDateTime", eventDate);
eventMsg.setDescription(eventDescription); eventMsg.setDescription(eventDescription);
try {
s_eventBus.publish(eventMsg); eventDistributor.publish(eventMsg);
} catch (EventBusException e) {
logger.warn("Failed to publish state change event on the event bus.");
}
} }
private String getEntityFromClassName(String entityClassName) { private String getEntityFromClassName(String entityClassName) {

View File

@ -0,0 +1,40 @@
// 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 com.cloud.network;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventDistributor;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
public class NetworkStateListenerTest {
@InjectMocks
NetworkStateListener networkStateListener = new NetworkStateListener(Mockito.mock(ConfigurationDao.class));
@Test
public void testSetEventDistributor() {
EventDistributor eventDistributor = null;
networkStateListener.setEventDistributor(eventDistributor);
Assert.assertNull(ReflectionTestUtils.getField(networkStateListener, "eventDistributor"));
eventDistributor = Mockito.mock(EventDistributor.class);
networkStateListener.setEventDistributor(eventDistributor);
Assert.assertEquals(eventDistributor, ReflectionTestUtils.getField(networkStateListener, "eventDistributor"));
}
}

View File

@ -70,7 +70,6 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.network_offerings','nsx_mode', 'varc
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','for_nsx', 'int(1) unsigned DEFAULT "0" COMMENT "is nsx enabled for the resource"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','for_nsx', 'int(1) unsigned DEFAULT "0" COMMENT "is nsx enabled for the resource"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','nsx_mode', 'varchar(32) COMMENT "mode in which the network would route traffic"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','nsx_mode', 'varchar(32) COMMENT "mode in which the network would route traffic"');
-- Create table to persist quota email template configurations -- Create table to persist quota email template configurations
CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`( CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`(
`account_id` int(11) NOT NULL, `account_id` int(11) NOT NULL,
@ -82,3 +81,44 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`(
-- Add `is_implicit` column to `host_tags` table -- Add `is_implicit` column to `host_tags` table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_tags', 'is_implicit', 'int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT "If host tag is implicit or explicit" '); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_tags', 'is_implicit', 'int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT "If host tag is implicit or explicit" ');
-- Webhooks feature
DROP TABLE IF EXISTS `cloud`.`webhook`;
CREATE TABLE `cloud`.`webhook` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the webhook',
`uuid` varchar(255) COMMENT 'uuid of the webhook',
`name` varchar(255) NOT NULL COMMENT 'name of the webhook',
`description` varchar(4096) COMMENT 'description for the webhook',
`state` char(32) NOT NULL COMMENT 'state of the webhook - Enabled or Disabled',
`domain_id` bigint unsigned NOT NULL COMMENT 'id of the owner domain of the webhook',
`account_id` bigint unsigned NOT NULL COMMENT 'id of the owner account of the webhook',
`payload_url` varchar(255) COMMENT 'payload URL for the webhook',
`secret_key` varchar(255) COMMENT 'secret key for the webhook',
`ssl_verification` boolean COMMENT 'for https payload url, if true then strict ssl verification',
`scope` char(32) NOT NULL COMMENT 'scope for the webhook - Local, Domain, Global',
`created` datetime COMMENT 'date the webhook was created',
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY(`id`),
INDEX `i_webhook__account_id`(`account_id`),
CONSTRAINT `fk_webhook__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `cloud`.`webhook_delivery`;
CREATE TABLE `cloud`.`webhook_delivery` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the webhook delivery',
`uuid` varchar(255) COMMENT 'uuid of the webhook',
`event_id` bigint unsigned NOT NULL COMMENT 'id of the event',
`webhook_id` bigint unsigned NOT NULL COMMENT 'id of the webhook',
`mshost_msid` bigint unsigned NOT NULL COMMENT 'msid of the management server',
`headers` TEXT COMMENT 'headers for the webhook delivery',
`payload` TEXT COMMENT 'payload for the webhook delivery',
`success` boolean COMMENT 'webhook delivery succeeded or not',
`response` TEXT COMMENT 'response of the webhook delivery',
`start_time` datetime COMMENT 'start timestamp of the webhook delivery',
`end_time` datetime COMMENT 'end timestamp of the webhook delivery',
PRIMARY KEY(`id`),
INDEX `i_webhook__event_id`(`event_id`),
INDEX `i_webhook__webhook_id`(`webhook_id`),
CONSTRAINT `fk_webhook__event_id` FOREIGN KEY (`event_id`) REFERENCES `event`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_webhook__webhook_id` FOREIGN KEY (`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -0,0 +1,48 @@
-- 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.
-- VIEW `cloud`.`webhook_delivery_view`;
DROP VIEW IF EXISTS `cloud`.`webhook_delivery_view`;
CREATE VIEW `cloud`.`webhook_delivery_view` AS
SELECT
webhook_delivery.id,
webhook_delivery.uuid,
webhook_delivery.headers,
webhook_delivery.payload,
webhook_delivery.success,
webhook_delivery.response,
webhook_delivery.start_time,
webhook_delivery.end_time,
event.id event_id,
event.uuid event_uuid,
event.type event_type,
webhook.id webhook_id,
webhook.uuid webhook_uuid,
webhook.name webhook_name,
mshost.id mshost_id,
mshost.uuid mshost_uuid,
mshost.msid mshost_msid,
mshost.name mshost_name
FROM
`cloud`.`webhook_delivery`
INNER JOIN
`cloud`.`event` ON webhook_delivery.event_id = event.id
INNER JOIN
`cloud`.`webhook` ON webhook_delivery.webhook_id = webhook.id
LEFT JOIN
`cloud`.`mshost` ON mshost.msid = webhook_delivery.mshost_msid;

View File

@ -0,0 +1,52 @@
-- 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.
-- VIEW `cloud`.`webhook_view`;
DROP VIEW IF EXISTS `cloud`.`webhook_view`;
CREATE VIEW `cloud`.`webhook_view` AS
SELECT
webhook.id,
webhook.uuid,
webhook.name,
webhook.description,
webhook.state,
webhook.payload_url,
webhook.secret_key,
webhook.ssl_verification,
webhook.scope,
webhook.created,
webhook.removed,
account.id account_id,
account.uuid account_uuid,
account.account_name account_name,
account.type account_type,
domain.id domain_id,
domain.uuid domain_uuid,
domain.name domain_name,
domain.path domain_path,
projects.id project_id,
projects.uuid project_uuid,
projects.name project_name
FROM
`cloud`.`webhook`
INNER JOIN
`cloud`.`account` ON webhook.account_id = account.id
INNER JOIN
`cloud`.`domain` ON webhook.domain_id = domain.id
LEFT JOIN
`cloud`.`projects` ON projects.project_account_id = webhook.account_id;

View File

@ -20,22 +20,49 @@
package org.apache.cloudstack.framework.events; package org.apache.cloudstack.framework.events;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.annotations.Expose;
public class Event { public class Event {
@Expose(serialize = false, deserialize = false)
Long eventId;
@Expose(serialize = false, deserialize = false)
String eventUuid;
String eventCategory; String eventCategory;
String eventType; String eventType;
String eventSource; String eventSource;
String resourceType; String resourceType;
String resourceUUID; String resourceUUID;
String description; String description;
@Expose(serialize = false, deserialize = false)
Long resourceAccountId;
@Expose(serialize = false, deserialize = false)
String resourceAccountUuid;
@Expose(serialize = false, deserialize = false)
Long resourceDomainId;
public Event(String eventSource, String eventCategory, String eventType, String resourceType, String resourceUUID) { public Event(String eventSource, String eventCategory, String eventType, String resourceType, String resourceUUID) {
this.eventCategory = eventCategory; setEventCategory(eventCategory);
this.eventType = eventType; setEventType(eventType);
this.eventSource = eventSource; setEventSource(eventSource);
this.resourceType = resourceType; setResourceType(resourceType);
this.resourceUUID = resourceUUID; setResourceUUID(resourceUUID);
}
public Long getEventId() {
return eventId;
}
public void setEventId(Long eventId) {
this.eventId = eventId;
}
public String getEventUuid() {
return eventUuid;
}
public void setEventUuid(String eventUuid) {
this.eventUuid = eventUuid;
} }
public String getEventCategory() { public String getEventCategory() {
@ -68,7 +95,7 @@ public class Event {
public void setDescription(Object message) { public void setDescription(Object message) {
Gson gson = new Gson(); Gson gson = new Gson();
this.description = gson.toJson(message).toString(); this.description = gson.toJson(message);
} }
public void setDescription(String description) { public void setDescription(String description) {
@ -90,4 +117,28 @@ public class Event {
public String getResourceUUID() { public String getResourceUUID() {
return resourceUUID; return resourceUUID;
} }
public Long getResourceAccountId() {
return resourceAccountId;
}
public void setResourceAccountId(Long resourceAccountId) {
this.resourceAccountId = resourceAccountId;
}
public String getResourceAccountUuid() {
return resourceAccountUuid;
}
public void setResourceAccountUuid(String resourceAccountUuid) {
this.resourceAccountUuid = resourceAccountUuid;
}
public Long getResourceDomainId() {
return resourceDomainId;
}
public void setResourceDomainId(Long resourceDomainId) {
this.resourceDomainId = resourceDomainId;
}
} }

View File

@ -27,6 +27,8 @@ import java.util.UUID;
*/ */
public interface EventBus { public interface EventBus {
String getName();
/** /**
* publish an event on to the event bus * publish an event on to the event bus
* *

View File

@ -0,0 +1,35 @@
/*
* 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.framework.events;
import java.util.Map;
import com.cloud.utils.component.Manager;
public interface EventDistributor extends Manager {
/**
* Publish an event on to all the available event buses
*
* @param event event that needs to be published on the event bus
* @return Map of bus names and EventBusException for buses that failed with
* exception
*/
Map<String, EventBusException> publish(Event event);
}

View File

@ -0,0 +1,68 @@
/*
* 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.framework.events;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.component.ManagerBase;
public class EventDistributorImpl extends ManagerBase implements EventDistributor {
List<EventBus> eventBuses;
public void setEventBuses(List<EventBus> eventBuses) {
this.eventBuses = eventBuses;
}
@PostConstruct
public void init() {
logger.trace("Found {} event buses : {}", () -> eventBuses.size(),
() -> StringUtils.join(eventBuses.stream().map(x->x.getClass().getName()).toArray()));
}
@Override
public Map<String, EventBusException> publish(Event event) {
Map<String, EventBusException> exceptions = new HashMap<>();
if (event == null) {
return exceptions;
}
logger.trace("Publishing event [category: {}, type: {}]: {} to {} event buses",
event.getEventCategory(), event.getEventType(),
event.getDescription(), eventBuses.size());
for (EventBus bus : eventBuses) {
try {
bus.publish(event);
} catch (EventBusException e) {
logger.warn("Failed to publish event [category: {}, type: {}] on bus {}",
event.getEventCategory(), event.getEventType(), bus.getName());
logger.trace(event.getDescription());
exceptions.put(bus.getName(), e);
}
}
return exceptions;
}
}

View File

@ -0,0 +1,67 @@
// 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.framework.events;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class EventDistributorImplTest {
@InjectMocks
EventDistributorImpl eventDistributor = new EventDistributorImpl();
@Test
public void testSetEventBuses() {
Assert.assertNull(ReflectionTestUtils.getField(eventDistributor, "eventBuses"));
EventBus eventBus = Mockito.mock(EventBus.class);
EventBus eventBus1 = Mockito.mock(EventBus.class);
eventDistributor.setEventBuses(List.of(eventBus, eventBus1));
Assert.assertNotNull(ReflectionTestUtils.getField(eventDistributor, "eventBuses"));
}
@Test
public void testPublishNullEvent() {
Map<String, EventBusException> exceptionMap = eventDistributor.publish(null);
Assert.assertTrue(MapUtils.isEmpty(exceptionMap));
}
@Test
public void testPublishOneReturnsException() throws EventBusException {
String busName = "Test";
EventBus eventBus = Mockito.mock(EventBus.class);
Mockito.doReturn(busName).when(eventBus).getName();
Mockito.doThrow(EventBusException.class).when(eventBus).publish(Mockito.any(Event.class));
EventBus eventBus1 = Mockito.mock(EventBus.class);
Mockito.doNothing().when(eventBus1).publish(Mockito.any(Event.class));
eventDistributor.eventBuses = List.of(eventBus, eventBus1);
Map<String, EventBusException> exceptionMap = eventDistributor.publish(Mockito.mock(Event.class));
Assert.assertTrue(MapUtils.isNotEmpty(exceptionMap));
Assert.assertEquals(1, exceptionMap.size());
Assert.assertTrue(exceptionMap.containsKey(busName));
}
}

View File

@ -16,13 +16,14 @@
// under the License. // under the License.
package org.apache.cloudstack.api.response; package org.apache.cloudstack.api.response;
import com.cloud.serializer.Param; import java.util.HashSet;
import com.google.gson.annotations.SerializedName; import java.util.Set;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.BaseResponse;
import java.util.HashSet; import com.cloud.serializer.Param;
import java.util.Set; import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ApiDiscoveryResponse extends BaseResponse { public class ApiDiscoveryResponse extends BaseResponse {
@ -64,6 +65,18 @@ public class ApiDiscoveryResponse extends BaseResponse {
isAsync = false; isAsync = false;
} }
public ApiDiscoveryResponse(ApiDiscoveryResponse another) {
this.name = another.getName();
this.description = another.getDescription();
this.since = another.getSince();
this.isAsync = another.getAsync();
this.related = another.getRelated();
this.params = new HashSet<>(another.getParams());
this.apiResponse = new HashSet<>(another.getApiResponse());
this.type = another.getType();
this.setObjectName(another.getObjectName());
}
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@ -123,4 +136,8 @@ public class ApiDiscoveryResponse extends BaseResponse {
public Set<ApiResponseResponse> getApiResponse() { public Set<ApiResponseResponse> getApiResponse() {
return apiResponse; return apiResponse;
} }
public String getType() {
return type;
}
} }

View File

@ -16,12 +16,14 @@
// under the License. // under the License.
package org.apache.cloudstack.api.response; package org.apache.cloudstack.api.response;
import com.google.gson.annotations.SerializedName; import java.util.List;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param; import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class ApiParameterResponse extends BaseResponse { public class ApiParameterResponse extends BaseResponse {
@SerializedName(ApiConstants.NAME) @SerializedName(ApiConstants.NAME)
@ -52,6 +54,8 @@ public class ApiParameterResponse extends BaseResponse {
@Param(description = "comma separated related apis to get the parameter") @Param(description = "comma separated related apis to get the parameter")
private String related; private String related;
private transient List<RoleType> authorizedRoleTypes = null;
public ApiParameterResponse() { public ApiParameterResponse() {
} }
@ -87,4 +91,11 @@ public class ApiParameterResponse extends BaseResponse {
this.related = related; this.related = related;
} }
public void setAuthorizedRoleTypes(List<RoleType> authorizedRoleTypes) {
this.authorizedRoleTypes = authorizedRoleTypes;
}
public List<RoleType> getAuthorizedRoleTypes() {
return authorizedRoleTypes;
}
} }

View File

@ -18,8 +18,10 @@ package org.apache.cloudstack.discovery;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -28,21 +30,22 @@ import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd; import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
import org.apache.cloudstack.api.response.ApiDiscoveryResponse; import org.apache.cloudstack.api.response.ApiDiscoveryResponse;
import org.apache.cloudstack.api.response.ApiParameterResponse; import org.apache.cloudstack.api.response.ApiParameterResponse;
import org.apache.cloudstack.api.response.ApiResponseResponse; import org.apache.cloudstack.api.response.ApiResponseResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils; import org.reflections.ReflectionUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -215,6 +218,9 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
paramResponse.setSince(parameterAnnotation.since()); paramResponse.setSince(parameterAnnotation.since());
} }
paramResponse.setRelated(parameterAnnotation.entityType()[0].getName()); paramResponse.setRelated(parameterAnnotation.entityType()[0].getName());
if (parameterAnnotation.authorized() != null) {
paramResponse.setAuthorizedRoleTypes(Arrays.asList(parameterAnnotation.authorized()));
}
response.addParam(paramResponse); response.addParam(paramResponse);
} }
} }
@ -247,6 +253,7 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
if (user == null) if (user == null)
return null; return null;
Account account = accountService.getAccount(user.getAccountId());
if (name != null) { if (name != null) {
if (!s_apiNameDiscoveryResponseMap.containsKey(name)) if (!s_apiNameDiscoveryResponseMap.containsKey(name))
@ -260,10 +267,9 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
return null; return null;
} }
} }
responseList.add(s_apiNameDiscoveryResponseMap.get(name)); responseList.add(getApiDiscoveryResponseWithAccessibleParams(name, account));
} else { } else {
Account account = accountService.getAccount(user.getAccountId());
if (account == null) { if (account == null) {
throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user)); throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user));
} }
@ -284,13 +290,33 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
} }
for (String apiName: apisAllowed) { for (String apiName: apisAllowed) {
responseList.add(s_apiNameDiscoveryResponseMap.get(apiName)); responseList.add(getApiDiscoveryResponseWithAccessibleParams(apiName, account));
} }
} }
response.setResponses(responseList); response.setResponses(responseList);
return response; return response;
} }
private static ApiDiscoveryResponse getApiDiscoveryResponseWithAccessibleParams(String name, Account account) {
if (Account.Type.ADMIN.equals(account.getType())) {
return s_apiNameDiscoveryResponseMap.get(name);
}
ApiDiscoveryResponse apiDiscoveryResponse =
new ApiDiscoveryResponse(s_apiNameDiscoveryResponseMap.get(name));
Iterator<ApiParameterResponse> iterator = apiDiscoveryResponse.getParams().iterator();
while (iterator.hasNext()) {
ApiParameterResponse parameterResponse = iterator.next();
List<RoleType> authorizedRoleTypes = parameterResponse.getAuthorizedRoleTypes();
RoleType accountRoleType = RoleType.getByAccountType(account.getType());
if (CollectionUtils.isNotEmpty(parameterResponse.getAuthorizedRoleTypes()) &&
accountRoleType != null &&
!authorizedRoleTypes.contains(accountRoleType)) {
iterator.remove();
}
}
return apiDiscoveryResponse;
}
@Override @Override
public List<Class<?>> getCommands() { public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>(); List<Class<?>> cmdList = new ArrayList<Class<?>>();

View File

@ -60,6 +60,8 @@ public class InMemoryEventBus extends ManagerBase implements EventBus {
if (subscriber == null || topic == null) { if (subscriber == null || topic == null) {
throw new EventBusException("Invalid EventSubscriber/EventTopic object passed."); throw new EventBusException("Invalid EventSubscriber/EventTopic object passed.");
} }
logger.debug("subscribing '{}' to events of type '{}' from '{}'", subscriber.toString(), topic.getEventType(), topic.getEventSource());
UUID subscriberId = UUID.randomUUID(); UUID subscriberId = UUID.randomUUID();
subscribers.put(subscriberId, new Pair<EventTopic, EventSubscriber>(topic, subscriber)); subscribers.put(subscriberId, new Pair<EventTopic, EventSubscriber>(topic, subscriber));
@ -68,6 +70,7 @@ public class InMemoryEventBus extends ManagerBase implements EventBus {
@Override @Override
public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException { public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException {
logger.debug("unsubscribing '{}'", subscriberId);
if (subscriberId == null) { if (subscriberId == null) {
throw new EventBusException("Cannot unregister a null subscriberId."); throw new EventBusException("Cannot unregister a null subscriberId.");
} }
@ -85,7 +88,9 @@ public class InMemoryEventBus extends ManagerBase implements EventBus {
@Override @Override
public void publish(Event event) throws EventBusException { public void publish(Event event) throws EventBusException {
logger.trace("publish '{}'", event.getDescription());
if (subscribers == null || subscribers.isEmpty()) { if (subscribers == null || subscribers.isEmpty()) {
logger.trace("no subscribers, no publish");
return; // no subscriber to publish to, so just return return; // no subscriber to publish to, so just return
} }

View File

@ -87,19 +87,23 @@ public class KafkaEventBus extends ManagerBase implements EventBus {
@Override @Override
public UUID subscribe(EventTopic topic, EventSubscriber subscriber) throws EventBusException { public UUID subscribe(EventTopic topic, EventSubscriber subscriber) throws EventBusException {
logger.debug("subscribing '{}' to events of type '{}' from '{}'", subscriber.toString(), topic.getEventType(), topic.getEventSource());
/* NOOP */ /* NOOP */
return UUID.randomUUID(); return UUID.randomUUID();
} }
@Override @Override
public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException { public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException {
logger.debug("unsubscribing '{}'", subscriberId);
/* NOOP */ /* NOOP */
} }
@Override @Override
public void publish(Event event) throws EventBusException { public void publish(Event event) throws EventBusException {
ProducerRecord<String, String> record = new ProducerRecord<String,String>(_topic, event.getResourceUUID(), event.getDescription()); logger.trace("publish '{}'", event.getDescription());
_producer.send(record); ProducerRecord<String, String> newRecord = new ProducerRecord<>(_topic, event.getResourceUUID(), event.getDescription());
_producer.send(newRecord);
} }
@Override @Override

View File

@ -185,11 +185,12 @@ public class RabbitMQEventBus extends ManagerBase implements EventBus {
*/ */
@Override @Override
public UUID subscribe(EventTopic topic, EventSubscriber subscriber) throws EventBusException { public UUID subscribe(EventTopic topic, EventSubscriber subscriber) throws EventBusException {
if (subscriber == null || topic == null) { if (subscriber == null || topic == null) {
throw new EventBusException("Invalid EventSubscriber/EventTopic object passed."); throw new EventBusException("Invalid EventSubscriber/EventTopic object passed.");
} }
logger.debug("subscribing '{}' to events of type '{}' from '{}'", subscriber.toString(), topic.getEventType(), topic.getEventSource());
// create a UUID, that will be used for managing subscriptions and also used as queue name // create a UUID, that will be used for managing subscriptions and also used as queue name
// for on the queue used for the subscriber on the AMQP broker // for on the queue used for the subscriber on the AMQP broker
UUID queueId = UUID.randomUUID(); UUID queueId = UUID.randomUUID();
@ -250,6 +251,7 @@ public class RabbitMQEventBus extends ManagerBase implements EventBus {
@Override @Override
public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException { public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException {
logger.debug("unsubscribing '{}'", subscriberId);
try { try {
String classname = subscriber.getClass().getName(); String classname = subscriber.getClass().getName();
String queueName = UUID.nameUUIDFromBytes(classname.getBytes()).toString(); String queueName = UUID.nameUUIDFromBytes(classname.getBytes()).toString();
@ -265,6 +267,7 @@ public class RabbitMQEventBus extends ManagerBase implements EventBus {
// publish event on to the exchange created on AMQP server // publish event on to the exchange created on AMQP server
@Override @Override
public void publish(Event event) throws EventBusException { public void publish(Event event) throws EventBusException {
logger.trace("publish '{}'", event.getDescription());
String routingKey = createRoutingKey(event); String routingKey = createRoutingKey(event);
String eventDescription = event.getDescription(); String eventDescription = event.getDescription();

View File

@ -0,0 +1,46 @@
<!--
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-mom-webhook</artifactId>
<name>Apache CloudStack Plugin - Webhook Event Bus</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.20.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-events</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-engine-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,48 @@
// 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.mom.webhook;
import java.util.Date;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface Webhook extends ControlledEntity, Identity, InternalIdentity {
public static final long ID_DUMMY = 0L;
public static final String NAME_DUMMY = "Test";
enum State {
Enabled, Disabled;
};
enum Scope {
Local, Domain, Global;
};
long getId();
String getName();
String getDescription();
State getState();
long getDomainId();
long getAccountId();
String getPayloadUrl();
String getSecretKey();
boolean isSslVerification();
Scope getScope();
Date getCreated();
}

View File

@ -0,0 +1,44 @@
// 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.mom.webhook;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.mom.webhook.api.command.user.CreateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ExecuteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookDeliveriesCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhooksCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.UpdateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.exception.CloudRuntimeException;
public interface WebhookApiService extends PluggableService {
ListResponse<WebhookResponse> listWebhooks(ListWebhooksCmd cmd);
WebhookResponse createWebhook(CreateWebhookCmd cmd) throws CloudRuntimeException;
boolean deleteWebhook(DeleteWebhookCmd cmd) throws CloudRuntimeException;
WebhookResponse updateWebhook(UpdateWebhookCmd cmd) throws CloudRuntimeException;
WebhookResponse createWebhookResponse(long webhookId);
ListResponse<WebhookDeliveryResponse> listWebhookDeliveries(ListWebhookDeliveriesCmd cmd);
int deleteWebhookDelivery(DeleteWebhookDeliveryCmd cmd) throws CloudRuntimeException;
WebhookDeliveryResponse executeWebhookDelivery(ExecuteWebhookDeliveryCmd cmd) throws CloudRuntimeException;
}

View File

@ -0,0 +1,574 @@
// 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.mom.webhook;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.api.command.user.CreateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ExecuteWebhookDeliveryCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhookDeliveriesCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.ListWebhooksCmd;
import org.apache.cloudstack.mom.webhook.api.command.user.UpdateWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.apache.cloudstack.mom.webhook.dao.WebhookDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryJoinDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookJoinDao;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryJoinVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookJoinVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.api.ApiResponseHelper;
import com.cloud.cluster.ManagementServerHostVO;
import com.cloud.cluster.dao.ManagementServerHostDao;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.projects.Project;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.UriUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.rest.HttpConstants;
public class WebhookApiServiceImpl extends ManagerBase implements WebhookApiService {
@Inject
AccountManager accountManager;
@Inject
DomainDao domainDao;
@Inject
WebhookDao webhookDao;
@Inject
WebhookJoinDao webhookJoinDao;
@Inject
WebhookDeliveryDao webhookDeliveryDao;
@Inject
WebhookDeliveryJoinDao webhookDeliveryJoinDao;
@Inject
ManagementServerHostDao managementServerHostDao;
@Inject
WebhookService webhookService;
protected WebhookResponse createWebhookResponse(WebhookJoinVO webhookVO) {
WebhookResponse response = new WebhookResponse();
response.setObjectName("webhook");
response.setId(webhookVO.getUuid());
response.setName(webhookVO.getName());
response.setDescription(webhookVO.getDescription());
ApiResponseHelper.populateOwner(response, webhookVO);
response.setState(webhookVO.getState().toString());
response.setPayloadUrl(webhookVO.getPayloadUrl());
response.setSecretKey(webhookVO.getSecretKey());
response.setSslVerification(webhookVO.isSslVerification());
response.setScope(webhookVO.getScope().toString());
response.setCreated(webhookVO.getCreated());
return response;
}
protected List<Long> getIdsOfAccessibleWebhooks(Account caller) {
if (Account.Type.ADMIN.equals(caller.getType())) {
return new ArrayList<>();
}
String domainPath = null;
if (Account.Type.DOMAIN_ADMIN.equals(caller.getType())) {
Domain domain = domainDao.findById(caller.getDomainId());
domainPath = domain.getPath();
}
List<WebhookJoinVO> webhooks = webhookJoinDao.listByAccountOrDomain(caller.getId(), domainPath);
return webhooks.stream().map(WebhookJoinVO::getId).collect(Collectors.toList());
}
protected ManagementServerHostVO basicWebhookDeliveryApiCheck(Account caller, final Long id, final Long webhookId,
final Long managementServerId, final Date startDate, final Date endDate) {
if (id != null) {
WebhookDeliveryVO webhookDeliveryVO = webhookDeliveryDao.findById(id);
if (webhookDeliveryVO == null) {
throw new InvalidParameterValueException("Invalid ID specified");
}
WebhookVO webhookVO = webhookDao.findById(webhookDeliveryVO.getWebhookId());
if (webhookVO != null) {
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
}
}
if (webhookId != null) {
WebhookVO webhookVO = webhookDao.findById(webhookId);
if (webhookVO == null) {
throw new InvalidParameterValueException("Invalid Webhook specified");
}
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhookVO);
}
if (endDate != null && startDate != null && endDate.before(startDate)) {
throw new InvalidParameterValueException(String.format("Invalid %s specified", ApiConstants.END_DATE));
}
ManagementServerHostVO managementServerHostVO = null;
if (managementServerId != null) {
if (!Account.Type.ADMIN.equals(caller.getType())) {
throw new PermissionDeniedException("Invalid parameter specified");
}
managementServerHostVO = managementServerHostDao.findById(managementServerId);
if (managementServerHostVO == null) {
throw new InvalidParameterValueException("Invalid management server specified");
}
}
return managementServerHostVO;
}
protected WebhookDeliveryResponse createWebhookDeliveryResponse(WebhookDeliveryJoinVO webhookDeliveryVO) {
WebhookDeliveryResponse response = new WebhookDeliveryResponse();
response.setObjectName(WebhookDelivery.class.getSimpleName().toLowerCase());
response.setId(webhookDeliveryVO.getUuid());
response.setEventId(webhookDeliveryVO.getEventUuid());
response.setEventType(webhookDeliveryVO.getEventType());
response.setWebhookId(webhookDeliveryVO.getWebhookUuId());
response.setWebhookName(webhookDeliveryVO.getWebhookName());
response.setManagementServerId(webhookDeliveryVO.getManagementServerUuId());
response.setManagementServerName(webhookDeliveryVO.getManagementServerName());
response.setHeaders(webhookDeliveryVO.getHeaders());
response.setPayload(webhookDeliveryVO.getPayload());
response.setSuccess(webhookDeliveryVO.isSuccess());
response.setResponse(webhookDeliveryVO.getResponse());
response.setStartTime(webhookDeliveryVO.getStartTime());
response.setEndTime(webhookDeliveryVO.getEndTime());
return response;
}
protected WebhookDeliveryResponse createTestWebhookDeliveryResponse(WebhookDelivery webhookDelivery,
Webhook webhook) {
WebhookDeliveryResponse response = new WebhookDeliveryResponse();
response.setObjectName(WebhookDelivery.class.getSimpleName().toLowerCase());
response.setId(webhookDelivery.getUuid());
response.setEventType(WebhookDelivery.TEST_EVENT_TYPE);
if (webhook != null) {
response.setWebhookId(webhook.getUuid());
response.setWebhookName(webhook.getName());
}
ManagementServerHostVO msHost =
managementServerHostDao.findByMsid(webhookDelivery.getManagementServerId());
if (msHost != null) {
response.setManagementServerId(msHost.getUuid());
response.setManagementServerName(msHost.getName());
}
response.setHeaders(webhookDelivery.getHeaders());
response.setPayload(webhookDelivery.getPayload());
response.setSuccess(webhookDelivery.isSuccess());
response.setResponse(webhookDelivery.getResponse());
response.setStartTime(webhookDelivery.getStartTime());
response.setEndTime(webhookDelivery.getEndTime());
return response;
}
/**
* @param cmd
* @return Account
*/
protected Account getOwner(final CreateWebhookCmd cmd) {
final Account caller = CallContext.current().getCallingAccount();
return accountManager.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId());
}
protected String getNormalizedPayloadUrl(String payloadUrl) {
if (StringUtils.isBlank(payloadUrl) || payloadUrl.startsWith("http://") || payloadUrl.startsWith("https://")) {
return payloadUrl;
}
return String.format("http://%s", payloadUrl);
}
protected void validateWebhookOwnerPayloadUrl(Account owner, String payloadUrl, Webhook currentWebhook) {
WebhookVO webhookVO = webhookDao.findByAccountAndPayloadUrl(owner.getId(), payloadUrl);
if (webhookVO == null) {
return;
}
if (currentWebhook != null && webhookVO.getId() == currentWebhook.getId()) {
return;
}
String error = String.format("Payload URL: %s is already in use by another webhook", payloadUrl);
logger.error(String.format("%s: %s for Account [%s]", error, webhookVO, owner));
throw new InvalidParameterValueException(error);
}
@Override
public ListResponse<WebhookResponse> listWebhooks(ListWebhooksCmd cmd) {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Long clusterId = cmd.getId();
final String stateStr = cmd.getState();
final String name = cmd.getName();
final String keyword = cmd.getKeyword();
final String scopeStr = cmd.getScope();
List<WebhookResponse> responsesList = new ArrayList<>();
List<Long> permittedAccounts = new ArrayList<>();
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject =
new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
accountManager.buildACLSearchParameters(caller, clusterId, cmd.getAccountName(), cmd.getProjectId(),
permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(WebhookJoinVO.class, "id", true, cmd.getStartIndex(),
cmd.getPageSizeVal());
SearchBuilder<WebhookJoinVO> sb = webhookJoinDao.createSearchBuilder();
accountManager.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts,
listProjectResourcesCriteria);
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("keyword", sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and("scope", sb.entity().getScope(), SearchCriteria.Op.EQ);
SearchCriteria<WebhookJoinVO> sc = sb.create();
accountManager.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts,
listProjectResourcesCriteria);
Webhook.Scope scope = null;
if (StringUtils.isNotEmpty(scopeStr)) {
try {
scope = Webhook.Scope.valueOf(scopeStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(caller.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(caller.getType()))) {
throw new InvalidParameterValueException(String.format("Scope %s can not be specified", scope));
}
Webhook.State state = null;
if (StringUtils.isNotEmpty(stateStr)) {
try {
state = Webhook.State.valueOf(stateStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
if (scope != null) {
sc.setParameters("scope", scope.name());
}
if (state != null) {
sc.setParameters("state", state.name());
}
if(keyword != null){
sc.setParameters("keyword", "%" + keyword + "%");
}
if (clusterId != null) {
sc.setParameters("id", clusterId);
}
if (name != null) {
sc.setParameters("name", name);
}
Pair<List<WebhookJoinVO>, Integer> webhooksAndCount = webhookJoinDao.searchAndCount(sc, searchFilter);
for (WebhookJoinVO webhook : webhooksAndCount.first()) {
WebhookResponse response = createWebhookResponse(webhook);
responsesList.add(response);
}
ListResponse<WebhookResponse> response = new ListResponse<>();
response.setResponses(responsesList, webhooksAndCount.second());
return response;
}
@Override
public WebhookResponse createWebhook(CreateWebhookCmd cmd) throws CloudRuntimeException {
final Account owner = getOwner(cmd);
final String name = cmd.getName();
final String description = cmd.getDescription();
final String payloadUrl = getNormalizedPayloadUrl(cmd.getPayloadUrl());
final String secretKey = cmd.getSecretKey();
final boolean sslVerification = cmd.isSslVerification();
final String scopeStr = cmd.getScope();
final String stateStr = cmd.getState();
Webhook.Scope scope = Webhook.Scope.Local;
if (StringUtils.isNotEmpty(scopeStr)) {
try {
scope = Webhook.Scope.valueOf(scopeStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(owner.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()))) {
throw new InvalidParameterValueException(
String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
}
Webhook.State state = Webhook.State.Enabled;
if (StringUtils.isNotEmpty(stateStr)) {
try {
state = Webhook.State.valueOf(stateStr);
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
UriUtils.validateUrl(payloadUrl);
validateWebhookOwnerPayloadUrl(owner, payloadUrl, null);
URI uri = URI.create(payloadUrl);
if (sslVerification && !HttpConstants.HTTPS.equalsIgnoreCase(uri.getScheme())) {
throw new InvalidParameterValueException(
String.format("SSL verification can be specified only for HTTPS URLs, %s", payloadUrl));
}
long domainId = owner.getDomainId();
Long cmdDomainId = cmd.getDomainId();
if (cmdDomainId != null &&
List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()) &&
Webhook.Scope.Domain.equals(scope)) {
domainId = cmdDomainId;
}
WebhookVO webhook = new WebhookVO(name, description, state, domainId, owner.getId(), payloadUrl, secretKey,
sslVerification, scope);
webhook = webhookDao.persist(webhook);
return createWebhookResponse(webhook.getId());
}
@Override
public boolean deleteWebhook(DeleteWebhookCmd cmd) throws CloudRuntimeException {
final Account caller = CallContext.current().getCallingAccount();
final long id = cmd.getId();
Webhook webhook = webhookDao.findById(id);
if (webhook == null) {
throw new InvalidParameterValueException("Unable to find the webhook with the specified ID");
}
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
return webhookDao.remove(id);
}
@Override
public WebhookResponse updateWebhook(UpdateWebhookCmd cmd) throws CloudRuntimeException {
final Account caller = CallContext.current().getCallingAccount();
final long id = cmd.getId();
final String name = cmd.getName();
final String description = cmd.getDescription();
final String payloadUrl = getNormalizedPayloadUrl(cmd.getPayloadUrl());
String secretKey = cmd.getSecretKey();
final Boolean sslVerification = cmd.isSslVerification();
final String scopeStr = cmd.getScope();
final String stateStr = cmd.getState();
WebhookVO webhook = webhookDao.findById(id);
if (webhook == null) {
throw new InvalidParameterValueException("Unable to find the webhook with the specified ID");
}
accountManager.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, false, webhook);
boolean updateNeeded = false;
if (StringUtils.isNotBlank(name)) {
webhook.setName(name);
updateNeeded = true;
}
if (description != null) {
webhook.setDescription(description);
updateNeeded = true;
}
if (StringUtils.isNotEmpty(stateStr)) {
try {
Webhook.State state = Webhook.State.valueOf(stateStr);
webhook.setState(state);
updateNeeded = true;
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid state specified");
}
}
Account owner = accountManager.getAccount(webhook.getAccountId());
if (StringUtils.isNotEmpty(scopeStr)) {
try {
Webhook.Scope scope = Webhook.Scope.valueOf(scopeStr);
if ((Webhook.Scope.Global.equals(scope) && !Account.Type.ADMIN.equals(owner.getType())) ||
(Webhook.Scope.Domain.equals(scope) &&
!List.of(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN).contains(owner.getType()))) {
throw new InvalidParameterValueException(
String.format("Scope %s can not be specified for owner %s", scope, owner.getName()));
}
webhook.setScope(scope);
updateNeeded = true;
} catch (IllegalArgumentException iae) {
throw new InvalidParameterValueException("Invalid scope specified");
}
}
URI uri = URI.create(webhook.getPayloadUrl());
if (StringUtils.isNotEmpty(payloadUrl)) {
UriUtils.validateUrl(payloadUrl);
validateWebhookOwnerPayloadUrl(owner, payloadUrl, webhook);
uri = URI.create(payloadUrl);
webhook.setPayloadUrl(payloadUrl);
updateNeeded = true;
}
if (sslVerification != null) {
if (Boolean.TRUE.equals(sslVerification) && !HttpConstants.HTTPS.equalsIgnoreCase(uri.getScheme())) {
throw new InvalidParameterValueException(
String.format("SSL verification can be specified only for HTTPS URLs, %s", payloadUrl));
}
webhook.setSslVerification(sslVerification);
updateNeeded = true;
}
if (secretKey != null) {
if (StringUtils.isBlank(secretKey)) {
secretKey = null;
}
webhook.setSecretKey(secretKey);
updateNeeded = true;
}
if (updateNeeded && !webhookDao.update(id, webhook)) {
return null;
}
return createWebhookResponse(webhook.getId());
}
@Override
public WebhookResponse createWebhookResponse(long webhookId) {
WebhookJoinVO webhookVO = webhookJoinDao.findById(webhookId);
return createWebhookResponse(webhookVO);
}
@Override
public ListResponse<WebhookDeliveryResponse> listWebhookDeliveries(ListWebhookDeliveriesCmd cmd) {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Long id = cmd.getId();
final Long webhookId = cmd.getWebhookId();
final Long managementServerId = cmd.getManagementServerId();
final String keyword = cmd.getKeyword();
final Date startDate = cmd.getStartDate();
final Date endDate = cmd.getEndDate();
final String eventType = cmd.getEventType();
List<WebhookDeliveryResponse> responsesList = new ArrayList<>();
ManagementServerHostVO host = basicWebhookDeliveryApiCheck(caller, id, webhookId, managementServerId,
startDate, endDate);
Filter searchFilter = new Filter(WebhookDeliveryJoinVO.class, "id", false, cmd.getStartIndex(),
cmd.getPageSizeVal());
List<Long> webhookIds = new ArrayList<>();
if (webhookId != null) {
webhookIds.add(webhookId);
} else {
webhookIds.addAll(getIdsOfAccessibleWebhooks(caller));
}
Pair<List<WebhookDeliveryJoinVO>, Integer> deliveriesAndCount =
webhookDeliveryJoinDao.searchAndCountByListApiParameters(id, webhookIds,
(host != null ? host.getMsid() : null), keyword, startDate, endDate, eventType, searchFilter);
for (WebhookDeliveryJoinVO delivery : deliveriesAndCount.first()) {
WebhookDeliveryResponse response = createWebhookDeliveryResponse(delivery);
responsesList.add(response);
}
ListResponse<WebhookDeliveryResponse> response = new ListResponse<>();
response.setResponses(responsesList, deliveriesAndCount.second());
return response;
}
@Override
public int deleteWebhookDelivery(DeleteWebhookDeliveryCmd cmd) throws CloudRuntimeException {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Long id = cmd.getId();
final Long webhookId = cmd.getWebhookId();
final Long managementServerId = cmd.getManagementServerId();
final Date startDate = cmd.getStartDate();
final Date endDate = cmd.getEndDate();
ManagementServerHostVO host = basicWebhookDeliveryApiCheck(caller, id, webhookId, managementServerId,
startDate, endDate);
int removed = webhookDeliveryDao.deleteByDeleteApiParams(id, webhookId,
(host != null ? host.getMsid() : null), startDate, endDate);
logger.info("{} webhook deliveries removed", removed);
return removed;
}
@Override
public WebhookDeliveryResponse executeWebhookDelivery(ExecuteWebhookDeliveryCmd cmd) throws CloudRuntimeException {
final CallContext ctx = CallContext.current();
final Account caller = ctx.getCallingAccount();
final Long deliveryId = cmd.getId();
final Long webhookId = cmd.getWebhookId();
final String payloadUrl = getNormalizedPayloadUrl(cmd.getPayloadUrl());
final String secretKey = cmd.getSecretKey();
final Boolean sslVerification = cmd.isSslVerification();
final String payload = cmd.getPayload();
final Account owner = accountManager.finalizeOwner(caller, null, null, null);
if (ObjectUtils.allNull(deliveryId, webhookId) && StringUtils.isBlank(payloadUrl)) {
throw new InvalidParameterValueException(String.format("One of the %s, %s or %s must be specified",
ApiConstants.ID, ApiConstants.WEBHOOK_ID, ApiConstants.PAYLOAD_URL));
}
WebhookDeliveryVO existingDelivery = null;
WebhookVO webhook = null;
if (deliveryId != null) {
existingDelivery = webhookDeliveryDao.findById(deliveryId);
if (existingDelivery == null) {
throw new InvalidParameterValueException("Invalid webhook delivery specified");
}
webhook = webhookDao.findById(existingDelivery.getWebhookId());
}
if (StringUtils.isNotBlank(payloadUrl)) {
UriUtils.validateUrl(payloadUrl);
}
if (webhookId != null) {
webhook = webhookDao.findById(webhookId);
if (webhook == null) {
throw new InvalidParameterValueException("Invalid webhook specified");
}
if (StringUtils.isNotBlank(payloadUrl)) {
webhook.setPayloadUrl(payloadUrl);
}
if (StringUtils.isNotBlank(secretKey)) {
webhook.setSecretKey(secretKey);
}
if (sslVerification != null) {
webhook.setSslVerification(Boolean.TRUE.equals(sslVerification));
}
}
if (ObjectUtils.allNull(deliveryId, webhookId)) {
webhook = new WebhookVO(owner.getDomainId(), owner.getId(), payloadUrl, secretKey,
Boolean.TRUE.equals(sslVerification));
}
WebhookDelivery webhookDelivery = webhookService.executeWebhookDelivery(existingDelivery, webhook, payload);
if (webhookDelivery.getId() != WebhookDelivery.ID_DUMMY) {
return createWebhookDeliveryResponse(webhookDeliveryJoinDao.findById(webhookDelivery.getId()));
}
return createTestWebhookDeliveryResponse(webhookDelivery, webhook);
}
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(CreateWebhookCmd.class);
cmdList.add(ListWebhooksCmd.class);
cmdList.add(UpdateWebhookCmd.class);
cmdList.add(DeleteWebhookCmd.class);
cmdList.add(ListWebhookDeliveriesCmd.class);
cmdList.add(DeleteWebhookDeliveryCmd.class);
cmdList.add(ExecuteWebhookDeliveryCmd.class);
return cmdList;
}
}

View File

@ -0,0 +1,39 @@
// 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.mom.webhook;
import java.util.Date;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface WebhookDelivery extends Identity, InternalIdentity {
public static final long ID_DUMMY = 0L;
public static final String TEST_EVENT_TYPE = "TEST.WEBHOOK";
long getId();
long getEventId();
long getWebhookId();
long getManagementServerId();
String getHeaders();
String getPayload();
boolean isSuccess();
String getResponse();
Date getStartTime();
Date getEndTime();
}

View File

@ -0,0 +1,287 @@
// 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.mom.webhook;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class WebhookDeliveryThread implements Runnable {
protected static Logger LOGGER = LogManager.getLogger(WebhookDeliveryThread.class);
private static final String HEADER_X_CS_EVENT_ID = "X-CS-Event-ID";
private static final String HEADER_X_CS_EVENT = "X-CS-Event";
private static final String HEADER_X_CS_SIGNATURE = "X-CS-Signature";
private static final String PREFIX_HEADER_USER_AGENT = "CS-Hookshot/";
private final Webhook webhook;
private final Event event;
private CloseableHttpClient httpClient;
private String headers;
private String payload;
private String response;
private Date startTime;
private int deliveryTries = 3;
private int deliveryTimeout = 10;
AsyncCompletionCallback<WebhookDeliveryResult> callback;
protected boolean isValidJson(String json) {
try {
new JSONObject(json);
} catch (JSONException ex) {
try {
new JSONArray(json);
} catch (JSONException ex1) {
return false;
}
}
return true;
}
protected void setHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if (webhook.isSslVerification()) {
httpClient = HttpClients.createDefault();
return;
}
httpClient = HttpClients
.custom()
.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null,
TrustAllStrategy.INSTANCE).build())
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
}
protected HttpPost getBasicHttpPostRequest() throws URISyntaxException {
final URI uri = new URI(webhook.getPayloadUrl());
HttpPost request = new HttpPost();
RequestConfig.Builder requestConfig = RequestConfig.custom();
requestConfig.setConnectTimeout(deliveryTimeout * 1000);
requestConfig.setConnectionRequestTimeout(deliveryTimeout * 1000);
requestConfig.setSocketTimeout(deliveryTimeout * 1000);
request.setConfig(requestConfig.build());
request.setURI(uri);
return request;
}
protected void updateRequestHeaders(HttpPost request) throws DecoderException, NoSuchAlgorithmException,
InvalidKeyException {
request.addHeader(HEADER_X_CS_EVENT_ID, event.getEventUuid());
request.addHeader(HEADER_X_CS_EVENT, event.getEventType());
request.setHeader(HttpHeaders.USER_AGENT, String.format("%s%s", PREFIX_HEADER_USER_AGENT,
event.getResourceAccountUuid()));
if (StringUtils.isNotBlank(webhook.getSecretKey())) {
request.addHeader(HEADER_X_CS_SIGNATURE, generateHMACSignature(payload, webhook.getSecretKey()));
}
List<Header> headers = new ArrayList<>(Arrays.asList(request.getAllHeaders()));
HttpEntity entity = request.getEntity();
if (entity.getContentLength() > 0 && !request.containsHeader(HttpHeaders.CONTENT_LENGTH)) {
headers.add(new BasicHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(entity.getContentLength())));
}
if (entity.getContentType() != null && !request.containsHeader(HttpHeaders.CONTENT_TYPE)) {
headers.add(entity.getContentType());
}
if (entity.getContentEncoding() != null && !request.containsHeader(HttpHeaders.CONTENT_ENCODING)) {
headers.add(entity.getContentEncoding());
}
this.headers = StringUtils.join(headers, "\n");
}
public WebhookDeliveryThread(Webhook webhook, Event event,
AsyncCompletionCallback<WebhookDeliveryResult> callback) {
this.webhook = webhook;
this.event = event;
this.callback = callback;
}
public void setDeliveryTries(int deliveryTries) {
this.deliveryTries = deliveryTries;
}
public void setDeliveryTimeout(int deliveryTimeout) {
this.deliveryTimeout = deliveryTimeout;
}
@Override
public void run() {
LOGGER.debug("Delivering event: {} for {}", event.getEventType(), webhook);
if (event == null) {
LOGGER.warn("Invalid event received for delivering to {}", webhook);
return;
}
payload = event.getDescription();
LOGGER.trace("Payload: {}", payload);
int attempt = 0;
boolean success = false;
try {
setHttpClient();
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
response = String.format("Failed to initiate delivery due to : %s", e.getMessage());
callback.complete(new WebhookDeliveryResult(headers, payload, success, response, new Date()));
return;
}
while (attempt < deliveryTries) {
attempt++;
if (delivery(attempt)) {
success = true;
break;
}
}
callback.complete(new WebhookDeliveryResult(headers, payload, success, response, startTime));
}
protected void updateResponseFromRequest(HttpEntity entity) {
try {
this.response = EntityUtils.toString(entity, StandardCharsets.UTF_8);
} catch (IOException e) {
LOGGER.error("Failed to parse response for event: {} for {}",
event.getEventType(), webhook);
this.response = "";
}
}
protected boolean delivery(int attempt) {
startTime = new Date();
try {
HttpPost request = getBasicHttpPostRequest();
StringEntity input = new StringEntity(payload,
isValidJson(payload) ? ContentType.APPLICATION_JSON : ContentType.TEXT_PLAIN);
request.setEntity(input);
updateRequestHeaders(request);
LOGGER.trace("Delivering event: {} for {} with timeout: {}, " +
"attempt #{}", event.getEventType(), webhook,
deliveryTimeout, attempt);
final CloseableHttpResponse response = httpClient.execute(request);
updateResponseFromRequest(response.getEntity());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
LOGGER.trace("Successfully delivered event: {} for {}",
event.getEventType(), webhook);
return true;
}
} catch (URISyntaxException | IOException | DecoderException | NoSuchAlgorithmException |
InvalidKeyException e) {
LOGGER.warn("Failed to deliver {}, in attempt #{} due to: {}",
webhook, attempt, e.getMessage());
response = String.format("Failed due to : %s", e.getMessage());
}
return false;
}
public static String generateHMACSignature(String data, String key)
throws InvalidKeyException, NoSuchAlgorithmException, DecoderException {
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
mac.init(secretKey);
byte[] dataAsBytes = data.getBytes(StandardCharsets.UTF_8);
byte[] encodedText = mac.doFinal(dataAsBytes);
return new String(Base64.encodeBase64(encodedText)).trim();
}
public static class WebhookDeliveryContext<T> extends AsyncRpcContext<T> {
private final Long eventId;
private final Long ruleId;
public WebhookDeliveryContext(AsyncCompletionCallback<T> callback, Long eventId, Long ruleId) {
super(callback);
this.eventId = eventId;
this.ruleId = ruleId;
}
public Long getEventId() {
return eventId;
}
public Long getRuleId() {
return ruleId;
}
}
public static class WebhookDeliveryResult extends CommandResult {
private final String headers;
private final String payload;
private final Date starTime;
private final Date endTime;
public WebhookDeliveryResult(String headers, String payload, boolean success, String response, Date starTime) {
super();
this.headers = headers;
this.payload = payload;
this.setResult(response);
this.setSuccess(success);
this.starTime = starTime;
this.endTime = new Date();
}
public String getHeaders() {
return headers;
}
public String getPayload() {
return payload;
}
public Date getStarTime() {
return starTime;
}
public Date getEndTime() {
return endTime;
}
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.mom.webhook;
import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBus;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.framework.events.EventSubscriber;
import org.apache.cloudstack.framework.events.EventTopic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.utils.component.ManagerBase;
import com.google.gson.Gson;
public class WebhookEventBus extends ManagerBase implements EventBus {
protected static Logger LOGGER = LogManager.getLogger(WebhookEventBus.class);
private static Gson gson;
@Inject
WebhookService webhookService;
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_name = name;
return true;
}
@Override
public void setName(String name) {
_name = name;
}
@Override
public UUID subscribe(EventTopic topic, EventSubscriber subscriber) throws EventBusException {
/* NOOP */
return UUID.randomUUID();
}
@Override
public void unsubscribe(UUID subscriberId, EventSubscriber subscriber) throws EventBusException {
/* NOOP */
}
@Override
public void publish(Event event) throws EventBusException {
webhookService.handleEvent(event);
}
@Override
public String getName() {
return _name;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
}

View File

@ -0,0 +1,63 @@
// 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.mom.webhook;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBusException;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.exception.CloudRuntimeException;
public interface WebhookService extends PluggableService, Configurable {
ConfigKey<Integer> WebhookDeliveryTimeout = new ConfigKey<>("Advanced", Integer.class,
"webhook.delivery.timeout", "10",
"Wait timeout (in seconds) for a webhook delivery delivery",
true, ConfigKey.Scope.Domain);
ConfigKey<Integer> WebhookDeliveryTries = new ConfigKey<>("Advanced", Integer.class,
"webhook.delivery.tries", "3",
"Number of tries to be made for a webhook delivery",
true, ConfigKey.Scope.Domain);
ConfigKey<Integer> WebhookDeliveryThreadPoolSize = new ConfigKey<>("Advanced", Integer.class,
"webhook.delivery.thread.pool.size", "5",
"Size of the thread pool for webhook deliveries",
false, ConfigKey.Scope.Global);
ConfigKey<Integer> WebhookDeliveriesLimit = new ConfigKey<>("Advanced", Integer.class,
"webhook.deliveries.limit", "10",
"Limit for the number of deliveries to keep in DB per webhook",
true, ConfigKey.Scope.Global);
ConfigKey<Integer> WebhookDeliveriesCleanupInitialDelay = new ConfigKey<>("Advanced", Integer.class,
"webhook.deliveries.cleanup.initial.delay", "180",
"Initial delay (in seconds) for webhook deliveries cleanup task",
false, ConfigKey.Scope.Global);
ConfigKey<Integer> WebhookDeliveriesCleanupInterval = new ConfigKey<>("Advanced", Integer.class,
"webhook.deliveries.cleanup.interval", "3600",
"Interval (in seconds) for cleaning up webhook deliveries",
false, ConfigKey.Scope.Global);
void handleEvent(Event event) throws EventBusException;
WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
throws CloudRuntimeException;
}

View File

@ -0,0 +1,354 @@
// 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.mom.webhook;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.mom.webhook.dao.WebhookDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryDao;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.lang3.StringUtils;
import com.cloud.api.query.vo.EventJoinVO;
import com.cloud.cluster.ManagementServerHostVO;
import com.cloud.cluster.dao.ManagementServerHostDao;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.EventCategory;
import com.cloud.event.dao.EventJoinDao;
import com.cloud.server.ManagementService;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
public class WebhookServiceImpl extends ManagerBase implements WebhookService, WebhookHelper {
public static final String WEBHOOK_JOB_POOL_THREAD_PREFIX = "Webhook-Job-Executor";
private ExecutorService webhookJobExecutor;
private ScheduledExecutorService webhookDeliveriesCleanupExecutor;
@Inject
EventJoinDao eventJoinDao;
@Inject
WebhookDao webhookDao;
@Inject
protected WebhookDeliveryDao webhookDeliveryDao;
@Inject
ManagementServerHostDao managementServerHostDao;
@Inject
DomainDao domainDao;
@Inject
AccountManager accountManager;
protected WebhookDeliveryThread getDeliveryJob(Event event, Webhook webhook, Pair<Integer, Integer> configs) {
WebhookDeliveryThread.WebhookDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context =
new WebhookDeliveryThread.WebhookDeliveryContext<>(null, event.getEventId(), webhook.getId());
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> caller =
AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().deliveryCompleteCallback(null, null))
.setContext(context);
WebhookDeliveryThread job = new WebhookDeliveryThread(webhook, event, caller);
job = ComponentContext.inject(job);
job.setDeliveryTries(configs.first());
job.setDeliveryTimeout(configs.second());
return job;
}
protected List<Runnable> getDeliveryJobs(Event event) throws EventBusException {
List<Runnable> jobs = new ArrayList<>();
if (!EventCategory.ACTION_EVENT.getName().equals(event.getEventCategory())) {
return jobs;
}
if (event.getResourceAccountId() == null) {
logger.warn("Skipping delivering event [ID: {}, description: {}] to any webhook as account ID is missing",
event.getEventId(), event.getDescription());
throw new EventBusException(String.format("Account missing for the event ID: %s", event.getEventUuid()));
}
List<Long> domainIds = new ArrayList<>();
if (event.getResourceDomainId() != null) {
domainIds.add(event.getResourceDomainId());
domainIds.addAll(domainDao.getDomainParentIds(event.getResourceDomainId()));
}
List<WebhookVO> webhooks =
webhookDao.listByEnabledForDelivery(event.getResourceAccountId(), domainIds);
Map<Long, Pair<Integer, Integer>> domainConfigs = new HashMap<>();
for (WebhookVO webhook : webhooks) {
if (!domainConfigs.containsKey(webhook.getDomainId())) {
domainConfigs.put(webhook.getDomainId(),
new Pair<>(WebhookDeliveryTries.valueIn(webhook.getDomainId()),
WebhookDeliveryTimeout.valueIn(webhook.getDomainId())));
}
Pair<Integer, Integer> configs = domainConfigs.get(webhook.getDomainId());
WebhookDeliveryThread job = getDeliveryJob(event, webhook, configs);
jobs.add(job);
}
return jobs;
}
protected Runnable getManualDeliveryJob(WebhookDelivery existingDelivery, Webhook webhook, String payload,
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
if (StringUtils.isBlank(payload)) {
payload = "{ \"CloudStack\": \"works!\" }";
}
long eventId = Webhook.ID_DUMMY;
String eventType = WebhookDelivery.TEST_EVENT_TYPE;
String eventUuid = UUID.randomUUID().toString();
String description = payload;
String resourceAccountUuid = null;
if (existingDelivery != null) {
EventJoinVO eventJoinVO = eventJoinDao.findById(existingDelivery.getEventId());
eventId = eventJoinVO.getId();
eventType = eventJoinVO.getType();
eventUuid = eventJoinVO.getUuid();
description = existingDelivery.getPayload();
resourceAccountUuid = eventJoinVO.getAccountUuid();
} else {
Account account = accountManager.getAccount(webhook.getAccountId());
resourceAccountUuid = account.getUuid();
}
Event event = new Event(ManagementService.Name, EventCategory.ACTION_EVENT.toString(),
eventType, null, null);
event.setEventId(eventId);
event.setEventUuid(eventUuid);
event.setDescription(description);
event.setResourceAccountUuid(resourceAccountUuid);
ManualDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context =
new ManualDeliveryContext<>(null, webhook, future);
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> caller =
AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().manualDeliveryCompleteCallback(null, null))
.setContext(context);
WebhookDeliveryThread job = new WebhookDeliveryThread(webhook, event, caller);
job.setDeliveryTries(WebhookDeliveryTries.valueIn(webhook.getDomainId()));
job.setDeliveryTimeout(WebhookDeliveryTimeout.valueIn(webhook.getDomainId()));
return job;
}
protected Void deliveryCompleteCallback(
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> callback,
WebhookDeliveryThread.WebhookDeliveryContext<Webhook> context) {
WebhookDeliveryThread.WebhookDeliveryResult result = callback.getResult();
WebhookDeliveryVO deliveryVO = new WebhookDeliveryVO(context.getEventId(), context.getRuleId(),
ManagementServerNode.getManagementServerId(), result.getHeaders(), result.getPayload(),
result.isSuccess(), result.getResult(), result.getStarTime(), result.getEndTime());
webhookDeliveryDao.persist(deliveryVO);
return null;
}
protected Void manualDeliveryCompleteCallback(
AsyncCallbackDispatcher<WebhookServiceImpl, WebhookDeliveryThread.WebhookDeliveryResult> callback,
ManualDeliveryContext<WebhookDeliveryThread.WebhookDeliveryResult> context) {
WebhookDeliveryThread.WebhookDeliveryResult result = callback.getResult();
context.future.complete(result);
return null;
}
protected long cleanupOldWebhookDeliveries(long deliveriesLimit) {
Filter filter = new Filter(WebhookVO.class, "id", true, 0L, 50L);
Pair<List<WebhookVO>, Integer> webhooksAndCount =
webhookDao.searchAndCount(webhookDao.createSearchCriteria(), filter);
List<WebhookVO> webhooks = webhooksAndCount.first();
long count = webhooksAndCount.second();
long processed = 0;
do {
for (WebhookVO webhook : webhooks) {
webhookDeliveryDao.removeOlderDeliveries(webhook.getId(), deliveriesLimit);
processed++;
}
if (processed < count) {
filter.setOffset(processed);
webhooks = webhookDao.listAll(filter);
}
} while (processed < count);
return processed;
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
try {
webhookJobExecutor = Executors.newFixedThreadPool(WebhookDeliveryThreadPoolSize.value(),
new NamedThreadFactory(WEBHOOK_JOB_POOL_THREAD_PREFIX));
webhookDeliveriesCleanupExecutor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("Webhook-Deliveries-Cleanup-Worker"));
} catch (final Exception e) {
throw new ConfigurationException("Unable to to configure WebhookServiceImpl");
}
return true;
}
@Override
public boolean start() {
long webhookDeliveriesCleanupInitialDelay = WebhookDeliveriesCleanupInitialDelay.value();
long webhookDeliveriesCleanupInterval = WebhookDeliveriesCleanupInterval.value();
logger.debug("Scheduling webhook deliveries cleanup task with initial delay={}s and interval={}s",
webhookDeliveriesCleanupInitialDelay, webhookDeliveriesCleanupInterval);
webhookDeliveriesCleanupExecutor.scheduleWithFixedDelay(new WebhookDeliveryCleanupWorker(),
webhookDeliveriesCleanupInitialDelay, webhookDeliveriesCleanupInterval, TimeUnit.SECONDS);
return true;
}
@Override
public boolean stop() {
webhookJobExecutor.shutdown();
return true;
}
@Override
public String getConfigComponentName() {
return WebhookService.class.getName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[]{
WebhookDeliveryTimeout,
WebhookDeliveryTries,
WebhookDeliveryThreadPoolSize,
WebhookDeliveriesLimit,
WebhookDeliveriesCleanupInitialDelay,
WebhookDeliveriesCleanupInterval
};
}
@Override
public void deleteWebhooksForAccount(long accountId) {
webhookDao.deleteByAccount(accountId);
}
@Override
public List<? extends ControlledEntity> listWebhooksByAccount(long accountId) {
return webhookDao.listByAccount(accountId);
}
@Override
public void handleEvent(Event event) throws EventBusException {
List<Runnable> jobs = getDeliveryJobs(event);
for(Runnable job : jobs) {
webhookJobExecutor.submit(job);
}
}
@Override
public WebhookDelivery executeWebhookDelivery(WebhookDelivery delivery, Webhook webhook, String payload)
throws CloudRuntimeException {
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future = new AsyncCallFuture<>();
Runnable job = getManualDeliveryJob(delivery, webhook, payload, future);
webhookJobExecutor.submit(job);
WebhookDeliveryThread.WebhookDeliveryResult result = null;
WebhookDeliveryVO webhookDeliveryVO;
try {
result = future.get();
if (delivery != null) {
webhookDeliveryVO = new WebhookDeliveryVO(delivery.getEventId(), delivery.getWebhookId(),
ManagementServerNode.getManagementServerId(), result.getHeaders(), result.getPayload(),
result.isSuccess(), result.getResult(), result.getStarTime(), result.getEndTime());
webhookDeliveryVO = webhookDeliveryDao.persist(webhookDeliveryVO);
} else {
webhookDeliveryVO = new WebhookDeliveryVO(ManagementServerNode.getManagementServerId(),
result.getHeaders(), result.getPayload(), result.isSuccess(), result.getResult(),
result.getStarTime(), result.getEndTime());
}
} catch (InterruptedException | ExecutionException e) {
logger.error(String.format("Failed to execute test webhook delivery due to: %s", e.getMessage()), e);
throw new CloudRuntimeException("Failed to execute test webhook delivery");
}
return webhookDeliveryVO;
}
@Override
public List<Class<?>> getCommands() {
return new ArrayList<>();
}
static public class ManualDeliveryContext<T> extends AsyncRpcContext<T> {
final Webhook webhook;
final AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future;
public ManualDeliveryContext(AsyncCompletionCallback<T> callback, Webhook webhook,
AsyncCallFuture<WebhookDeliveryThread.WebhookDeliveryResult> future) {
super(callback);
this.webhook = webhook;
this.future = future;
}
}
public class WebhookDeliveryCleanupWorker extends ManagedContextRunnable {
protected void runCleanupForLongestRunningManagementServer() {
try {
ManagementServerHostVO msHost = managementServerHostDao.findOneByLongestRuntime();
if (msHost == null || (msHost.getMsid() != ManagementServerNode.getManagementServerId())) {
logger.debug("Skipping the webhook delivery cleanup task on this management server");
return;
}
long deliveriesLimit = WebhookDeliveriesLimit.value();
logger.debug("Clearing old deliveries for webhooks with limit={} using management server {}",
deliveriesLimit, msHost.getMsid());
long processed = cleanupOldWebhookDeliveries(deliveriesLimit);
logger.debug("Cleared old deliveries with limit={} for {} webhooks", deliveriesLimit, processed);
} catch (Exception e) {
logger.warn("Cleanup task failed to cleanup old webhook deliveries", e);
}
}
@Override
protected void runInContext() {
GlobalLock gcLock = GlobalLock.getInternLock("WebhookDeliveriesCleanup");
try {
if (gcLock.lock(3)) {
try {
runCleanupForLongestRunningManagementServer();
} finally {
gcLock.unlock();
}
}
} finally {
gcLock.releaseRef();
}
}
}
}

View File

@ -0,0 +1,167 @@
// 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.mom.webhook.api.command.user;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ACL;
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.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "createWebhook",
description = "Creates a Webhook",
responseObject = WebhookResponse.class,
responseView = ResponseObject.ResponseView.Restricted,
entityType = {Webhook.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class CreateWebhookCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true, description = "Name for the Webhook")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "Description for the Webhook")
private String description;
@Parameter(name = ApiConstants.STATE, type = BaseCmd.CommandType.STRING, description = "State of the Webhook")
private String state;
@ACL(accessType = SecurityChecker.AccessType.UseEntry)
@Parameter(name = ApiConstants.ACCOUNT, type = BaseCmd.CommandType.STRING, description = "An optional account for the" +
" Webhook. Must be used with domainId.")
private String accountName;
@ACL(accessType = SecurityChecker.AccessType.UseEntry)
@Parameter(name = ApiConstants.DOMAIN_ID, type = BaseCmd.CommandType.UUID, entityType = DomainResponse.class,
description = "an optional domainId for the Webhook. If the account parameter is used, domainId must also be used.")
private Long domainId;
@ACL(accessType = SecurityChecker.AccessType.UseEntry)
@Parameter(name = ApiConstants.PROJECT_ID, type = BaseCmd.CommandType.UUID, entityType = ProjectResponse.class,
description = "Project for the Webhook")
private Long projectId;
@Parameter(name = ApiConstants.PAYLOAD_URL,
type = BaseCmd.CommandType.STRING,
required = true,
description = "Payload URL of the Webhook")
private String payloadUrl;
@Parameter(name = ApiConstants.SECRET_KEY, type = BaseCmd.CommandType.STRING, description = "Secret key of the Webhook")
private String secretKey;
@Parameter(name = ApiConstants.SSL_VERIFICATION, type = BaseCmd.CommandType.BOOLEAN, description = "If set to true then SSL verification will be done for the Webhook otherwise not")
private Boolean sslVerification;
@Parameter(name = ApiConstants.SCOPE, type = BaseCmd.CommandType.STRING, description = "Scope of the Webhook",
authorized = {RoleType.Admin, RoleType.DomainAdmin})
private String scope;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getState() {
return state;
}
public String getAccountName() {
return accountName;
}
public Long getDomainId() {
return domainId;
}
public Long getProjectId() {
return projectId;
}
public String getPayloadUrl() {
return payloadUrl;
}
public String getSecretKey() {
return secretKey;
}
public boolean isSslVerification() {
return Boolean.TRUE.equals(sslVerification);
}
public String getScope() {
return scope;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
WebhookResponse response = webhookApiService.createWebhook(this);
if (response == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create webhook");
}
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

View File

@ -0,0 +1,84 @@
// 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.mom.webhook.api.command.user;
import javax.inject.Inject;
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.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "deleteWebhook",
description = "Deletes a Webhook",
responseObject = SuccessResponse.class,
entityType = {Webhook.class},
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class DeleteWebhookCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = WebhookResponse.class,
required = true,
description = "The ID of the Webhook")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
if (!webhookApiService.deleteWebhook(this)) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to delete webhook ID: %d", getId()));
}
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

View File

@ -0,0 +1,126 @@
// 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.mom.webhook.api.command.user;
import java.util.Date;
import javax.inject.Inject;
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.ManagementServerResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.WebhookDelivery;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "deleteWebhookDelivery",
description = "Deletes Webhook delivery",
responseObject = SuccessResponse.class,
entityType = {WebhookDelivery.class},
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class DeleteWebhookDeliveryCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID,
entityType = WebhookDeliveryResponse.class,
description = "The ID of the Webhook delivery")
private Long id;
@Parameter(name = ApiConstants.WEBHOOK_ID, type = BaseCmd.CommandType.UUID,
entityType = WebhookResponse.class,
description = "The ID of the Webhook")
private Long webhookId;
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = BaseCmd.CommandType.UUID,
entityType = ManagementServerResponse.class,
description = "The ID of the management server",
authorized = {RoleType.Admin})
private Long managementServerId;
@Parameter(name = ApiConstants.START_DATE,
type = CommandType.DATE,
description = "The start date range for the Webhook delivery " +
"(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\"). " +
"All deliveries having start date equal to or after the specified date will be considered.")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
description = "The end date range for the Webhook delivery " +
"(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\"). " +
"All deliveries having end date equal to or before the specified date will be considered.")
private Date endDate;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getWebhookId() {
return webhookId;
}
public Long getManagementServerId() {
return managementServerId;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
webhookApiService.deleteWebhookDelivery(this);
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

View File

@ -0,0 +1,132 @@
// 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.mom.webhook.api.command.user;
import javax.inject.Inject;
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.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.WebhookDelivery;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "executeWebhookDelivery",
description = "Executes a Webhook delivery",
responseObject = WebhookDeliveryResponse.class,
entityType = {WebhookDelivery.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class ExecuteWebhookDeliveryCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = WebhookDeliveryResponse.class,
description = "The ID of the Webhook delivery for redelivery")
private Long id;
@Parameter(name = ApiConstants.WEBHOOK_ID, type = CommandType.UUID,
entityType = WebhookResponse.class,
description = "The ID of the Webhook")
private Long webhookId;
@Parameter(name = ApiConstants.PAYLOAD_URL,
type = BaseCmd.CommandType.STRING,
description = "Payload URL of the Webhook delivery")
private String payloadUrl;
@Parameter(name = ApiConstants.SECRET_KEY, type = BaseCmd.CommandType.STRING, description = "Secret key of the Webhook delivery")
private String secretKey;
@Parameter(name = ApiConstants.SSL_VERIFICATION, type = BaseCmd.CommandType.BOOLEAN, description = "If set to true then SSL verification will be done for the Webhook delivery otherwise not")
private Boolean sslVerification;
@Parameter(name = ApiConstants.PAYLOAD,
type = BaseCmd.CommandType.STRING,
description = "Payload of the Webhook delivery")
private String payload;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getWebhookId() {
return webhookId;
}
public String getPayloadUrl() {
return payloadUrl;
}
public String getSecretKey() {
return secretKey;
}
public Boolean isSslVerification() {
return sslVerification;
}
public String getPayload() {
return payload;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
WebhookDeliveryResponse response = webhookApiService.executeWebhookDelivery(this);
if (response == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to test Webhook delivery");
}
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

View File

@ -0,0 +1,125 @@
// 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.mom.webhook.api.command.user;
import java.util.Date;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ManagementServerResponse;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.WebhookDelivery;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
@APICommand(name = "listWebhookDeliveries",
description = "Lists Webhook deliveries",
responseObject = WebhookResponse.class,
responseView = ResponseObject.ResponseView.Restricted,
entityType = {WebhookDelivery.class},
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class ListWebhookDeliveriesCmd extends BaseListCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID,
entityType = WebhookDeliveryResponse.class,
description = "The ID of the Webhook delivery")
private Long id;
@Parameter(name = ApiConstants.WEBHOOK_ID, type = BaseCmd.CommandType.UUID,
entityType = WebhookResponse.class,
description = "The ID of the Webhook")
private Long webhookId;
@Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = BaseCmd.CommandType.UUID,
entityType = ManagementServerResponse.class,
description = "The ID of the management server",
authorized = {RoleType.Admin})
private Long managementServerId;
@Parameter(name = ApiConstants.START_DATE,
type = CommandType.DATE,
description = "The start date range for the Webhook delivery " +
"(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\"). " +
"All deliveries having start date equal to or after the specified date will be listed.")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
description = "The end date range for the Webhook delivery " +
"(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\"). " +
"All deliveries having end date equal to or before the specified date will be listed.")
private Date endDate;
@Parameter(name = ApiConstants.EVENT_TYPE,
type = CommandType.STRING,
description = "The event type of the Webhook delivery")
private String eventType;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getWebhookId() {
return webhookId;
}
public Long getManagementServerId() {
return managementServerId;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public String getEventType() {
return eventType;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
ListResponse<WebhookDeliveryResponse> response = webhookApiService.listWebhookDeliveries(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,95 @@
// 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.mom.webhook.api.command.user;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
@APICommand(name = "listWebhooks",
description = "Lists Webhooks",
responseObject = WebhookResponse.class,
responseView = ResponseObject.ResponseView.Restricted,
entityType = {Webhook.class},
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class ListWebhooksCmd extends BaseListProjectAndAccountResourcesCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = WebhookResponse.class,
description = "The ID of the Webhook")
private Long id;
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "The state of the Webhook")
private String state;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "The name of the Webhook")
private String name;
@Parameter(name = ApiConstants.SCOPE,
type = CommandType.STRING,
description = "The scope of the Webhook",
authorized = {RoleType.Admin, RoleType.DomainAdmin})
private String scope;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getState() {
return state;
}
public String getName() {
return name;
}
public String getScope() {
return scope;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
ListResponse<WebhookResponse> response = webhookApiService.listWebhooks(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,136 @@
// 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.mom.webhook.api.command.user;
import javax.inject.Inject;
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.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "updateWebhook",
description = "Updates a Webhook",
responseObject = SuccessResponse.class,
entityType = {Webhook.class},
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
since = "4.20.0")
public class UpdateWebhookCmd extends BaseCmd {
@Inject
WebhookApiService webhookApiService;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = WebhookResponse.class,
required = true,
description = "The ID of the Webhook")
private Long id;
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, description = "Name for the Webhook")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, description = "Description for the Webhook")
private String description;
@Parameter(name = ApiConstants.STATE, type = BaseCmd.CommandType.STRING, description = "State of the Webhook")
private String state;
@Parameter(name = ApiConstants.PAYLOAD_URL,
type = BaseCmd.CommandType.STRING,
description = "Payload URL of the Webhook")
private String payloadUrl;
@Parameter(name = ApiConstants.SECRET_KEY, type = BaseCmd.CommandType.STRING, description = "Secret key of the Webhook")
private String secretKey;
@Parameter(name = ApiConstants.SSL_VERIFICATION, type = BaseCmd.CommandType.BOOLEAN, description = "If set to true then SSL verification will be done for the Webhook otherwise not")
private Boolean sslVerification;
@Parameter(name = ApiConstants.SCOPE, type = BaseCmd.CommandType.STRING, description = "Scope of the Webhook",
authorized = {RoleType.Admin, RoleType.DomainAdmin})
private String scope;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getState() {
return state;
}
public String getPayloadUrl() {
return payloadUrl;
}
public String getSecretKey() {
return secretKey;
}
public Boolean isSslVerification() {
return sslVerification;
}
public String getScope() {
return scope;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
WebhookResponse response = webhookApiService.updateWebhook(this);
if (response == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update Webhook");
}
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
}

View File

@ -0,0 +1,136 @@
// 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.mom.webhook.api.response;
import java.util.Date;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.mom.webhook.WebhookDelivery;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = {WebhookDelivery.class})
public class WebhookDeliveryResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of the Webhook delivery")
private String id;
@SerializedName(ApiConstants.EVENT_ID)
@Param(description = "The ID of the event")
private String eventId;
@SerializedName(ApiConstants.EVENT_TYPE)
@Param(description = "The type of the event")
private String eventType;
@SerializedName(ApiConstants.WEBHOOK_ID)
@Param(description = "The ID of the Webhook")
private String webhookId;
@SerializedName(ApiConstants.WEBHOOK_NAME)
@Param(description = "The name of the Webhook")
private String webhookName;
@SerializedName(ApiConstants.MANAGEMENT_SERVER_ID)
@Param(description = "The ID of the management server which executed delivery")
private String managementServerId;
@SerializedName(ApiConstants.MANAGEMENT_SERVER_NAME)
@Param(description = "The name of the management server which executed delivery")
private String managementServerName;
@SerializedName(ApiConstants.HEADERS)
@Param(description = "The headers of the webhook delivery")
private String headers;
@SerializedName(ApiConstants.PAYLOAD)
@Param(description = "The payload of the webhook delivery")
private String payload;
@SerializedName(ApiConstants.SUCCESS)
@Param(description = "Whether Webhook delivery succeeded or not")
private boolean success;
@SerializedName(ApiConstants.RESPONSE)
@Param(description = "The response of the webhook delivery")
private String response;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "The start time of the Webhook delivery")
private Date startTime;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "The end time of the Webhook delivery")
private Date endTime;
public void setId(String id) {
this.id = id;
}
public void setEventId(String eventId) {
this.eventId = eventId;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public void setWebhookId(String webhookId) {
this.webhookId = webhookId;
}
public void setWebhookName(String webhookName) {
this.webhookName = webhookName;
}
public void setManagementServerId(String managementServerId) {
this.managementServerId = managementServerId;
}
public void setManagementServerName(String managementServerName) {
this.managementServerName = managementServerName;
}
public void setHeaders(String headers) {
this.headers = headers;
}
public void setPayload(String payload) {
this.payload = payload;
}
public void setSuccess(boolean success) {
this.success = success;
}
public void setResponse(String response) {
this.response = response;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
}

View File

@ -0,0 +1,149 @@
// 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.mom.webhook.api.response;
import java.util.Date;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.api.response.ControlledViewEntityResponse;
import org.apache.cloudstack.mom.webhook.Webhook;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = {Webhook.class})
public class WebhookResponse extends BaseResponse implements ControlledViewEntityResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of the Webhook")
private String id;
@SerializedName(ApiConstants.NAME)
@Param(description = "The name of the Webhook")
private String name;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "The description of the Webhook")
private String description;
@SerializedName(ApiConstants.STATE)
@Param(description = "The state of the Webhook")
private String state;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "The ID of the domain in which the Webhook exists")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "The name of the domain in which the Webhook exists")
private String domainName;
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "The account associated with the Webhook")
private String accountName;
@SerializedName(ApiConstants.PROJECT_ID)
@Param(description = "the project id of the Kubernetes cluster")
private String projectId;
@SerializedName(ApiConstants.PROJECT)
@Param(description = "the project name of the Kubernetes cluster")
private String projectName;
@SerializedName(ApiConstants.PAYLOAD_URL)
@Param(description = "The payload URL end point for the Webhook")
private String payloadUrl;
@SerializedName(ApiConstants.SECRET_KEY)
@Param(description = "The secret key for the Webhook")
private String secretKey;
@SerializedName(ApiConstants.SSL_VERIFICATION)
@Param(description = "Whether SSL verification is enabled for the Webhook")
private boolean sslVerification;
@SerializedName(ApiConstants.SCOPE)
@Param(description = "The scope of the Webhook")
private String scope;
@SerializedName(ApiConstants.CREATED)
@Param(description = "The date when this Webhook was created")
private Date created;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setDescription(String description) {
this.description = description;
}
public void setState(String state) {
this.state = state;
}
@Override
public void setDomainId(String domainId) {
this.domainId = domainId;
}
@Override
public void setDomainName(String domainName) {
this.domainName = domainName;
}
@Override
public void setAccountName(String accountName) {
this.accountName = accountName;
}
@Override
public void setProjectId(String projectId) {
this.projectId = projectId;
}
@Override
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void setPayloadUrl(String payloadUrl) {
this.payloadUrl = payloadUrl;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public void setSslVerification(boolean sslVerification) {
this.sslVerification = sslVerification;
}
public void setScope(String scope) {
this.scope = scope;
}
public void setCreated(Date created) {
this.created = created;
}
}

View File

@ -0,0 +1,31 @@
// 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.mom.webhook.dao;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import com.cloud.utils.db.GenericDao;
public interface WebhookDao extends GenericDao<WebhookVO, Long> {
List<WebhookVO> listByEnabledForDelivery(Long accountId, List<Long> domainIds);
void deleteByAccount(long accountId);
List<WebhookVO> listByAccount(long accountId);
WebhookVO findByAccountAndPayloadUrl(long accountId, String payloadUrl);
}

View File

@ -0,0 +1,99 @@
// 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.mom.webhook.dao;
import java.util.List;
import java.util.Map;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.commons.collections.CollectionUtils;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class WebhookDaoImpl extends GenericDaoBase<WebhookVO, Long> implements WebhookDao {
SearchBuilder<WebhookVO> accountIdSearch;
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
super.configure(name, params);
accountIdSearch = createSearchBuilder();
accountIdSearch.and("accountId", accountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
return true;
}
@Override
public List<WebhookVO> listByEnabledForDelivery(Long accountId, List<Long> domainIds) {
SearchBuilder<WebhookVO> sb = createSearchBuilder();
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and().op("scopeGlobal", sb.entity().getScope(), SearchCriteria.Op.EQ);
if (accountId != null) {
sb.or().op("scopeLocal", sb.entity().getScope(), SearchCriteria.Op.EQ);
sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ);
sb.cp();
}
if (CollectionUtils.isNotEmpty(domainIds)) {
sb.or().op("scopeDomain", sb.entity().getScope(), SearchCriteria.Op.EQ);
sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.cp();
SearchCriteria<WebhookVO> sc = sb.create();
sc.setParameters("state", Webhook.State.Enabled.name());
sc.setParameters("scopeGlobal", Webhook.Scope.Global.name());
if (accountId != null) {
sc.setParameters("scopeLocal", Webhook.Scope.Local.name());
sc.setParameters("accountId", accountId);
}
if (CollectionUtils.isNotEmpty(domainIds)) {
sc.setParameters("scopeDomain", Webhook.Scope.Domain.name());
sc.setParameters("domainId", domainIds.toArray());
}
return listBy(sc);
}
@Override
public void deleteByAccount(long accountId) {
SearchCriteria<WebhookVO> sc = accountIdSearch.create();
sc.setParameters("accountId", accountId);
remove(sc);
}
@Override
public List<WebhookVO> listByAccount(long accountId) {
SearchCriteria<WebhookVO> sc = accountIdSearch.create();
sc.setParameters("accountId", accountId);
return listBy(sc);
}
@Override
public WebhookVO findByAccountAndPayloadUrl(long accountId, String payloadUrl) {
SearchBuilder<WebhookVO> sb = createSearchBuilder();
sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ);
sb.and("payloadUrl", sb.entity().getPayloadUrl(), SearchCriteria.Op.EQ);
SearchCriteria<WebhookVO> sc = sb.create();
sc.setParameters("accountId", accountId);
sc.setParameters("payloadUrl", payloadUrl);
return findOneBy(sc);
}
}

View File

@ -0,0 +1,29 @@
// 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.mom.webhook.dao;
import java.util.Date;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import com.cloud.utils.db.GenericDao;
public interface WebhookDeliveryDao extends GenericDao<WebhookDeliveryVO, Long> {
int deleteByDeleteApiParams(Long id, Long webhookId, Long managementServerId, Date startDate, Date endDate);
void removeOlderDeliveries(long webhookId, long limit);
}

View File

@ -0,0 +1,73 @@
// 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.mom.webhook.dao;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryVO;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class WebhookDeliveryDaoImpl extends GenericDaoBase<WebhookDeliveryVO, Long> implements WebhookDeliveryDao {
@Override
public int deleteByDeleteApiParams(Long id, Long webhookId, Long managementServerId, Date startDate,
Date endDate) {
SearchBuilder<WebhookDeliveryVO> sb = createSearchBuilder();
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("webhookId", sb.entity().getWebhookId(), SearchCriteria.Op.EQ);
sb.and("managementServerId", sb.entity().getManagementServerId(), SearchCriteria.Op.EQ);
sb.and("startDate", sb.entity().getStartTime(), SearchCriteria.Op.GTEQ);
sb.and("endDate", sb.entity().getEndTime(), SearchCriteria.Op.LTEQ);
SearchCriteria<WebhookDeliveryVO> sc = sb.create();
if (id != null) {
sc.setParameters("id", id);
}
if (webhookId != null) {
sc.setParameters("webhookId", webhookId);
}
if (managementServerId != null) {
sc.setParameters("managementServerId", managementServerId);
}
if (startDate != null) {
sc.setParameters("startDate", startDate);
}
if (endDate != null) {
sc.setParameters("endDate", endDate);
}
return remove(sc);
}
@Override
public void removeOlderDeliveries(long webhookId, long limit) {
Filter searchFilter = new Filter(WebhookDeliveryVO.class, "id", false, 0L, limit);
SearchBuilder<WebhookDeliveryVO> sb = createSearchBuilder();
sb.and("webhookId", sb.entity().getWebhookId(), SearchCriteria.Op.EQ);
SearchCriteria<WebhookDeliveryVO> sc = sb.create();
sc.setParameters("webhookId", webhookId);
List<WebhookDeliveryVO> keep = listBy(sc, searchFilter);
SearchBuilder<WebhookDeliveryVO> sbDelete = createSearchBuilder();
sbDelete.and("id", sbDelete.entity().getId(), SearchCriteria.Op.NOTIN);
SearchCriteria<WebhookDeliveryVO> scDelete = sbDelete.create();
scDelete.setParameters("id", keep.stream().map(WebhookDeliveryVO::getId).toArray());
remove(scDelete);
}
}

View File

@ -0,0 +1,33 @@
// 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.mom.webhook.dao;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryJoinVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
public interface WebhookDeliveryJoinDao extends GenericDao<WebhookDeliveryJoinVO, Long> {
Pair<List<WebhookDeliveryJoinVO>, Integer> searchAndCountByListApiParameters(Long id,
List<Long> webhookIds, Long managementServerId, final String keyword, final Date startDate,
final Date endDate, final String eventType, Filter searchFilter);
}

View File

@ -0,0 +1,71 @@
// 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.mom.webhook.dao;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookDeliveryJoinVO;
import org.apache.commons.collections.CollectionUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class WebhookDeliveryJoinDaoImpl extends GenericDaoBase<WebhookDeliveryJoinVO, Long>
implements WebhookDeliveryJoinDao {
@Override
public Pair<List<WebhookDeliveryJoinVO>, Integer> searchAndCountByListApiParameters(Long id,
List<Long> webhookIds, Long managementServerId, String keyword, final Date startDate,
final Date endDate, final String eventType, Filter searchFilter) {
SearchBuilder<WebhookDeliveryJoinVO> sb = createSearchBuilder();
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("webhookId", sb.entity().getWebhookId(), SearchCriteria.Op.IN);
sb.and("managementServerId", sb.entity().getManagementServerMsId(), SearchCriteria.Op.EQ);
sb.and("keyword", sb.entity().getPayload(), SearchCriteria.Op.LIKE);
sb.and("startDate", sb.entity().getStartTime(), SearchCriteria.Op.GTEQ);
sb.and("endDate", sb.entity().getEndTime(), SearchCriteria.Op.LTEQ);
sb.and("eventType", sb.entity().getEventType(), SearchCriteria.Op.EQ);
SearchCriteria<WebhookDeliveryJoinVO> sc = sb.create();
if (id != null) {
sc.setParameters("id", id);
}
if (CollectionUtils.isNotEmpty(webhookIds)) {
sc.setParameters("webhookId", webhookIds.toArray());
}
if (managementServerId != null) {
sc.setParameters("managementServerId", managementServerId);
}
if (keyword != null) {
sc.setParameters("keyword", "%" + keyword + "%");
}
if (startDate != null) {
sc.setParameters("startDate", startDate);
}
if (endDate != null) {
sc.setParameters("endDate", endDate);
}
if (StringUtils.isNotBlank(eventType)) {
sc.setParameters("eventType", eventType);
}
return searchAndCount(sc, searchFilter);
}
}

View File

@ -0,0 +1,28 @@
// 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.mom.webhook.dao;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookJoinVO;
import com.cloud.utils.db.GenericDao;
public interface WebhookJoinDao extends GenericDao<WebhookJoinVO, Long> {
List<WebhookJoinVO> listByAccountOrDomain(long accountId, String domainPath);
}

View File

@ -0,0 +1,45 @@
// 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.mom.webhook.dao;
import java.util.List;
import org.apache.cloudstack.mom.webhook.vo.WebhookJoinVO;
import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class WebhookJoinDaoImpl extends GenericDaoBase<WebhookJoinVO, Long> implements WebhookJoinDao {
@Override
public List<WebhookJoinVO> listByAccountOrDomain(long accountId, String domainPath) {
SearchBuilder<WebhookJoinVO> sb = createSearchBuilder();
sb.and().op("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ);
if (StringUtils.isNotBlank(domainPath)) {
sb.or("domainPath", sb.entity().getDomainPath(), SearchCriteria.Op.LIKE);
}
sb.cp();
SearchCriteria<WebhookJoinVO> sc = sb.create();
sc.setParameters("accountId", accountId);
if (StringUtils.isNotBlank(domainPath)) {
sc.setParameters("domainPath", domainPath);
}
return listBy(sc);
}
}

View File

@ -0,0 +1,182 @@
// 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.mom.webhook.vo;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import com.cloud.api.query.vo.BaseViewVO;
@Entity
@Table(name = "webhook_delivery_view")
public class WebhookDeliveryJoinVO extends BaseViewVO implements InternalIdentity, Identity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "event_id")
private long eventId;
@Column(name = "event_uuid")
private String eventUuid;
@Column(name = "event_type")
private String eventType;
@Column(name = "webhook_id")
private long webhookId;
@Column(name = "webhook_uuid")
private String webhookUuId;
@Column(name = "webhook_name")
private String webhookName;
@Column(name = "mshost_id")
private long managementServerId;
@Column(name = "mshost_uuid")
private String managementServerUuId;
@Column(name = "mshost_msid")
private long managementServerMsId;
@Column(name = "mshost_name")
private String managementServerName;
@Column(name = "headers", length = 65535)
private String headers;
@Column(name = "payload", length = 65535)
private String payload;
@Column(name = "success")
private boolean success;
@Column(name = "response", length = 65535)
private String response;
@Column(name = "start_time")
@Temporal(value = TemporalType.TIMESTAMP)
private Date startTime;
@Column(name = "end_time")
@Temporal(value = TemporalType.TIMESTAMP)
private Date endTime;
@Override
public long getId() {
return 0;
}
@Override
public String getUuid() {
return uuid;
}
public long getEventId() {
return eventId;
}
public String getEventUuid() {
return eventUuid;
}
public String getEventType() {
return eventType;
}
public long getWebhookId() {
return webhookId;
}
public String getWebhookUuId() {
return webhookUuId;
}
public String getWebhookName() {
return webhookName;
}
public long getManagementServerId() {
return managementServerId;
}
public String getManagementServerUuId() {
return managementServerUuId;
}
public long getManagementServerMsId() {
return managementServerMsId;
}
public String getManagementServerName() {
return managementServerName;
}
public String getHeaders() {
return headers;
}
public String getPayload() {
return payload;
}
public boolean isSuccess() {
return success;
}
public String getResponse() {
return response;
}
public Date getStartTime() {
return startTime;
}
public Date getEndTime() {
return endTime;
}
@Override
public String toString() {
return String.format("WebhookDelivery [%s]", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "webhookId", "startTime", "success"));
}
public WebhookDeliveryJoinVO() {
}
}

View File

@ -0,0 +1,174 @@
// 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.mom.webhook.vo;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.cloudstack.mom.webhook.WebhookDelivery;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
@Entity
@Table(name = "webhook_delivery")
public class WebhookDeliveryVO implements WebhookDelivery {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "event_id")
private long eventId;
@Column(name = "webhook_id")
private long webhookId;
@Column(name = "mshost_msid")
private long mangementServerId;
@Column(name = "headers", length = 65535)
private String headers;
@Column(name = "payload", length = 65535)
private String payload;
@Column(name = "success")
private boolean success;
@Column(name = "response", length = 65535)
private String response;
@Column(name = "start_time")
@Temporal(value = TemporalType.TIMESTAMP)
private Date startTime;
@Column(name = "end_time")
@Temporal(value = TemporalType.TIMESTAMP)
private Date endTime;
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public long getEventId() {
return eventId;
}
@Override
public long getWebhookId() {
return webhookId;
}
@Override
public long getManagementServerId() {
return mangementServerId;
}
public String getHeaders() {
return headers;
}
@Override
public String getPayload() {
return payload;
}
@Override
public boolean isSuccess() {
return success;
}
@Override
public String getResponse() {
return response;
}
@Override
public Date getStartTime() {
return startTime;
}
@Override
public Date getEndTime() {
return endTime;
}
@Override
public String toString() {
return String.format("WebhookDelivery [%s]", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "webhookId", "startTime", "success"));
}
public WebhookDeliveryVO() {
this.uuid = UUID.randomUUID().toString();
}
public WebhookDeliveryVO(long eventId, long webhookId, long managementServerId, String headers, String payload,
boolean success, String response, Date startTime, Date endTime) {
this.uuid = UUID.randomUUID().toString();
this.eventId = eventId;
this.webhookId = webhookId;
this.mangementServerId = managementServerId;
this.headers = headers;
this.payload = payload;
this.success = success;
this.response = response;
this.startTime = startTime;
this.endTime = endTime;
}
/*
* For creating a dummy object for testing delivery
*/
public WebhookDeliveryVO(long managementServerId, String headers, String payload, boolean success,
String response, Date startTime, Date endTime) {
this.id = WebhookDelivery.ID_DUMMY;
this.uuid = UUID.randomUUID().toString();
this.eventId = WebhookDelivery.ID_DUMMY;
this.webhookId = WebhookDelivery.ID_DUMMY;
this.mangementServerId = managementServerId;
this.headers = headers;
this.payload = payload;
this.success = success;
this.response = response;
this.startTime = startTime;
this.endTime = endTime;
}
}

View File

@ -0,0 +1,234 @@
// 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.mom.webhook.vo;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import com.cloud.api.query.vo.ControlledViewEntity;
import com.cloud.user.Account;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
@Entity
@Table(name = "webhook_view")
public class WebhookJoinVO implements ControlledViewEntity {
@Id
@Column(name = "id", updatable = false, nullable = false)
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "name")
private String name;
@Column(name = "description", length = 4096)
private String description;
@Column(name = "state")
@Enumerated(value = EnumType.STRING)
private Webhook.State state;
@Column(name = "payload_url")
private String payloadUrl;
@Column(name = "secret_key")
@Encrypt
private String secretKey;
@Column(name = "ssl_verification")
private boolean sslVerification;
@Column(name = "scope")
@Enumerated(value = EnumType.STRING)
private Webhook.Scope scope;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
@Column(name = "account_id")
private long accountId;
@Column(name = "account_uuid")
private String accountUuid;
@Column(name = "account_name")
private String accountName;
@Column(name = "account_type")
@Enumerated(value = EnumType.STRING)
private Account.Type accountType;
@Column(name = "domain_id")
private long domainId;
@Column(name = "domain_uuid")
private String domainUuid;
@Column(name = "domain_name")
private String domainName;
@Column(name = "domain_path")
private String domainPath;
@Column(name = "project_id")
private long projectId;
@Column(name = "project_uuid")
private String projectUuid;
@Column(name = "project_name")
private String projectName;
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Webhook.State getState() {
return state;
}
public String getPayloadUrl() {
return payloadUrl;
}
public void setPayloadUrl(String payloadUrl) {
this.payloadUrl = payloadUrl;
}
public String getSecretKey() {
return secretKey;
}
public Webhook.Scope getScope() {
return scope;
}
public boolean isSslVerification() {
return sslVerification;
}
public Date getCreated() {
return created;
}
public Date getRemoved() {
return removed;
}
@Override
public long getDomainId() {
return domainId;
}
@Override
public String getDomainPath() {
return domainPath;
}
@Override
public String getDomainUuid() {
return domainUuid;
}
@Override
public String getDomainName() {
return domainName;
}
@Override
public Account.Type getAccountType() {
return accountType;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public String getAccountUuid() {
return accountUuid;
}
@Override
public String getAccountName() {
return accountName;
}
@Override
public String getProjectUuid() {
return projectUuid;
}
@Override
public String getProjectName() {
return projectName;
}
@Override
public Class<?> getEntityType() {
return Webhook.class;
}
@Override
public String toString() {
return String.format("Webhook [%s]", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "name"));
}
public WebhookJoinVO() {
}
}

View File

@ -0,0 +1,232 @@
// 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.mom.webhook.vo;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.mom.webhook.Webhook;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
@Entity
@Table(name = "webhook")
public class WebhookVO implements Webhook {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "name")
private String name;
@Column(name = "description", length = 4096)
private String description;
@Column(name = "state")
@Enumerated(value = EnumType.STRING)
private State state;
@Column(name = "domain_id")
private long domainId;
@Column(name = "account_id")
private long accountId;
@Column(name = "payload_url")
private String payloadUrl;
@Column(name = "secret_key")
@Encrypt
private String secretKey;
@Column(name = "ssl_verification")
private boolean sslVerification;
@Column(name = "scope")
@Enumerated(value = EnumType.STRING)
private Scope scope;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
@Override
public long getDomainId() {
return domainId;
}
public void setDomainId(long domainId) {
this.domainId = domainId;
}
@Override
public long getAccountId() {
return accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public String getPayloadUrl() {
return payloadUrl;
}
public void setPayloadUrl(String payloadUrl) {
this.payloadUrl = payloadUrl;
}
@Override
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public Scope getScope() {
return scope;
}
public void setScope(Scope scope) {
this.scope = scope;
}
@Override
public boolean isSslVerification() {
return sslVerification;
}
public void setSslVerification(boolean sslVerification) {
this.sslVerification = sslVerification;
}
@Override
public Date getCreated() {
return created;
}
public Date getRemoved() {
return removed;
}
@Override
public Class<?> getEntityType() {
return Webhook.class;
}
@Override
public String toString() {
return String.format("Webhook [%s]",ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "name", "payloadUrl"));
}
public WebhookVO() {
this.uuid = UUID.randomUUID().toString();
}
public WebhookVO(String name, String description, State state, long domainId, long accountId,
String payloadUrl, String secretKey, boolean sslVerification, Scope scope) {
this.uuid = UUID.randomUUID().toString();
this.name = name;
this.description = description;
this.state = state;
this.domainId = domainId;
this.accountId = accountId;
this.payloadUrl = payloadUrl;
this.secretKey = secretKey;
this.sslVerification = sslVerification;
this.scope = scope;
}
/*
* For creating a dummy rule for testing delivery
*/
public WebhookVO(long domainId, long accountId, String payloadUrl, String secretKey, boolean sslVerification) {
this.uuid = UUID.randomUUID().toString();
this.id = ID_DUMMY;
this.name = NAME_DUMMY;
this.description = NAME_DUMMY;
this.state = State.Enabled;
this.domainId = domainId;
this.accountId = accountId;
this.payloadUrl = payloadUrl;
this.secretKey = secretKey;
this.sslVerification = sslVerification;
this.scope = Scope.Local;
}
}

View File

@ -0,0 +1,18 @@
# 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.
name=webhook
parent=event

View File

@ -0,0 +1,41 @@
<!--
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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<bean id="webhookDao" class="org.apache.cloudstack.mom.webhook.dao.WebhookDaoImpl" />
<bean id="webhookJoinDao" class="org.apache.cloudstack.mom.webhook.dao.WebhookJoinDaoImpl" />
<bean id="webhookDeliveryDao" class="org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryDaoImpl" />
<bean id="webhookDeliveryJoinDao" class="org.apache.cloudstack.mom.webhook.dao.WebhookDeliveryJoinDaoImpl" />
<bean id="webhookApiService" class="org.apache.cloudstack.mom.webhook.WebhookApiServiceImpl" />
<bean id="webhookService" class="org.apache.cloudstack.mom.webhook.WebhookServiceImpl" />
<bean id="webhookEventBus" class="org.apache.cloudstack.mom.webhook.WebhookEventBus">
<property name="name" value="webhookEventBus" />
</bean>
</beans>

View File

@ -0,0 +1,253 @@
// 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.mom.webhook;
import java.util.List;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.api.command.user.DeleteWebhookCmd;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.apache.cloudstack.mom.webhook.dao.WebhookDao;
import org.apache.cloudstack.mom.webhook.dao.WebhookJoinDao;
import org.apache.cloudstack.mom.webhook.vo.WebhookJoinVO;
import org.apache.cloudstack.mom.webhook.vo.WebhookVO;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.api.ApiResponseHelper;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
@RunWith(MockitoJUnitRunner.class)
public class WebhookApiServiceImplTest {
@Mock
WebhookDao webhookDao;
@Mock
WebhookJoinDao webhookJoinDao;
@Mock
AccountManager accountManager;
@Mock
DomainDao domainDao;
@InjectMocks
WebhookApiServiceImpl webhookApiServiceImpl = new WebhookApiServiceImpl();
private WebhookJoinVO prepareTestWebhookJoinVO() {
String name = "webhook";
String description = "webhook-description";
Webhook.State state = Webhook.State.Enabled;
String payloadUrl = "url";
String secretKey = "key";
boolean sslVerification = false;
Webhook.Scope scope = Webhook.Scope.Local;
WebhookJoinVO webhookJoinVO = new WebhookJoinVO();
ReflectionTestUtils.setField(webhookJoinVO, "name", name);
ReflectionTestUtils.setField(webhookJoinVO, "description", description);
ReflectionTestUtils.setField(webhookJoinVO, "state", state);
ReflectionTestUtils.setField(webhookJoinVO, "payloadUrl", payloadUrl);
ReflectionTestUtils.setField(webhookJoinVO, "secretKey", secretKey);
ReflectionTestUtils.setField(webhookJoinVO, "sslVerification", sslVerification);
ReflectionTestUtils.setField(webhookJoinVO, "scope", scope);
return webhookJoinVO;
}
private void validateWebhookResponseWithWebhookJoinVO(WebhookResponse response, WebhookJoinVO webhookJoinVO) {
Assert.assertEquals(webhookJoinVO.getName(), ReflectionTestUtils.getField(response, "name"));
Assert.assertEquals(webhookJoinVO.getDescription(), ReflectionTestUtils.getField(response, "description"));
Assert.assertEquals(webhookJoinVO.getState().toString(), ReflectionTestUtils.getField(response, "state"));
Assert.assertEquals(webhookJoinVO.getPayloadUrl(), ReflectionTestUtils.getField(response, "payloadUrl"));
Assert.assertEquals(webhookJoinVO.getSecretKey(), ReflectionTestUtils.getField(response, "secretKey"));
Assert.assertEquals(webhookJoinVO.isSslVerification(), ReflectionTestUtils.getField(response, "sslVerification"));
Assert.assertEquals(webhookJoinVO.getScope().toString(), ReflectionTestUtils.getField(response, "scope"));
}
@Test
public void testCreateWebhookResponse() {
WebhookJoinVO webhookJoinVO = prepareTestWebhookJoinVO();
try (MockedStatic<ApiResponseHelper> mockedApiResponseHelper = Mockito.mockStatic(ApiResponseHelper.class)) {
WebhookResponse response = webhookApiServiceImpl.createWebhookResponse(webhookJoinVO);
validateWebhookResponseWithWebhookJoinVO(response, webhookJoinVO);
}
}
@Test
public void testCreateWebhookResponseId() {
WebhookJoinVO webhookJoinVO = prepareTestWebhookJoinVO();
long id = 1L;
Mockito.when(webhookJoinDao.findById(id)).thenReturn(webhookJoinVO);
try (MockedStatic<ApiResponseHelper> mockedApiResponseHelper = Mockito.mockStatic(ApiResponseHelper.class)) {
WebhookResponse response = webhookApiServiceImpl.createWebhookResponse(id);
validateWebhookResponseWithWebhookJoinVO(response, webhookJoinVO);
}
}
@Test
public void testGetIdsOfAccessibleWebhooksAdmin() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getType()).thenReturn(Account.Type.ADMIN);
Assert.assertTrue(CollectionUtils.isEmpty(webhookApiServiceImpl.getIdsOfAccessibleWebhooks(account)));
}
@Test
public void testGetIdsOfAccessibleWebhooksDomainAdmin() {
Long accountId = 1L;
Account account = Mockito.mock(Account.class);
Mockito.when(account.getType()).thenReturn(Account.Type.DOMAIN_ADMIN);
Mockito.when(account.getDomainId()).thenReturn(1L);
Mockito.when(account.getId()).thenReturn(accountId);
String domainPath = "d1";
DomainVO domain = Mockito.mock(DomainVO.class);
Mockito.when(domain.getPath()).thenReturn(domainPath);
Mockito.when(domainDao.findById(1L)).thenReturn(domain);
WebhookJoinVO webhookJoinVO = Mockito.mock(WebhookJoinVO.class);
Mockito.when(webhookJoinVO.getId()).thenReturn(1L);
Mockito.when(webhookJoinDao.listByAccountOrDomain(accountId, domainPath)).thenReturn(List.of(webhookJoinVO));
List<Long> result = webhookApiServiceImpl.getIdsOfAccessibleWebhooks(account);
Assert.assertTrue(CollectionUtils.isNotEmpty(result));
Assert.assertEquals(1, result.size());
}
@Test
public void testGetIdsOfAccessibleWebhooksNormalUser() {
Long accountId = 1L;
Account account = Mockito.mock(Account.class);
Mockito.when(account.getType()).thenReturn(Account.Type.NORMAL);
Mockito.when(account.getId()).thenReturn(accountId);
WebhookJoinVO webhookJoinVO = Mockito.mock(WebhookJoinVO.class);
Mockito.when(webhookJoinVO.getId()).thenReturn(1L);
Mockito.when(webhookJoinDao.listByAccountOrDomain(accountId, null)).thenReturn(List.of(webhookJoinVO));
List<Long> result = webhookApiServiceImpl.getIdsOfAccessibleWebhooks(account);
Assert.assertTrue(CollectionUtils.isNotEmpty(result));
Assert.assertEquals(1, result.size());
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteWebhookInvalidWebhook() {
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
DeleteWebhookCmd cmd = Mockito.mock(DeleteWebhookCmd.class);
Mockito.when(cmd.getId()).thenReturn(1L);
CallContext callContextMock = Mockito.mock(CallContext.class);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
webhookApiServiceImpl.deleteWebhook(cmd);
}
}
@Test(expected = PermissionDeniedException.class)
public void testDeleteWebhookNoPermission() {
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
DeleteWebhookCmd cmd = Mockito.mock(DeleteWebhookCmd.class);
Mockito.when(cmd.getId()).thenReturn(1L);
WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
CallContext callContextMock = Mockito.mock(CallContext.class);
Account account = Mockito.mock(Account.class);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.doThrow(PermissionDeniedException.class).when(accountManager).checkAccess(account,
SecurityChecker.AccessType.OperateEntry, false, webhookVO);
webhookApiServiceImpl.deleteWebhook(cmd);
}
}
@Test
public void testDeleteWebhook() {
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
DeleteWebhookCmd cmd = Mockito.mock(DeleteWebhookCmd.class);
Mockito.when(cmd.getId()).thenReturn(1L);
WebhookVO webhookVO = Mockito.mock(WebhookVO.class);
Mockito.when(webhookDao.findById(1L)).thenReturn(webhookVO);
CallContext callContextMock = Mockito.mock(CallContext.class);
Account account = Mockito.mock(Account.class);
Mockito.when(callContextMock.getCallingAccount()).thenReturn(account);
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.doNothing().when(accountManager).checkAccess(account,
SecurityChecker.AccessType.OperateEntry, false, webhookVO);
Mockito.doReturn(true).when(webhookDao).remove(Mockito.anyLong());
Assert.assertTrue(webhookApiServiceImpl.deleteWebhook(cmd));
}
}
@Test
public void testValidateWebhookOwnerPayloadUrlNonExistent() {
Mockito.when(webhookDao.findByAccountAndPayloadUrl(Mockito.anyLong(), Mockito.anyString())).thenReturn(null);
Account account = Mockito.mock(Account.class);
String url = "url";
webhookApiServiceImpl.validateWebhookOwnerPayloadUrl(account, url, null);
webhookApiServiceImpl.validateWebhookOwnerPayloadUrl(account, url, Mockito.mock(Webhook.class));
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateWebhookOwnerPayloadUrlCreateExist() {
Mockito.when(webhookDao.findByAccountAndPayloadUrl(Mockito.anyLong(), Mockito.anyString()))
.thenReturn(Mockito.mock(WebhookVO.class));
webhookApiServiceImpl.validateWebhookOwnerPayloadUrl(Mockito.mock(Account.class), "url",
null);
}
private Webhook mockWebhook(long id) {
Webhook webhook = Mockito.mock(Webhook.class);
Mockito.when(webhook.getId()).thenReturn(id);
return webhook;
}
private WebhookVO mockWebhookVO(long id) {
WebhookVO webhook = Mockito.mock(WebhookVO.class);
Mockito.when(webhook.getId()).thenReturn(id);
return webhook;
}
@Test
public void testValidateWebhookOwnerPayloadUrlUpdateSameExist() {
WebhookVO webhookVO = mockWebhookVO(1L);
Mockito.when(webhookDao.findByAccountAndPayloadUrl(Mockito.anyLong(), Mockito.anyString()))
.thenReturn(webhookVO);
webhookApiServiceImpl.validateWebhookOwnerPayloadUrl(Mockito.mock(Account.class), "url",
mockWebhook(1L));
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateWebhookOwnerPayloadUrlUpdateDifferentExist() {
WebhookVO webhookVO = mockWebhookVO(2L);
Mockito.when(webhookDao.findByAccountAndPayloadUrl(Mockito.anyLong(), Mockito.anyString()))
.thenReturn(webhookVO);
webhookApiServiceImpl.validateWebhookOwnerPayloadUrl(Mockito.mock(Account.class), "url",
mockWebhook(1L));
}
@Test
public void testGetNormalizedPayloadUrl() {
Assert.assertEquals("http://abc.com", webhookApiServiceImpl.getNormalizedPayloadUrl("abc.com"));
Assert.assertEquals("http://abc.com", webhookApiServiceImpl.getNormalizedPayloadUrl("http://abc.com"));
Assert.assertEquals("https://abc.com",
webhookApiServiceImpl.getNormalizedPayloadUrl("https://abc.com"));
}
}

View File

@ -0,0 +1,62 @@
// 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.mom.webhook;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.DecoderException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class WebhookDeliveryThreadTest {
@InjectMocks
WebhookDeliveryThread webhookDeliveryThread;
@Test
public void testIsValidJson() {
Assert.assertFalse(webhookDeliveryThread.isValidJson("text"));
Assert.assertTrue(webhookDeliveryThread.isValidJson("{ \"CloudStack\": \"works!\" }"));
Assert.assertTrue(webhookDeliveryThread.isValidJson("[{ \"CloudStack\": \"works!\" }]"));
}
@Test
public void testGenerateHMACSignature() {
String data = "CloudStack works!";
String key = "Pj4pnwSUBZ4wQFXw2zWdVY1k5Ku9bIy70wCNG1DmS8keO7QapCLw2Axtgc2nEPYzfFCfB38ATNLt6caDqU2dSw";
String result = "HYLWSII5Ap23WeSaykNsIo6mOhmV3d18s5p2cq2ebCA=";
try {
String sign = WebhookDeliveryThread.generateHMACSignature(data, key);
Assert.assertEquals(result, sign);
} catch (InvalidKeyException | NoSuchAlgorithmException | DecoderException e) {
Assert.fail(e.getMessage());
}
}
@Test
public void testSetDeliveryTries() {
int tries = 2;
webhookDeliveryThread.setDeliveryTries(tries);
Assert.assertEquals(tries, ReflectionTestUtils.getField(webhookDeliveryThread, "deliveryTries"));
}
}

View File

@ -0,0 +1,106 @@
// 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.mom.webhook;
import java.util.HashMap;
import java.util.UUID;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.framework.events.EventSubscriber;
import org.apache.cloudstack.framework.events.EventTopic;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class WebhookEventBusTest {
@Mock
WebhookService webhookService;
@InjectMocks
WebhookEventBus eventBus = new WebhookEventBus();
@Test
public void testConfigure() {
String name = "name";
try {
Assert.assertTrue(eventBus.configure(name, new HashMap<>()));
String result = (String)ReflectionTestUtils.getField(eventBus, "_name");
Assert.assertEquals(name, result);
} catch (ConfigurationException e) {
Assert.fail("Error configuring");
}
}
@Test
public void testSetName() {
String name = "name";
eventBus.setName(name);
String result = (String)ReflectionTestUtils.getField(eventBus, "_name");
Assert.assertEquals(name, result);
}
@Test
public void testGetName() {
String name = "name";
ReflectionTestUtils.setField(eventBus, "_name", name);
Assert.assertEquals(name, eventBus.getName());
}
@Test
public void testStart() {
Assert.assertTrue(eventBus.start());
}
@Test
public void testStop() {
Assert.assertTrue(eventBus.stop());
}
@Test
public void testSubscribe() {
try {
Assert.assertNotNull(eventBus.subscribe(Mockito.mock(EventTopic.class), Mockito.mock(EventSubscriber.class)));
} catch (EventBusException e) {
Assert.fail("Error subscribing");
}
}
@Test
public void testUnsubscribe() {
try {
eventBus.unsubscribe(Mockito.mock(UUID.class), Mockito.mock(EventSubscriber.class));
} catch (EventBusException e) {
Assert.fail("Error unsubscribing");
}
}
@Test(expected = EventBusException.class)
public void testPublishException() throws EventBusException {
Mockito.doThrow(EventBusException.class).when(webhookService).handleEvent(Mockito.any(Event.class));
eventBus.publish(Mockito.mock(Event.class));
}
}

View File

@ -0,0 +1,173 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class)
public class CreateWebhookCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runStringMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
CreateWebhookCmd cmd = new CreateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
String value = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
CreateWebhookCmd cmd = new CreateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runBooleanMemberTest(String memberName) {
String methodName = "is" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
CreateWebhookCmd cmd = new CreateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertFalse((boolean)getCommandMethodValue(cmd, methodName));
Boolean value = true;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetName() {
runStringMemberTest("name");
}
@Test
public void testGetDescription() {
runStringMemberTest("description");
}
@Test
public void testGetPayloadUrl() {
runStringMemberTest("payloadUrl");
}
@Test
public void testGetSecretKey() {
runStringMemberTest("secretKey");
}
@Test
public void testGetScope() {
runStringMemberTest("scope");
}
@Test
public void testGetState() {
runStringMemberTest("state");
}
@Test
public void testGetAccount() {
runStringMemberTest("accountName");
}
@Test
public void testGetDomainId() {
runLongMemberTest("domainId");
}
@Test
public void testGetProjectId() {
runLongMemberTest("projectId");
}
@Test
public void testIsSslVerification() {
runBooleanMemberTest("sslVerification");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
CreateWebhookCmd cmd = new CreateWebhookCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test(expected = ServerApiException.class)
public void testExecuteNullResponse() {
CreateWebhookCmd cmd = new CreateWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.createWebhook(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteCRE() {
CreateWebhookCmd cmd = new CreateWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.createWebhook(cmd)).thenThrow(CloudRuntimeException.class);
cmd.execute();
}
@Test
public void testExecute() {
CreateWebhookCmd cmd = new CreateWebhookCmd();
cmd.webhookApiService = webhookApiService;
WebhookResponse response = new WebhookResponse();
Mockito.when(webhookApiService.createWebhook(cmd)).thenReturn(response);
cmd.execute();
Assert.assertEquals(cmd.getCommandName(), response.getResponseName());
}
}

View File

@ -0,0 +1,106 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class)
public class DeleteWebhookCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
DeleteWebhookCmd cmd = new DeleteWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
DeleteWebhookCmd cmd = new DeleteWebhookCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test(expected = ServerApiException.class)
public void testExecuteFalseResponse() {
DeleteWebhookCmd cmd = new DeleteWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.deleteWebhook(cmd)).thenReturn(false);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteCRE() {
DeleteWebhookCmd cmd = new DeleteWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.deleteWebhook(cmd)).thenThrow(CloudRuntimeException.class);
cmd.execute();
}
@Test
public void testExecute() {
DeleteWebhookCmd cmd = new DeleteWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.deleteWebhook(cmd)).thenReturn(true);
cmd.execute();
Assert.assertNotNull(cmd.getResponseObject());
}
}

View File

@ -0,0 +1,108 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class)
public class DeleteWebhookDeliveryCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
DeleteWebhookDeliveryCmd cmd = new DeleteWebhookDeliveryCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetWebhookId() {
runLongMemberTest("webhookId");
}
@Test
public void testGetManagementServerId() {
runLongMemberTest("managementServerId");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
DeleteWebhookDeliveryCmd cmd = new DeleteWebhookDeliveryCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test(expected = ServerApiException.class)
public void testExecuteCRE() {
DeleteWebhookDeliveryCmd cmd = new DeleteWebhookDeliveryCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.deleteWebhookDelivery(cmd)).thenThrow(CloudRuntimeException.class);
cmd.execute();
}
@Test
public void testExecute() {
DeleteWebhookDeliveryCmd cmd = new DeleteWebhookDeliveryCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.deleteWebhookDelivery(cmd)).thenReturn(10);
cmd.execute();
Assert.assertNotNull(cmd.getResponseObject());
}
}

View File

@ -0,0 +1,153 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class)
public class ExecuteWebhookDeliveryCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runStringMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
String value = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runBooleanMemberTest(String memberName) {
String methodName = "is" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Boolean value = true;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetWebhookId() {
runLongMemberTest("webhookId");
}
@Test
public void testGetPayloadUrl() {
runStringMemberTest("payloadUrl");
}
@Test
public void testGetSecretKey() {
runStringMemberTest("secretKey");
}
@Test
public void testIsSslVerification() {
runBooleanMemberTest("sslVerification");
}
@Test
public void testGetPayload() {
runStringMemberTest("payload");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test(expected = ServerApiException.class)
public void testExecuteNullResponse() {
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.executeWebhookDelivery(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteCRE() {
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.executeWebhookDelivery(cmd)).thenThrow(CloudRuntimeException.class);
cmd.execute();
}
@Test
public void testExecute() {
ExecuteWebhookDeliveryCmd cmd = new ExecuteWebhookDeliveryCmd();
cmd.webhookApiService = webhookApiService;
WebhookDeliveryResponse response = new WebhookDeliveryResponse();
Mockito.when(webhookApiService.executeWebhookDelivery(cmd)).thenReturn(response);
cmd.execute();
Assert.assertEquals(cmd.getCommandName(), response.getResponseName());
}
}

View File

@ -0,0 +1,141 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.api.response.WebhookDeliveryResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
@RunWith(MockitoJUnitRunner.class)
public class ListWebhookDeliveriesCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ListWebhookDeliveriesCmd cmd = new ListWebhookDeliveriesCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runStringMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ListWebhookDeliveriesCmd cmd = new ListWebhookDeliveriesCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
String value = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runDateMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ListWebhookDeliveriesCmd cmd = new ListWebhookDeliveriesCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Date value = new Date();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetWebhookId() {
runLongMemberTest("webhookId");
}
@Test
public void testGetManagementServerId() {
runLongMemberTest("managementServerId");
}
@Test
public void testStartDate() {
runDateMemberTest("startDate");
}
@Test
public void testEndDate() {
runDateMemberTest("endDate");
}
@Test
public void testEventType() {
runStringMemberTest("eventType");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
ListWebhookDeliveriesCmd cmd = new ListWebhookDeliveriesCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test
public void testExecute() {
ListWebhookDeliveriesCmd cmd = new ListWebhookDeliveriesCmd();
cmd.webhookApiService = webhookApiService;
List<WebhookDeliveryResponse> responseList = new ArrayList<>();
ListResponse<WebhookDeliveryResponse> listResponse = new ListResponse<>();
listResponse.setResponses(responseList);
Mockito.when(webhookApiService.listWebhookDeliveries(cmd)).thenReturn(listResponse);
cmd.execute();
Assert.assertNotNull(cmd.getResponseObject());
}
}

View File

@ -0,0 +1,105 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class ListWebhooksCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runStringMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ListWebhooksCmd cmd = new ListWebhooksCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
String value = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
ListWebhooksCmd cmd = new ListWebhooksCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetName() {
runStringMemberTest("name");
}
@Test
public void testGetState() {
runStringMemberTest("state");
}
@Test
public void testGetScope() {
runStringMemberTest("scope");
}
@Test
public void testExecute() {
ListWebhooksCmd cmd = new ListWebhooksCmd();
cmd.webhookApiService = webhookApiService;
List<WebhookResponse> responseList = new ArrayList<>();
ListResponse<WebhookResponse> listResponse = new ListResponse<>();
listResponse.setResponses(responseList);
Mockito.when(webhookApiService.listWebhooks(cmd)).thenReturn(listResponse);
cmd.execute();
Assert.assertNotNull(cmd.getResponseObject());
}
}

View File

@ -0,0 +1,163 @@
// 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.mom.webhook.api.command.user;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.mom.webhook.WebhookApiService;
import org.apache.cloudstack.mom.webhook.api.response.WebhookResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class)
public class UpdateWebhookCmdTest {
@Mock
WebhookApiService webhookApiService;
private Object getCommandMethodValue(Object obj, String methodName) {
Object result = null;
try {
Method method = obj.getClass().getMethod(methodName);
result = method.invoke(obj);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Assert.fail(String.format("Failed to get method %s value", methodName));
}
return result;
}
private void runStringMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
String value = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runLongMemberTest(String memberName) {
String methodName = "get" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Long value = 100L;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
private void runBooleanMemberTest(String memberName) {
String methodName = "is" + memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
ReflectionTestUtils.setField(cmd, memberName, null);
Assert.assertNull(getCommandMethodValue(cmd, methodName));
Boolean value = true;
ReflectionTestUtils.setField(cmd, memberName, value);
Assert.assertEquals(value, getCommandMethodValue(cmd, methodName));
}
@Test
public void testGetId() {
runLongMemberTest("id");
}
@Test
public void testGetName() {
runStringMemberTest("name");
}
@Test
public void testGetDescription() {
runStringMemberTest("description");
}
@Test
public void testGetPayloadUrl() {
runStringMemberTest("payloadUrl");
}
@Test
public void testGetSecretKey() {
runStringMemberTest("secretKey");
}
@Test
public void testGetScope() {
runStringMemberTest("scope");
}
@Test
public void testGetState() {
runStringMemberTest("state");
}
@Test
public void testIsSslVerification() {
runBooleanMemberTest("sslVerification");
}
@Test
public void testGetEntityOwnerId() {
Account account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
CallContext.register(user, account);
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
Assert.assertEquals(account.getId(), cmd.getEntityOwnerId());
}
@Test(expected = ServerApiException.class)
public void testExecuteNullResponse() {
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.updateWebhook(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteCRE() {
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
cmd.webhookApiService = webhookApiService;
Mockito.when(webhookApiService.updateWebhook(cmd)).thenThrow(CloudRuntimeException.class);
cmd.execute();
}
@Test
public void testExecute() {
UpdateWebhookCmd cmd = new UpdateWebhookCmd();
cmd.webhookApiService = webhookApiService;
WebhookResponse response = new WebhookResponse();
Mockito.when(webhookApiService.updateWebhook(cmd)).thenReturn(response);
cmd.execute();
Assert.assertEquals(cmd.getCommandName(), response.getResponseName());
}
}

View File

@ -18,21 +18,19 @@
package org.apache.cloudstack.network.contrail.management; package org.apache.cloudstack.network.contrail.management;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.events.EventBus;
import org.apache.cloudstack.framework.events.EventBusException;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component;
import com.cloud.event.ActionEvent; import com.cloud.event.ActionEvent;
import com.cloud.event.ActionEvents; import com.cloud.event.ActionEvents;
@ -43,15 +41,20 @@ import com.cloud.server.ManagementService;
import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ComponentMethodInterceptor; import com.cloud.utils.component.ComponentMethodInterceptor;
@Component @Component
public class EventUtils { public class EventUtils {
protected static Logger LOGGER = LogManager.getLogger(EventUtils.class); protected static Logger LOGGER = LogManager.getLogger(EventUtils.class);
protected static EventBus s_eventBus = null; private static EventDistributor eventDistributor;
public EventUtils() { public EventUtils() {
} }
public static void setEventDistributor(EventDistributor eventDistributorImpl) {
eventDistributor = eventDistributorImpl;
}
private static void publishOnMessageBus(String eventCategory, String eventType, String details, Event.State state) { private static void publishOnMessageBus(String eventCategory, String eventType, String details, Event.State state) {
if (state != com.cloud.event.Event.State.Completed) { if (state != com.cloud.event.Event.State.Completed) {
@ -59,7 +62,7 @@ public class EventUtils {
} }
try { try {
s_eventBus = ComponentContext.getComponent(EventBus.class); setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
} catch (NoSuchBeanDefinitionException nbe) { } catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return return; // no provider is configured to provide events bus, so just return
} }
@ -67,18 +70,12 @@ public class EventUtils {
org.apache.cloudstack.framework.events.Event event = org.apache.cloudstack.framework.events.Event event =
new org.apache.cloudstack.framework.events.Event(ManagementService.Name, eventCategory, eventType, EventTypes.getEntityForEvent(eventType), null); new org.apache.cloudstack.framework.events.Event(ManagementService.Name, eventCategory, eventType, EventTypes.getEntityForEvent(eventType), null);
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<>();
eventDescription.put("event", eventType); eventDescription.put("event", eventType);
eventDescription.put("status", state.toString()); eventDescription.put("status", state.toString());
eventDescription.put("details", details); eventDescription.put("details", details);
event.setDescription(eventDescription); event.setDescription(eventDescription);
try { eventDistributor.publish(event);
s_eventBus.publish(event);
} catch (EventBusException evx) {
String errMsg = "Failed to publish contrail event.";
LOGGER.warn(errMsg, evx);
}
} }
public static class EventInterceptor implements ComponentMethodInterceptor, MethodInterceptor { public static class EventInterceptor implements ComponentMethodInterceptor, MethodInterceptor {
@ -119,7 +116,7 @@ public class EventUtils {
} }
protected List<ActionEvent> getActionEvents(Method m) { protected List<ActionEvent> getActionEvents(Method m) {
List<ActionEvent> result = new ArrayList<ActionEvent>(); List<ActionEvent> result = new ArrayList<>();
ActionEvents events = m.getAnnotation(ActionEvents.class); ActionEvents events = m.getAnnotation(ActionEvents.class);

View File

@ -79,6 +79,7 @@
<module>event-bus/inmemory</module> <module>event-bus/inmemory</module>
<module>event-bus/kafka</module> <module>event-bus/kafka</module>
<module>event-bus/rabbitmq</module> <module>event-bus/rabbitmq</module>
<module>event-bus/webhook</module>
<module>ha-planners/skip-heurestics</module> <module>ha-planners/skip-heurestics</module>

View File

@ -95,8 +95,7 @@ import org.apache.cloudstack.config.ApiServiceConfiguration;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
@ -132,10 +131,9 @@ import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent; import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate; import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer; import org.apache.http.protocol.ResponseServer;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.api.dispatch.DispatchChainFactory; import com.cloud.api.dispatch.DispatchChainFactory;
@ -196,26 +194,26 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
*/ */
private static final String CONTROL_CHARACTERS = "[\000-\011\013-\014\016-\037\177]"; private static final String CONTROL_CHARACTERS = "[\000-\011\013-\014\016-\037\177]";
@Inject
private AccountManager accountMgr;
@Inject
private APIAuthenticationManager authManager;
@Inject @Inject
private ApiDispatcher dispatcher; private ApiDispatcher dispatcher;
@Inject @Inject
private DispatchChainFactory dispatchChainFactory; private AsyncJobManager asyncMgr;
@Inject @Inject
private AccountManager accountMgr; private DispatchChainFactory dispatchChainFactory;
@Inject @Inject
private DomainManager domainMgr; private DomainManager domainMgr;
@Inject @Inject
private DomainDao domainDao; private DomainDao domainDao;
@Inject @Inject
private UUIDManager uuidMgr;
@Inject
private AsyncJobManager asyncMgr;
@Inject
private EntityManager entityMgr; private EntityManager entityMgr;
@Inject @Inject
private APIAuthenticationManager authManager;
@Inject
private ProjectDao projectDao; private ProjectDao projectDao;
@Inject
private UUIDManager uuidMgr;
private List<PluggableService> pluggableServices; private List<PluggableService> pluggableServices;
@ -224,6 +222,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@Inject @Inject
private ApiAsyncJobDispatcher asyncDispatcher; private ApiAsyncJobDispatcher asyncDispatcher;
private EventDistributor eventDistributor = null;
private static int s_workerCount = 0; private static int s_workerCount = 0;
private static Map<String, List<Class<?>>> s_apiNameCmdClassMap = new HashMap<String, List<Class<?>>>(); private static Map<String, List<Class<?>>> s_apiNameCmdClassMap = new HashMap<String, List<Class<?>>>();
@ -311,6 +310,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return true; return true;
} }
public void setEventDistributor(EventDistributor eventDistributor) {
this.eventDistributor = eventDistributor;
}
@MessageHandler(topic = AsyncJob.Topics.JOB_EVENT_PUBLISH) @MessageHandler(topic = AsyncJob.Topics.JOB_EVENT_PUBLISH)
public void handleAsyncJobPublishEvent(String subject, String senderAddress, Object args) { public void handleAsyncJobPublishEvent(String subject, String senderAddress, Object args) {
assert (args != null); assert (args != null);
@ -322,12 +325,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (logger.isTraceEnabled()) if (logger.isTraceEnabled())
logger.trace("Handle asyjob publish event " + jobEvent); logger.trace("Handle asyjob publish event " + jobEvent);
if (eventDistributor == null) {
EventBus eventBus = null; setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
try {
eventBus = ComponentContext.getComponent(EventBus.class);
} catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return
} }
if (!job.getDispatcher().equalsIgnoreCase("ApiAsyncJobDispatcher")) { if (!job.getDispatcher().equalsIgnoreCase("ApiAsyncJobDispatcher")) {
@ -340,7 +339,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
// Get the event type from the cmdInfo json string // Get the event type from the cmdInfo json string
String info = job.getCmdInfo(); String info = job.getCmdInfo();
String cmdEventType = "unknown"; String cmdEventType = "unknown";
Map<String, Object> cmdInfoObj = new HashMap<String, Object>(); Map<String, Object> cmdInfoObj = new HashMap<>();
if (info != null) { if (info != null) {
Type type = new TypeToken<Map<String, String>>(){}.getType(); Type type = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> cmdInfo = ApiGsonHelper.getBuilder().create().fromJson(info, type); Map<String, String> cmdInfo = ApiGsonHelper.getBuilder().create().fromJson(info, type);
@ -368,7 +367,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
org.apache.cloudstack.framework.events.Event event = new org.apache.cloudstack.framework.events.Event("management-server", EventCategory.ASYNC_JOB_CHANGE_EVENT.getName(), org.apache.cloudstack.framework.events.Event event = new org.apache.cloudstack.framework.events.Event("management-server", EventCategory.ASYNC_JOB_CHANGE_EVENT.getName(),
jobEvent, instanceType, instanceUuid); jobEvent, instanceType, instanceUuid);
Map<String, Object> eventDescription = new HashMap<String, Object>(); Map<String, Object> eventDescription = new HashMap<>();
eventDescription.put("command", job.getCmd()); eventDescription.put("command", job.getCmd());
eventDescription.put("user", userJobOwner.getUuid()); eventDescription.put("user", userJobOwner.getUuid());
eventDescription.put("account", jobOwner.getUuid()); eventDescription.put("account", jobOwner.getUuid());
@ -389,13 +388,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
eventDescription.put("domainname", domain.getName()); eventDescription.put("domainname", domain.getName());
} }
event.setDescription(eventDescription); event.setDescription(eventDescription);
eventDistributor.publish(event);
try {
eventBus.publish(event);
} catch (EventBusException evx) {
String errMsg = "Failed to publish async job event on the event bus.";
logger.warn(errMsg, evx);
}
} }
@Override @Override

View File

@ -32,12 +32,11 @@ import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
@ -63,7 +62,7 @@ public class ActionEventUtils {
private static AccountDao s_accountDao; private static AccountDao s_accountDao;
private static ProjectDao s_projectDao; private static ProjectDao s_projectDao;
protected static UserDao s_userDao; protected static UserDao s_userDao;
protected static EventBus s_eventBus = null; private static EventDistributor eventDistributor;
protected static EntityManager s_entityMgr; protected static EntityManager s_entityMgr;
protected static ConfigurationDao s_configDao; protected static ConfigurationDao s_configDao;
@ -101,8 +100,10 @@ public class ActionEventUtils {
public static Long onActionEvent(Long userId, Long accountId, Long domainId, String type, String description, Long resourceId, String resourceType) { public static Long onActionEvent(Long userId, Long accountId, Long domainId, String type, String description, Long resourceId, String resourceType) {
Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type); Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type);
publishOnEventBus(userId, accountId, EventCategory.ACTION_EVENT.getName(), type, com.cloud.event.Event.State.Completed, description, resourceDetails.second(), resourceDetails.third()); Event event = persistActionEvent(userId, accountId, domainId, null, type, Event.State.Completed,
Event event = persistActionEvent(userId, accountId, domainId, null, type, Event.State.Completed, true, description, resourceDetails.first(), resourceDetails.third(), null); true, description, resourceDetails.first(), resourceDetails.third(), null);
publishOnEventBus(event, userId, accountId, domainId, EventCategory.ACTION_EVENT.getName(), type,
com.cloud.event.Event.State.Completed, description, resourceDetails.second(), resourceDetails.third());
return event.getId(); return event.getId();
} }
@ -111,8 +112,10 @@ public class ActionEventUtils {
*/ */
public static Long onScheduledActionEvent(Long userId, Long accountId, String type, String description, Long resourceId, String resourceType, boolean eventDisplayEnabled, long startEventId) { public static Long onScheduledActionEvent(Long userId, Long accountId, String type, String description, Long resourceId, String resourceType, boolean eventDisplayEnabled, long startEventId) {
Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type); Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type);
publishOnEventBus(userId, accountId, EventCategory.ACTION_EVENT.getName(), type, com.cloud.event.Event.State.Scheduled, description, resourceDetails.second(), resourceDetails.third()); Event event = persistActionEvent(userId, accountId, null, null, type, Event.State.Scheduled,
Event event = persistActionEvent(userId, accountId, null, null, type, Event.State.Scheduled, eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId); eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId);
publishOnEventBus(event, userId, accountId, EventCategory.ACTION_EVENT.getName(), type,
com.cloud.event.Event.State.Scheduled, description, resourceDetails.second(), resourceDetails.third());
return event.getId(); return event.getId();
} }
@ -136,8 +139,10 @@ public class ActionEventUtils {
*/ */
public static Long onStartedActionEvent(Long userId, Long accountId, String type, String description, Long resourceId, String resourceType, boolean eventDisplayEnabled, long startEventId) { public static Long onStartedActionEvent(Long userId, Long accountId, String type, String description, Long resourceId, String resourceType, boolean eventDisplayEnabled, long startEventId) {
Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type); Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type);
publishOnEventBus(userId, accountId, EventCategory.ACTION_EVENT.getName(), type, com.cloud.event.Event.State.Started, description, resourceDetails.second(), resourceDetails.third()); Event event = persistActionEvent(userId, accountId, null, null, type, Event.State.Started,
Event event = persistActionEvent(userId, accountId, null, null, type, Event.State.Started, eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId); eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId);
publishOnEventBus(event, userId, accountId, EventCategory.ACTION_EVENT.getName(), type,
com.cloud.event.Event.State.Started, description, resourceDetails.second(), resourceDetails.third());
return event.getId(); return event.getId();
} }
@ -148,16 +153,20 @@ public class ActionEventUtils {
public static Long onCompletedActionEvent(Long userId, Long accountId, String level, String type, boolean eventDisplayEnabled, String description, Long resourceId, String resourceType, long startEventId) { public static Long onCompletedActionEvent(Long userId, Long accountId, String level, String type, boolean eventDisplayEnabled, String description, Long resourceId, String resourceType, long startEventId) {
Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type); Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type);
publishOnEventBus(userId, accountId, EventCategory.ACTION_EVENT.getName(), type, com.cloud.event.Event.State.Completed, description, resourceDetails.second(), resourceDetails.third()); Event event = persistActionEvent(userId, accountId, null, level, type, Event.State.Completed,
Event event = persistActionEvent(userId, accountId, null, level, type, Event.State.Completed, eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId); eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId);
publishOnEventBus(event, userId, accountId, EventCategory.ACTION_EVENT.getName(), type,
com.cloud.event.Event.State.Completed, description, resourceDetails.second(), resourceDetails.third());
return event.getId(); return event.getId();
} }
public static Long onCreatedActionEvent(Long userId, Long accountId, String level, String type, boolean eventDisplayEnabled, String description, Long resourceId, String resourceType) { public static Long onCreatedActionEvent(Long userId, Long accountId, String level, String type, boolean eventDisplayEnabled, String description, Long resourceId, String resourceType) {
Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type); Ternary<Long, String, String> resourceDetails = getResourceDetails(resourceId, resourceType, type);
publishOnEventBus(userId, accountId, EventCategory.ACTION_EVENT.getName(), type, com.cloud.event.Event.State.Created, description, resourceDetails.second(), resourceDetails.third()); Event event = persistActionEvent(userId, accountId, null, level, type, Event.State.Created,
Event event = persistActionEvent(userId, accountId, null, level, type, Event.State.Created, eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), null); eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), null);
publishOnEventBus(event, userId, accountId, EventCategory.ACTION_EVENT.getName(), type,
com.cloud.event.Event.State.Created, description, resourceDetails.second(), resourceDetails.third());
return event.getId(); return event.getId();
} }
@ -193,20 +202,25 @@ public class ActionEventUtils {
return event; return event;
} }
private static void publishOnEventBus(long userId, long accountId, String eventCategory, String eventType, Event.State state, String description, String resourceUuid, String resourceType) { private static void publishOnEventBus(Event eventRecord, long userId, long accountId, Long domainId,
String eventCategory, String eventType, Event.State state, String description, String resourceUuid,
String resourceType) {
String configKey = Config.PublishActionEvent.key(); String configKey = Config.PublishActionEvent.key();
String value = s_configDao.getValue(configKey); String value = s_configDao.getValue(configKey);
boolean configValue = Boolean.parseBoolean(value); boolean configValue = Boolean.parseBoolean(value);
if(!configValue) if(!configValue)
return; return;
try { try {
s_eventBus = ComponentContext.getComponent(EventBus.class); eventDistributor = ComponentContext.getComponent(EventDistributor.class);
} catch (NoSuchBeanDefinitionException nbe) { } catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return return; // no provider is configured to provide events bus, so just return
} }
org.apache.cloudstack.framework.events.Event event = org.apache.cloudstack.framework.events.Event event =
new org.apache.cloudstack.framework.events.Event(ManagementService.Name, eventCategory, eventType, resourceType, resourceUuid); new org.apache.cloudstack.framework.events.Event(ManagementService.Name, eventCategory, eventType, resourceType, resourceUuid);
event.setEventId(eventRecord.getId());
event.setEventUuid(eventRecord.getUuid());
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<String, String>();
Project project = s_projectDao.findByProjectAccountId(accountId); Project project = s_projectDao.findByProjectAccountId(accountId);
@ -219,6 +233,9 @@ public class ActionEventUtils {
return; return;
if (project != null) if (project != null)
eventDescription.put("project", project.getUuid()); eventDescription.put("project", project.getUuid());
event.setResourceAccountId(accountId);
event.setResourceAccountUuid(account.getUuid());
event.setResourceDomainId(domainId == null ? account.getDomainId() : domainId);
eventDescription.put("user", user.getUuid()); eventDescription.put("user", user.getUuid());
eventDescription.put("account", account.getUuid()); eventDescription.put("account", account.getUuid());
eventDescription.put("event", eventType); eventDescription.put("event", eventType);
@ -234,11 +251,13 @@ public class ActionEventUtils {
event.setDescription(eventDescription); event.setDescription(eventDescription);
try { eventDistributor.publish(event);
s_eventBus.publish(event); }
} catch (EventBusException e) {
LOGGER.warn("Failed to publish action event on the event bus."); private static void publishOnEventBus(Event event, long userId, long accountId, String eventCategory,
} String eventType, Event.State state, String description, String resourceUuid, String resourceType) {
publishOnEventBus(event, userId, accountId, null, eventCategory, eventType, state, description,
resourceUuid, resourceType);
} }
private static Ternary<Long, String, String> getResourceDetailsUsingEntityClassAndContext(Class<?> entityClass, ApiCommandResourceType resourceType) { private static Ternary<Long, String, String> getResourceDetailsUsingEntityClassAndContext(Class<?> entityClass, ApiCommandResourceType resourceType) {

View File

@ -25,15 +25,13 @@ import java.util.Map;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.logging.log4j.Logger; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus;
import org.apache.cloudstack.framework.events.EventBusException;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
import com.cloud.dc.DataCenterVO; import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO; import com.cloud.dc.HostPodVO;
@ -48,8 +46,8 @@ public class AlertGenerator {
protected static Logger LOGGER = LogManager.getLogger(AlertGenerator.class); protected static Logger LOGGER = LogManager.getLogger(AlertGenerator.class);
private static DataCenterDao s_dcDao; private static DataCenterDao s_dcDao;
private static HostPodDao s_podDao; private static HostPodDao s_podDao;
protected static EventBus s_eventBus = null;
protected static ConfigurationDao s_configDao; protected static ConfigurationDao s_configDao;
protected static EventDistributor eventDistributor;
@Inject @Inject
DataCenterDao dcDao; DataCenterDao dcDao;
@ -76,9 +74,9 @@ public class AlertGenerator {
if(!configValue) if(!configValue)
return; return;
try { try {
s_eventBus = ComponentContext.getComponent(EventBus.class); eventDistributor = ComponentContext.getComponent(EventDistributor.class);
} catch (NoSuchBeanDefinitionException nbe) { } catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return return; // no provider is configured to provide events distributor, so just return
} }
org.apache.cloudstack.framework.events.Event event = org.apache.cloudstack.framework.events.Event event =
@ -107,10 +105,6 @@ public class AlertGenerator {
event.setDescription(eventDescription); event.setDescription(eventDescription);
try { eventDistributor.publish(event);
s_eventBus.publish(event);
} catch (EventBusException e) {
LOGGER.warn("Failed to publish alert on the event bus.");
}
} }
} }

View File

@ -18,6 +18,7 @@ package com.cloud.projects;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -35,6 +36,7 @@ import javax.inject.Inject;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.ProjectRole;
import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.acl.dao.ProjectRoleDao; import org.apache.cloudstack.acl.dao.ProjectRoleDao;
@ -48,7 +50,9 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.utils.mailing.MailAddress; import org.apache.cloudstack.utils.mailing.MailAddress;
import org.apache.cloudstack.utils.mailing.SMTPMailProperties; import org.apache.cloudstack.utils.mailing.SMTPMailProperties;
import org.apache.cloudstack.utils.mailing.SMTPMailSender; import org.apache.cloudstack.utils.mailing.SMTPMailSender;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiDBUtils;
@ -89,6 +93,7 @@ import com.cloud.user.ResourceLimitService;
import com.cloud.user.User; import com.cloud.user.User;
import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserDao;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.DB; import com.cloud.utils.db.DB;
@ -163,6 +168,17 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
private String senderAddress; private String senderAddress;
protected SMTPMailSender mailSender; protected SMTPMailSender mailSender;
protected List<? extends ControlledEntity> listWebhooksForProject(Project project) {
List<? extends ControlledEntity> webhooks = new ArrayList<>();
try {
WebhookHelper webhookService = ComponentContext.getDelegateComponentOfType(WebhookHelper.class);
webhooks = webhookService.listWebhooksByAccount(project.getProjectAccountId());
} catch (NoSuchBeanDefinitionException ignored) {
logger.debug("No WebhookHelper bean found");
}
return webhooks;
}
@Override @Override
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
@ -339,8 +355,9 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId()); List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId());
List<NetworkVO> networks = _networkDao.listByOwner(project.getProjectAccountId()); List<NetworkVO> networks = _networkDao.listByOwner(project.getProjectAccountId());
List<? extends Vpc> vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId()); List<? extends Vpc> vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId());
List<? extends ControlledEntity> webhooks = listWebhooksForProject(project);
Optional<String> message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs) Optional<String> message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs, webhooks)
.filter(entity -> !entity.isEmpty()) .filter(entity -> !entity.isEmpty())
.map(entity -> entity.size() + " " + entity.get(0).getEntityType().getSimpleName() + " to clean up") .map(entity -> entity.size() + " " + entity.get(0).getEntityType().getSimpleName() + " to clean up")
.findFirst(); .findFirst();

View File

@ -26,11 +26,9 @@ import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
@ -47,12 +45,12 @@ import com.cloud.utils.fsm.StateMachine2;
@Component @Component
public class SnapshotStateListener implements StateListener<State, Event, SnapshotVO> { public class SnapshotStateListener implements StateListener<State, Event, SnapshotVO> {
protected static EventBus s_eventBus = null;
protected static ConfigurationDao s_configDao; protected static ConfigurationDao s_configDao;
@Inject @Inject
private ConfigurationDao configDao; private ConfigurationDao configDao;
private EventDistributor eventDistributor = null;
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
public SnapshotStateListener() { public SnapshotStateListener() {
@ -64,6 +62,10 @@ public class SnapshotStateListener implements StateListener<State, Event, Snapsh
s_configDao = configDao; s_configDao = configDao;
} }
public void setEventDistributor(EventDistributor eventDistributor) {
this.eventDistributor = eventDistributor;
}
@Override @Override
public boolean preStateTransitionEvent(State oldState, Event event, State newState, SnapshotVO vo, boolean status, Object opaque) { public boolean preStateTransitionEvent(State oldState, Event event, State newState, SnapshotVO vo, boolean status, Object opaque) {
pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState); pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState);
@ -84,17 +86,15 @@ public class SnapshotStateListener implements StateListener<State, Event, Snapsh
if(!configValue) { if(!configValue) {
return; return;
} }
try { if (eventDistributor == null) {
s_eventBus = ComponentContext.getComponent(EventBus.class); setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
} catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return
} }
String resourceName = getEntityFromClassName(Snapshot.class.getName()); String resourceName = getEntityFromClassName(Snapshot.class.getName());
org.apache.cloudstack.framework.events.Event eventMsg = org.apache.cloudstack.framework.events.Event eventMsg =
new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName, new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName,
vo.getUuid()); vo.getUuid());
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<>();
eventDescription.put("resource", resourceName); eventDescription.put("resource", resourceName);
eventDescription.put("id", vo.getUuid()); eventDescription.put("id", vo.getUuid());
eventDescription.put("old-state", oldState.name()); eventDescription.put("old-state", oldState.name());
@ -104,11 +104,7 @@ public class SnapshotStateListener implements StateListener<State, Event, Snapsh
eventDescription.put("eventDateTime", eventDate); eventDescription.put("eventDateTime", eventDate);
eventMsg.setDescription(eventDescription); eventMsg.setDescription(eventDescription);
try { eventDistributor.publish(eventMsg);
s_eventBus.publish(eventMsg);
} catch (EventBusException e) {
logger.warn("Failed to publish state change event on the event bus.");
}
} }
private String getEntityFromClassName(String entityClassName) { private String getEntityFromClassName(String entityClassName) {

View File

@ -22,35 +22,32 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
import com.cloud.event.EventCategory; import com.cloud.event.EventCategory;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.server.ManagementService; import com.cloud.server.ManagementService;
import com.cloud.storage.Volume; import com.cloud.storage.Volume;
import com.cloud.storage.Volume.Event; import com.cloud.storage.Volume.Event;
import com.cloud.storage.Volume.State; import com.cloud.storage.Volume.State;
import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.fsm.StateListener; import com.cloud.utils.fsm.StateListener;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
public class VolumeStateListener implements StateListener<State, Event, Volume> { public class VolumeStateListener implements StateListener<State, Event, Volume> {
protected static EventBus s_eventBus = null;
protected ConfigurationDao _configDao; protected ConfigurationDao _configDao;
protected VMInstanceDao _vmInstanceDao; protected VMInstanceDao _vmInstanceDao;
private EventDistributor eventDistributor;
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
public VolumeStateListener(ConfigurationDao configDao, VMInstanceDao vmInstanceDao) { public VolumeStateListener(ConfigurationDao configDao, VMInstanceDao vmInstanceDao) {
@ -58,6 +55,10 @@ public class VolumeStateListener implements StateListener<State, Event, Volume>
this._vmInstanceDao = vmInstanceDao; this._vmInstanceDao = vmInstanceDao;
} }
public void setEventDistributor(EventDistributor eventDistributor) {
this.eventDistributor = eventDistributor;
}
@Override @Override
public boolean preStateTransitionEvent(State oldState, Event event, State newState, Volume vo, boolean status, Object opaque) { public boolean preStateTransitionEvent(State oldState, Event event, State newState, Volume vo, boolean status, Object opaque) {
pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState); pubishOnEventBus(event.name(), "preStateTransitionEvent", vo, oldState, newState);
@ -93,23 +94,21 @@ public class VolumeStateListener implements StateListener<State, Event, Volume>
return true; return true;
} }
private void pubishOnEventBus(String event, String status, Volume vo, State oldState, State newState) { private void pubishOnEventBus(String event, String status, Volume vo, State oldState, State newState) {
String configKey = Config.PublishResourceStateEvent.key(); String configKey = Config.PublishResourceStateEvent.key();
String value = _configDao.getValue(configKey); String value = _configDao.getValue(configKey);
boolean configValue = Boolean.parseBoolean(value); boolean configValue = Boolean.parseBoolean(value);
if(!configValue) if(!configValue)
return; return;
try { if (eventDistributor == null) {
s_eventBus = ComponentContext.getComponent(EventBus.class); setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
} catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return
} }
String resourceName = getEntityFromClassName(Volume.class.getName()); String resourceName = getEntityFromClassName(Volume.class.getName());
org.apache.cloudstack.framework.events.Event eventMsg = org.apache.cloudstack.framework.events.Event eventMsg =
new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName, new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName,
vo.getUuid()); vo.getUuid());
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<String, String>();
eventDescription.put("resource", resourceName); eventDescription.put("resource", resourceName);
eventDescription.put("id", vo.getUuid()); eventDescription.put("id", vo.getUuid());
@ -120,11 +119,7 @@ public class VolumeStateListener implements StateListener<State, Event, Volume>
eventDescription.put("eventDateTime", eventDate); eventDescription.put("eventDateTime", eventDate);
eventMsg.setDescription(eventDescription); eventMsg.setDescription(eventDescription);
try { eventDistributor.publish(eventMsg);
s_eventBus.publish(eventMsg);
} catch (EventBusException e) {
logger.warn("Failed to state change event on the event bus.");
}
} }
private String getEntityFromClassName(String entityClassName) { private String getEntityFromClassName(String entityClassName) {

View File

@ -77,11 +77,13 @@ import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
import org.apache.cloudstack.resourcedetail.UserDetailVO; import org.apache.cloudstack.resourcedetail.UserDetailVO;
import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao; import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao;
import org.apache.cloudstack.utils.baremetal.BaremetalUtils; import org.apache.cloudstack.utils.baremetal.BaremetalUtils;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiDBUtils;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd; import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
@ -168,6 +170,7 @@ import com.cloud.utils.ConstantTimeComparator;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.Ternary; import com.cloud.utils.Ternary;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.Manager; import com.cloud.utils.component.Manager;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.component.PluggableService; import com.cloud.utils.component.PluggableService;
@ -426,6 +429,15 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
_querySelectors = querySelectors; _querySelectors = querySelectors;
} }
protected void deleteWebhooksForAccount(long accountId) {
try {
WebhookHelper webhookService = ComponentContext.getDelegateComponentOfType(WebhookHelper.class);
webhookService.deleteWebhooksForAccount(accountId);
} catch (NoSuchBeanDefinitionException ignored) {
logger.debug("No WebhookHelper bean found");
}
}
@Override @Override
public List<String> getApiNameList() { public List<String> getApiNameList() {
return apiNameList; return apiNameList;
@ -1105,6 +1117,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// Delete registered UserData // Delete registered UserData
userDataDao.removeByAccountId(accountId); userDataDao.removeByAccountId(accountId);
// Delete Webhooks
deleteWebhooksForAccount(accountId);
return true; return true;
} catch (Exception ex) { } catch (Exception ex) {
logger.warn("Failed to cleanup account " + account + " due to ", ex); logger.warn("Failed to cleanup account " + account + " due to ", ex);

View File

@ -24,15 +24,11 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.server.ManagementService;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.dao.UserVmDao;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
import com.cloud.event.EventCategory; import com.cloud.event.EventCategory;
@ -41,12 +37,15 @@ import com.cloud.event.UsageEventUtils;
import com.cloud.event.dao.UsageEventDao; import com.cloud.event.dao.UsageEventDao;
import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.NetworkVO;
import com.cloud.server.ManagementService;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.fsm.StateListener; import com.cloud.utils.fsm.StateListener;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.VirtualMachine.Event;
import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
public class UserVmStateListener implements StateListener<State, VirtualMachine.Event, VirtualMachine> { public class UserVmStateListener implements StateListener<State, VirtualMachine.Event, VirtualMachine> {
@ -57,10 +56,9 @@ public class UserVmStateListener implements StateListener<State, VirtualMachine.
@Inject protected UserVmDao _userVmDao; @Inject protected UserVmDao _userVmDao;
@Inject protected UserVmManager _userVmMgr; @Inject protected UserVmManager _userVmMgr;
@Inject protected ConfigurationDao _configDao; @Inject protected ConfigurationDao _configDao;
private EventDistributor eventDistributor;
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
protected static EventBus s_eventBus = null;
public UserVmStateListener(UsageEventDao usageEventDao, NetworkDao networkDao, NicDao nicDao, ServiceOfferingDao offeringDao, UserVmDao userVmDao, UserVmManager userVmMgr, public UserVmStateListener(UsageEventDao usageEventDao, NetworkDao networkDao, NicDao nicDao, ServiceOfferingDao offeringDao, UserVmDao userVmDao, UserVmManager userVmMgr,
ConfigurationDao configDao) { ConfigurationDao configDao) {
this._usageEventDao = usageEventDao; this._usageEventDao = usageEventDao;
@ -130,16 +128,16 @@ public class UserVmStateListener implements StateListener<State, VirtualMachine.
if(!configValue) if(!configValue)
return; return;
try { try {
s_eventBus = ComponentContext.getComponent(EventBus.class); eventDistributor = ComponentContext.getComponent(EventDistributor.class);
} catch (NoSuchBeanDefinitionException nbe) { } catch (NoSuchBeanDefinitionException nbe) {
return; // no provider is configured to provide events bus, so just return return; // no provider is configured to provide events distributor, so just return
} }
String resourceName = getEntityFromClassName(VirtualMachine.class.getName()); String resourceName = getEntityFromClassName(VirtualMachine.class.getName());
org.apache.cloudstack.framework.events.Event eventMsg = org.apache.cloudstack.framework.events.Event eventMsg =
new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName, new org.apache.cloudstack.framework.events.Event(ManagementService.Name, EventCategory.RESOURCE_STATE_CHANGE_EVENT.getName(), event, resourceName,
vo.getUuid()); vo.getUuid());
Map<String, String> eventDescription = new HashMap<String, String>(); Map<String, String> eventDescription = new HashMap<>();
eventDescription.put("resource", resourceName); eventDescription.put("resource", resourceName);
eventDescription.put("id", vo.getUuid()); eventDescription.put("id", vo.getUuid());
eventDescription.put("old-state", oldState.name()); eventDescription.put("old-state", oldState.name());
@ -150,12 +148,7 @@ public class UserVmStateListener implements StateListener<State, VirtualMachine.
eventDescription.put("eventDateTime", eventDate); eventDescription.put("eventDateTime", eventDate);
eventMsg.setDescription(eventDescription); eventMsg.setDescription(eventDescription);
try { eventDistributor.publish(eventMsg);
s_eventBus.publish(eventMsg);
} catch (org.apache.cloudstack.framework.events.EventBusException e) {
logger.warn("Failed to publish state change event on the event bus.");
}
} }
private String getEntityFromClassName(String entityClassName) { private String getEntityFromClassName(String entityClassName) {

View File

@ -0,0 +1,28 @@
// 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.webhook;
import java.util.List;
import org.apache.cloudstack.acl.ControlledEntity;
public interface WebhookHelper {
void deleteWebhooksForAccount(long accountId);
List<? extends ControlledEntity> listWebhooksByAccount(long accountId);
}

View File

@ -145,6 +145,10 @@
<property name="staticNatElements" value="#{staticNatServiceProvidersRegistry.registered}" /> <property name="staticNatElements" value="#{staticNatServiceProvidersRegistry.registered}" />
</bean> </bean>
<bean id="eventDistributor" class="org.apache.cloudstack.framework.events.EventDistributorImpl" >
<property name="eventBuses" value="#{eventBusRegistry.registered}" />
</bean>
<bean id="hypervisorGuruManagerImpl" class="com.cloud.hypervisor.HypervisorGuruManagerImpl" > <bean id="hypervisorGuruManagerImpl" class="com.cloud.hypervisor.HypervisorGuruManagerImpl" >
<property name="hvGuruList" value="#{hypervisorGurusRegistry.registered}" /> <property name="hvGuruList" value="#{hypervisorGurusRegistry.registered}" />
</bean> </bean>

View File

@ -29,7 +29,8 @@ import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.Event; import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.framework.events.EventDistributor;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -97,7 +98,7 @@ public class ActionEventUtilsTest {
protected ConfigurationDao configDao; protected ConfigurationDao configDao;
@Mock @Mock
protected EventBus eventBus; protected EventDistributor eventDistributor;
private AccountVO account; private AccountVO account;
private UserVO user; private UserVO user;
@ -149,7 +150,7 @@ public class ActionEventUtilsTest {
//Some basic mocks. //Some basic mocks.
Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true"); Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true");
componentContextMocked = Mockito.mockStatic(ComponentContext.class); componentContextMocked = Mockito.mockStatic(ComponentContext.class);
componentContextMocked.when(() -> ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus); componentContextMocked.when(() -> ComponentContext.getComponent(EventDistributor.class)).thenReturn(eventDistributor);
//Needed for persist to actually set an ID that can be returned from the ActionEventUtils //Needed for persist to actually set an ID that can be returned from the ActionEventUtils
//methods. //methods.
@ -166,14 +167,11 @@ public class ActionEventUtilsTest {
}); });
//Needed to record events published on the bus. //Needed to record events published on the bus.
Mockito.doAnswer(new Answer<Void>() { Mockito.doAnswer((Answer<Map<String, EventBusException>>) invocation -> {
@Override public Void answer(InvocationOnMock invocation) throws Throwable { Event event = (Event)invocation.getArguments()[0];
Event event = (Event)invocation.getArguments()[0]; publishedEvents.add(event);
publishedEvents.add(event); return new HashMap<>();
return null; }).when(eventDistributor).publish(Mockito.any(Event.class));
}
}).when(eventBus).publish(Mockito.any(Event.class));
account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid"); account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
account.setId(ACCOUNT_ID); account.setId(ACCOUNT_ID);

View File

@ -16,20 +16,27 @@
// under the License. // under the License.
package com.cloud.projects; package com.cloud.projects;
import com.cloud.projects.dao.ProjectDao; import java.util.ArrayList;
import java.util.List;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import java.util.ArrayList; import com.cloud.projects.dao.ProjectDao;
import java.util.List; import com.cloud.utils.component.ComponentContext;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -94,4 +101,31 @@ public class ProjectManagerImplTest {
public void testUpdateProjectNameAndDisplayTextUpdateNameDisplayText() { public void testUpdateProjectNameAndDisplayTextUpdateNameDisplayText() {
runUpdateProjectNameAndDisplayTextTest(true, true); runUpdateProjectNameAndDisplayTextTest(true, true);
} }
@Test
public void testDeleteWebhooksForAccount() {
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
WebhookHelper webhookHelper = Mockito.mock(WebhookHelper.class);
List<ControlledEntity> webhooks = List.of(Mockito.mock(ControlledEntity.class),
Mockito.mock(ControlledEntity.class));
Mockito.doReturn(webhooks).when(webhookHelper).listWebhooksByAccount(Mockito.anyLong());
mockedComponentContext.when(() -> ComponentContext.getDelegateComponentOfType(WebhookHelper.class))
.thenReturn(webhookHelper);
Project project = Mockito.mock(Project.class);
Mockito.when(project.getProjectAccountId()).thenReturn(1L);
List<? extends ControlledEntity> result = projectManager.listWebhooksForProject(project);
Assert.assertEquals(2, result.size());
}
}
@Test
public void testDeleteWebhooksForAccountNoBean() {
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
mockedComponentContext.when(() -> ComponentContext.getDelegateComponentOfType(WebhookHelper.class))
.thenThrow(NoSuchBeanDefinitionException.class);
List<? extends ControlledEntity> result =
projectManager.listWebhooksForProject(Mockito.mock(Project.class));
Assert.assertTrue(CollectionUtils.isEmpty(result));
}
}
} }

View File

@ -18,25 +18,26 @@
package com.cloud.template; package com.cloud.template;
import com.cloud.dc.DataCenterVO; import static org.mockito.Mockito.any;
import com.cloud.dc.dao.DataCenterDao; import static org.mockito.Mockito.anyLong;
import com.cloud.event.EventTypes; import static org.mockito.Mockito.doAnswer;
import com.cloud.event.UsageEventUtils; import static org.mockito.Mockito.eq;
import com.cloud.event.UsageEventVO; import static org.mockito.Mockito.mock;
import com.cloud.event.dao.UsageEventDao; import static org.mockito.Mockito.times;
import com.cloud.exception.InvalidParameterValueException; import static org.mockito.Mockito.when;
import com.cloud.org.Grouping;
import com.cloud.server.StatsCollector; import java.lang.reflect.Field;
import com.cloud.storage.Storage.ImageFormat; import java.lang.reflect.InvocationTargetException;
import com.cloud.storage.TemplateProfile; import java.lang.reflect.Method;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; import java.util.ArrayList;
import com.cloud.storage.VMTemplateVO; import java.util.Collections;
import com.cloud.storage.dao.VMTemplateZoneDao; import java.util.HashMap;
import com.cloud.user.AccountVO; import java.util.HashSet;
import com.cloud.user.ResourceLimitService; import java.util.List;
import com.cloud.user.dao.AccountDao; import java.util.Map;
import com.cloud.utils.component.ComponentContext; import java.util.Set;
import com.cloud.utils.exception.CloudRuntimeException; import java.util.concurrent.ExecutionException;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@ -46,8 +47,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.Templa
import org.apache.cloudstack.framework.async.AsyncCallFuture; import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.events.Event; import org.apache.cloudstack.framework.events.Event;
import org.apache.cloudstack.framework.events.EventBus; import org.apache.cloudstack.framework.events.EventDistributor;
import org.apache.cloudstack.framework.events.EventBusException;
import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.secstorage.heuristics.HeuristicType; import org.apache.cloudstack.secstorage.heuristics.HeuristicType;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
@ -70,30 +70,30 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import java.lang.reflect.Field; import com.cloud.dc.DataCenterVO;
import java.lang.reflect.InvocationTargetException; import com.cloud.dc.dao.DataCenterDao;
import java.lang.reflect.Method; import com.cloud.event.EventTypes;
import java.util.ArrayList; import com.cloud.event.UsageEventUtils;
import java.util.Collections; import com.cloud.event.UsageEventVO;
import java.util.HashMap; import com.cloud.event.dao.UsageEventDao;
import java.util.HashSet; import com.cloud.exception.InvalidParameterValueException;
import java.util.List; import com.cloud.org.Grouping;
import java.util.Map; import com.cloud.server.StatsCollector;
import java.util.Set; import com.cloud.storage.Storage.ImageFormat;
import java.util.concurrent.ExecutionException; import com.cloud.storage.TemplateProfile;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import static org.mockito.Mockito.any; import com.cloud.storage.VMTemplateVO;
import static org.mockito.Mockito.anyLong; import com.cloud.storage.dao.VMTemplateZoneDao;
import static org.mockito.Mockito.doAnswer; import com.cloud.user.AccountVO;
import static org.mockito.Mockito.eq; import com.cloud.user.ResourceLimitService;
import static org.mockito.Mockito.mock; import com.cloud.user.dao.AccountDao;
import static org.mockito.Mockito.times; import com.cloud.utils.component.ComponentContext;
import static org.mockito.Mockito.when; import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class HypervisorTemplateAdapterTest { public class HypervisorTemplateAdapterTest {
@Mock @Mock
EventBus _bus; EventDistributor eventDistributor;
List<Event> events = new ArrayList<>(); List<Event> events = new ArrayList<>();
@Mock @Mock
@ -168,7 +168,7 @@ public class HypervisorTemplateAdapterTest {
closeable.close(); closeable.close();
} }
public UsageEventUtils setupUsageUtils() throws EventBusException { public UsageEventUtils setupUsageUtils() {
Mockito.when(_configDao.getValue(eq("publish.usage.events"))).thenReturn("true"); Mockito.when(_configDao.getValue(eq("publish.usage.events"))).thenReturn("true");
Mockito.when(_usageEventDao.persist(Mockito.any(UsageEventVO.class))).then(new Answer<Void>() { Mockito.when(_usageEventDao.persist(Mockito.any(UsageEventVO.class))).then(new Answer<Void>() {
@Override public Void answer(InvocationOnMock invocation) throws Throwable { @Override public Void answer(InvocationOnMock invocation) throws Throwable {
@ -180,16 +180,14 @@ public class HypervisorTemplateAdapterTest {
Mockito.when(_usageEventDao.listAll()).thenReturn(usageEvents); Mockito.when(_usageEventDao.listAll()).thenReturn(usageEvents);
doAnswer(new Answer<Void>() { doAnswer((Answer<Void>) invocation -> {
@Override public Void answer(InvocationOnMock invocation) throws Throwable { Event event = (Event)invocation.getArguments()[0];
Event event = (Event)invocation.getArguments()[0]; events.add(event);
events.add(event); return null;
return null; }).when(eventDistributor).publish(any(Event.class));
}
}).when(_bus).publish(any(Event.class));
componentContextMocked = Mockito.mockStatic(ComponentContext.class); componentContextMocked = Mockito.mockStatic(ComponentContext.class);
when(ComponentContext.getComponent(eq(EventBus.class))).thenReturn(_bus); when(ComponentContext.getComponent(eq(EventDistributor.class))).thenReturn(eventDistributor);
UsageEventUtils utils = new UsageEventUtils(); UsageEventUtils utils = new UsageEventUtils();
@ -257,7 +255,7 @@ public class HypervisorTemplateAdapterTest {
} }
//@Test //@Test
public void testEmitDeleteEventUuid() throws InterruptedException, ExecutionException, EventBusException { public void testEmitDeleteEventUuid() throws InterruptedException, ExecutionException {
//All the mocks required for this test to work. //All the mocks required for this test to work.
ImageStoreEntity store = mock(ImageStoreEntity.class); ImageStoreEntity store = mock(ImageStoreEntity.class);
when(store.getId()).thenReturn(1l); when(store.getId()).thenReturn(1l);

View File

@ -16,6 +16,38 @@
// under the License. // under the License.
package com.cloud.user; package com.cloud.user;
import static org.mockito.ArgumentMatchers.nullable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserAuthenticator;
import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.acl.DomainChecker; import com.cloud.acl.DomainChecker;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd; import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import com.cloud.domain.Domain; import com.cloud.domain.Domain;
@ -28,40 +60,12 @@ import com.cloud.projects.Project;
import com.cloud.projects.ProjectAccountVO; import com.cloud.projects.ProjectAccountVO;
import com.cloud.user.Account.State; import com.cloud.user.Account.State;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManagerImpl; import com.cloud.vm.UserVmManagerImpl;
import com.cloud.vm.UserVmVO; import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.VMSnapshotVO;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserAuthenticator;
import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.ArgumentMatchers.nullable;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class AccountManagerImplTest extends AccountManagetImplTestBase { public class AccountManagerImplTest extends AccountManagetImplTestBase {
@ -173,6 +177,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.when(_sshKeyPairDao.listKeyPairs(Mockito.anyLong(), Mockito.anyLong())).thenReturn(sshkeyList); Mockito.when(_sshKeyPairDao.listKeyPairs(Mockito.anyLong(), Mockito.anyLong())).thenReturn(sshkeyList);
Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true); Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true);
Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222); Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222);
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l)); Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l));
// assert that this was a clean delete // assert that this was a clean delete
@ -192,6 +197,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.when(_vmMgr.expunge(Mockito.any(UserVmVO.class))).thenReturn(false); Mockito.when(_vmMgr.expunge(Mockito.any(UserVmVO.class))).thenReturn(false);
Mockito.lenient().when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domain); Mockito.lenient().when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domain);
Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class))).thenReturn(true); Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class))).thenReturn(true);
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l)); Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l));
// assert that this was NOT a clean delete // assert that this was NOT a clean delete
@ -1032,4 +1038,24 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Assert.assertEquals(userAccountVOList.size(), userAccounts.size()); Assert.assertEquals(userAccountVOList.size(), userAccounts.size());
Assert.assertEquals(userAccountVOList.get(0), userAccounts.get(0)); Assert.assertEquals(userAccountVOList.get(0), userAccounts.get(0));
} }
@Test
public void testDeleteWebhooksForAccount() {
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
WebhookHelper webhookHelper = Mockito.mock(WebhookHelper.class);
Mockito.doNothing().when(webhookHelper).deleteWebhooksForAccount(Mockito.anyLong());
mockedComponentContext.when(() -> ComponentContext.getDelegateComponentOfType(WebhookHelper.class))
.thenReturn(webhookHelper);
accountManagerImpl.deleteWebhooksForAccount(1L);
}
}
@Test
public void testDeleteWebhooksForAccountNoBean() {
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
mockedComponentContext.when(() -> ComponentContext.getDelegateComponentOfType(WebhookHelper.class))
.thenThrow(NoSuchBeanDefinitionException.class);
accountManagerImpl.deleteWebhooksForAccount(1L);
}
}
} }

View File

@ -0,0 +1,212 @@
# 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.
""" BVT tests for webhooks delivery with a basic server
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.base import (Account,
Domain,
Webhook,
SSHKeyPair)
from marvin.lib.common import (get_domain,
get_zone)
from marvin.lib.utils import (random_gen)
from marvin.cloudstackException import CloudstackAPIException
from nose.plugins.attrib import attr
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
# Import System modules
import time
import json
import socket
import _thread
_multiprocess_shared_ = True
deliveries_received = []
class WebhookReceiver(BaseHTTPRequestHandler):
"""
WebhookReceiver class to receive webhook events
"""
def _set_response(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
post_data = post_data.decode('utf-8')
event_id = self.headers.get('X-CS-Event-ID')
print("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n" %
(str(self.path), str(self.headers), post_data))
self._set_response()
global deliveries_received
if deliveries_received is None:
deliveries_received = []
deliveries_received.append({'event': event_id, 'payload': post_data})
if event_id != None:
self.wfile.write("Event with ID: {} successfully processed!".format(str(event_id)).encode('utf-8'))
else:
self.wfile.write("POST request for {}".format(self.path).encode('utf-8'))
class TestWebhookDelivery(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestWebhookDelivery, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__
# Get Zone, Domain and templates
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls.logger = logging.getLogger('TestWebhookDelivery')
cls.logger.setLevel(logging.DEBUG)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((cls.mgtSvrDetails["mgtSvrIp"], cls.mgtSvrDetails["port"]))
cls.server_ip = s.getsockname()[0]
s.close()
if cls.server_ip == "127.0.0.1":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
cls.server_ip = s.getsockname()[0]
s.close()
# use random port for webhookreceiver server
s = socket.socket()
s.bind(('', 0))
cls.server_port = s.getsockname()[1]
s.close()
cls.webhook_receiver_url = "http://" + cls.server_ip + ":" + str(cls.server_port)
cls.logger.debug("Running Webhook receiver @ %s" % cls.webhook_receiver_url)
def startMgmtServer(tname, server):
cls.logger.debug("Starting WebhookReceiver")
try:
server.serve_forever()
except Exception: pass
cls.server = HTTPServer(('0.0.0.0', cls.server_port), WebhookReceiver)
_thread.start_new_thread(startMgmtServer, ("webhook-receiver", cls.server,))
cls._cleanup = []
@classmethod
def tearDownClass(cls):
if cls.server:
cls.server.socket.close()
global deliveries_received
deliveries_received = []
super(TestWebhookDelivery, cls).tearDownClass()
def setUp(self):
self.cleanup = []
self.domain1 = Domain.create(
self.apiclient,
self.services["domain"])
self.cleanup.append(self.domain1)
def tearDown(self):
super(TestWebhookDelivery, self).tearDown()
def popItemFromCleanup(self, item_id):
for idx, x in enumerate(self.cleanup):
if x.id == item_id:
self.cleanup.pop(idx)
break
def createDomainAccount(self, isDomainAdmin=False):
self.account = Account.create(
self.apiclient,
self.services["account"],
admin=isDomainAdmin,
domainid=self.domain1.id)
self.cleanup.append(self.account)
self.userapiclient = self.testClient.getUserApiClient(
UserName=self.account.name,
DomainName=self.account.domain
)
def createWebhook(self, apiclient, scope=None, domainid=None, account=None, payloadurl=None, description=None, sslverification=None, secretkey=None, state=None):
name = "Test-" + random_gen()
if payloadurl is None:
payloadurl = self.webhook_receiver_url
self.webhook = Webhook.create(
apiclient,
name=name,
payloadurl=payloadurl,
description=description,
scope=scope,
sslverification=sslverification,
secretkey=secretkey,
state=state,
domainid=domainid,
account=account
)
self.cleanup.append(self.webhook)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_01_webhook_deliveries(self):
global deliveries_received
self.createDomainAccount()
self.createWebhook(self.userapiclient)
self.keypair = SSHKeyPair.register(
self.userapiclient,
name="Test-" + random_gen(),
publickey="ssh-rsa: e6:9a:1e:b5:98:75:88:5d:56:bc:92:7b:43:48:05:b2"
)
self.logger.debug("Registered sshkeypair: %s" % str(self.keypair.__dict__))
time.sleep(2)
list_deliveries = self.webhook.list_deliveries(
self.userapiclient,
page=1,
pagesize=20
)
self.assertNotEqual(
list_deliveries,
None,
"Check webhook deliveries list"
)
self.assertTrue(
len(list_deliveries) > 0,
"Check webhook deliveries list length"
)
for delivery in list_deliveries:
self.assertEqual(
delivery.success,
True,
"Check webhook delivery success"
)
self.assertEqual(
delivery.response,
("Event with ID: %s successfully processed!" % delivery.eventid),
"Check webhook delivery response"
)
delivery_matched = False
for received in deliveries_received:
if received['event'] == delivery.eventid:
self.assertEqual(
delivery.payload,
received['payload'],
"Check webhook delivery payload"
)
delivery_matched = True
self.assertTrue(
delivery_matched,
"Delivery for %s did not match with server" % delivery.id
)

View File

@ -0,0 +1,392 @@
# 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.
""" BVT tests for webhooks lifecycle functionalities
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.cloudstackAPI import (listEvents)
from marvin.lib.base import (Account,
Domain,
Webhook,
SSHKeyPair)
from marvin.lib.common import (get_domain,
get_zone)
from marvin.lib.utils import (random_gen)
from marvin.cloudstackException import CloudstackAPIException
from nose.plugins.attrib import attr
import logging
# Import System modules
import time
from datetime import datetime
_multiprocess_shared_ = True
HTTP_PAYLOAD_URL = "http://smee.io/C9LPa7Ei3iB6Qj2"
HTTPS_PAYLOAD_URL = "https://smee.io/C9LPa7Ei3iB6Qj2"
class TestWebhooks(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestWebhooks, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
# Get Zone, Domain and templates
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls._cleanup = []
cls.logger = logging.getLogger('TestWebhooks')
cls.logger.setLevel(logging.DEBUG)
@classmethod
def tearDownClass(cls):
super(TestWebhooks, cls).tearDownClass()
def setUp(self):
self.cleanup = []
self.domain1 = Domain.create(
self.apiclient,
self.services["domain"])
self.cleanup.append(self.domain1)
def tearDown(self):
super(TestWebhooks, self).tearDown()
def popItemFromCleanup(self, item_id):
for idx, x in enumerate(self.cleanup):
if x.id == item_id:
self.cleanup.pop(idx)
break
def createDomainAccount(self, isDomainAdmin=False):
self.account = Account.create(
self.apiclient,
self.services["account"],
admin=isDomainAdmin,
domainid=self.domain1.id)
self.cleanup.append(self.account)
self.userapiclient = self.testClient.getUserApiClient(
UserName=self.account.name,
DomainName=self.account.domain
)
def runWebhookLifecycleTest(self, apiclient, scope=None, domainid=None, account=None, normaluser=None, payloadurl=None, description=None, sslverification=None, secretkey=None, state=None, isdelete=True):
name = "Test-" + random_gen()
if payloadurl is None:
payloadurl = HTTP_PAYLOAD_URL
self.webhook = Webhook.create(
apiclient,
name=name,
payloadurl=payloadurl,
description=description,
scope=scope,
sslverification=sslverification,
secretkey=secretkey,
state=state,
domainid=domainid,
account=account
)
self.cleanup.append(self.webhook)
self.assertNotEqual(
self.webhook,
None,
"Check webhook created"
)
webhook_id = self.webhook.id
self.logger.debug("Created webhook: %s" % str(self.webhook.__dict__))
self.assertEqual(
name,
self.webhook.name,
"Check webhook name"
)
self.assertEqual(
payloadurl,
self.webhook.payloadurl,
"Check webhook payloadurl"
)
if state is None:
state = 'Enabled'
self.assertEqual(
state,
self.webhook.state,
"Check webhook state"
)
if scope is None or normaluser is not None:
scope = 'Local'
self.assertEqual(
scope,
self.webhook.scope,
"Check webhook scope"
)
if sslverification is None:
sslverification = False
self.assertEqual(
sslverification,
self.webhook.sslverification,
"Check webhook sslverification"
)
if domainid is not None:
if normaluser is not None:
domainid = normaluser.domainid
self.assertEqual(
domainid,
self.webhook.domainid,
"Check webhook domainid"
)
if account is not None:
self.assertEqual(
account,
self.webhook.account,
"Check webhook account"
)
if description is not None:
self.assertEqual(
description,
self.webhook.description,
"Check webhook description"
)
if secretkey is not None:
self.assertEqual(
secretkey,
self.webhook.secretkey,
"Check webhook secretkey"
)
list_webhook = Webhook.list(
apiclient,
id=webhook_id
)
self.assertNotEqual(
list_webhook,
None,
"Check webhook list"
)
self.assertEqual(
len(list_webhook),
1,
"Check webhook list length"
)
self.assertEqual(
list_webhook[0].id,
webhook_id,
"Check webhook list item"
)
if isdelete == False:
return
self.webhook.delete(apiclient)
self.popItemFromCleanup(webhook_id)
list_webhook = Webhook.list(
apiclient,
id=webhook_id
)
self.assertTrue(
list_webhook is None or len(list_webhook) == 0,
"Check webhook list after delete"
)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_01_create_webhook_admin_local(self):
self.runWebhookLifecycleTest(self.apiclient)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_02_create_webhook_admin_domain(self):
self.runWebhookLifecycleTest(self.apiclient, 'Domain', self.domain1.id)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_03_create_webhook_admin_global(self):
self.runWebhookLifecycleTest(self.apiclient, 'Global')
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_04_create_webhook_domainadmin_local(self):
self.createDomainAccount(True)
self.runWebhookLifecycleTest(self.userapiclient)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_05_create_webhook_domainadmin_subdomain(self):
self.createDomainAccount(True)
self.domain11 = Domain.create(
self.apiclient,
self.services["domain"],
parentdomainid=self.domain1.id)
self.cleanup.append(self.domain11)
self.runWebhookLifecycleTest(self.userapiclient, 'Domain', self.domain11.id)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_06_create_webhook_domainadmin_global_negative(self):
self.createDomainAccount(True)
try:
self.runWebhookLifecycleTest(self.userapiclient, 'Global')
except CloudstackAPIException as e:
self.assertTrue(
"errorText:Scope Global can not be specified for owner" in str(e),
"Check Global scope error check"
)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_07_create_webhook_user_local(self):
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_08_create_webhook_user_domain(self):
"""For normal user scope will always be Local irrespective of the passed value
"""
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient, 'Domain', self.domain1.id, normaluser=self.account)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_09_create_webhook_user_gloabl(self):
"""For normal user scope will always be Local irrespective of the passed value
"""
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient, 'Global', normaluser=self.account)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_10_create_webhook_admin_advanced(self):
self.createDomainAccount()
self.runWebhookLifecycleTest(
self.apiclient,
payloadurl=HTTPS_PAYLOAD_URL,
scope="Local",
description="Webhook",
sslverification=True,
secretkey="webhook",
state="Disabled",
domainid=self.domain1.id,
account=self.account.name
)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_11_update_webhook(self):
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient, isdelete=False)
description = "Desc-" + random_gen()
secretkey = random_gen()
state = 'Disabled'
updated_webhook = self.webhook.update(
self.userapiclient,
description=description,
secretkey=secretkey,
state=state
)['webhook']
self.assertNotEqual(
updated_webhook,
None,
"Check updated webhook"
)
self.assertEqual(
description,
updated_webhook.description,
"Check webhook description"
)
self.assertEqual(
secretkey,
updated_webhook.secretkey,
"Check webhook secretkey"
)
self.assertEqual(
state,
updated_webhook.state,
"Check webhook state"
)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_12_list_user_webhook_deliveries(self):
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient, isdelete=False)
now = datetime.now() # current date and time
start_time = now.strftime("%Y-%m-%d %H:%M:%S")
self.keypair = SSHKeyPair.register(
self.userapiclient,
name="Test-" + random_gen(),
publickey="ssh-rsa: e6:9a:1e:b5:98:75:88:5d:56:bc:92:7b:43:48:05:b2"
)
self.logger.debug("Registered sshkeypair: %s" % str(self.keypair.__dict__))
cmd = listEvents.listEventsCmd()
cmd.startdate = start_time
cmd.listall = True
events = self.apiclient.listEvents(cmd)
register_sshkeypair_event_count = 0
if events is not None:
for event in events:
if event.type == "REGISTER.SSH.KEYPAIR":
register_sshkeypair_event_count = register_sshkeypair_event_count + 1
time.sleep(5)
list_deliveries = self.webhook.list_deliveries(
self.userapiclient,
page=1,
pagesize=20
)
self.assertNotEqual(
list_deliveries,
None,
"Check webhook deliveries list"
)
self.assertTrue(
len(list_deliveries) > 0,
"Check webhook deliveries list length"
)
register_sshkeypair_delivery_count = 0
for delivery in list_deliveries:
if delivery.eventtype == "REGISTER.SSH.KEYPAIR":
register_sshkeypair_delivery_count = register_sshkeypair_delivery_count + 1
self.assertEqual(
register_sshkeypair_event_count,
register_sshkeypair_delivery_count,
"Check sshkeypair webhook deliveries count"
)
self.webhook.delete_deliveries(
self.userapiclient
)
list_deliveries = self.webhook.list_deliveries(
self.userapiclient
)
self.assertTrue(
list_deliveries is None or len(list_deliveries) == 0,
"Check webhook deliveries list after delete"
)
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_13_webhook_execute_delivery(self):
self.createDomainAccount()
self.runWebhookLifecycleTest(self.userapiclient, isdelete=False)
payload = "{ \"CloudStack\": \"Integration Test\" }"
delivery = self.webhook.execute_delivery(
self.userapiclient,
payload=payload
)
self.assertNotEqual(
delivery,
None,
"Check test webhook delivery"
)
self.assertEqual(
self.webhook.id,
delivery.webhookid,
"Check test webhook delivery webhook"
)
self.assertEqual(
payload,
delivery.payload,
"Check test webhook delivery payload"
)
self.assertEqual(
self.webhook.id,
delivery.webhookid,
"Check test webhook delivery webhook"
)

View File

@ -274,7 +274,9 @@ known_categories = {
'deleteBucket': 'Object Store', 'deleteBucket': 'Object Store',
'listBuckets': 'Object Store', 'listBuckets': 'Object Store',
'listVmsForImport': 'Virtual Machine', 'listVmsForImport': 'Virtual Machine',
'importVm': 'Virtual Machine' 'importVm': 'Virtual Machine',
'Webhook': 'Webhook',
'Webhooks': 'Webhook'
} }

View File

@ -7227,3 +7227,65 @@ class Bucket:
cmd.id = self.id cmd.id = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())] [setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.updateBucket(cmd) return apiclient.updateBucket(cmd)
class Webhook:
"""Manage Webhook Life cycle"""
def __init__(self, items):
self.__dict__.update(items)
@classmethod
def create(cls, apiclient, name, payloadurl, **kwargs):
"""Create Webhook"""
cmd = createWebhook.createWebhookCmd()
cmd.name = name
cmd.payloadurl = payloadurl
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return Webhook(apiclient.createWebhook(cmd).__dict__)
@classmethod
def list(cls, apiclient, **kwargs):
cmd = listWebhooks.listWebhooksCmd()
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()):
cmd.listall = True
return apiclient.listWebhooks(cmd)
def delete(self, apiclient):
"""Delete Webhook"""
cmd = deleteWebhook.deleteWebhookCmd()
cmd.id = self.id
apiclient.deleteWebhook(cmd)
def update(self, apiclient, **kwargs):
"""Update Webhook"""
cmd = updateWebhook.updateWebhookCmd()
cmd.id = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.updateWebhook(cmd)
def list_deliveries(self, apiclient, **kwargs):
"""List Webhook Deliveries"""
cmd = listWebhookDeliveries.listWebhookDeliveriesCmd()
cmd.webhookid = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.listWebhookDeliveries(cmd)
def execute_delivery(self, apiclient, **kwargs):
"""Execute Webhook Delivery"""
cmd = executeWebhookDelivery.executeWebhookDeliveryCmd()
cmd.webhookid = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.executeWebhookDelivery(cmd)
def delete_deliveries(self, apiclient, **kwargs):
"""Delete Webhook Deliveries"""
cmd = deleteWebhookDelivery.deleteWebhookDeliveryCmd()
cmd.webhookid = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.deleteWebhookDelivery(cmd)

View File

@ -60,6 +60,8 @@
"label.action.bulk.release.public.ip.address": "Bulk release public IP addresses", "label.action.bulk.release.public.ip.address": "Bulk release public IP addresses",
"label.action.cancel.maintenance.mode": "Cancel maintenance mode", "label.action.cancel.maintenance.mode": "Cancel maintenance mode",
"label.action.change.password": "Change password", "label.action.change.password": "Change password",
"label.action.clear.webhook.deliveries": "Clear deliveries",
"label.action.delete.webhook.deliveries": "Delete deliveries",
"label.action.configure.stickiness": "Stickiness", "label.action.configure.stickiness": "Stickiness",
"label.action.copy.iso": "Copy ISO", "label.action.copy.iso": "Copy ISO",
"label.action.copy.snapshot": "Copy Snapshot", "label.action.copy.snapshot": "Copy Snapshot",
@ -583,6 +585,7 @@
"label.create.tungsten.routing.policy": "Create Tungsten-Fabric routing policy", "label.create.tungsten.routing.policy": "Create Tungsten-Fabric routing policy",
"label.create.user": "Create User", "label.create.user": "Create User",
"label.create.vpn.connection": "Create VPN connection", "label.create.vpn.connection": "Create VPN connection",
"label.create.webhook": "Create Webhook",
"label.created": "Created", "label.created": "Created",
"label.creating": "Creating", "label.creating": "Creating",
"label.creating.iprange": "Creating IP ranges", "label.creating.iprange": "Creating IP ranges",
@ -603,6 +606,9 @@
"label.data.disk": "Data disk", "label.data.disk": "Data disk",
"label.data.disk.offering": "Data disk offering", "label.data.disk.offering": "Data disk offering",
"label.date": "Date", "label.date": "Date",
"label.datetime.filter.period": "From <b>{startDate}</b> to <b>{endDate}</b>",
"label.datetime.filter.starting": "Starting <b>{startDate}</b>.",
"label.datetime.filter.up.to": "Up to <b>{endDate}</b>.",
"label.day": "Day", "label.day": "Day",
"label.day.of.month": "Day of month", "label.day.of.month": "Day of month",
"label.day.of.week": "Day of week", "label.day.of.week": "Day of week",
@ -672,6 +678,8 @@
"label.delete.vpn.customer.gateway": "Delete VPN customer gateway", "label.delete.vpn.customer.gateway": "Delete VPN customer gateway",
"label.delete.vpn.gateway": "Delete VPN gateway", "label.delete.vpn.gateway": "Delete VPN gateway",
"label.delete.vpn.user": "Delete VPN User", "label.delete.vpn.user": "Delete VPN User",
"label.delete.webhook": "Delete Webhook",
"label.delete.webhook.delivery": "Delete Webhook Delivery",
"label.deleteconfirm": "Please confirm that you would like to delete this", "label.deleteconfirm": "Please confirm that you would like to delete this",
"label.deleting": "Deleting", "label.deleting": "Deleting",
"label.deleting.failed": "Deleting failed", "label.deleting.failed": "Deleting failed",
@ -730,6 +738,7 @@
"label.disable.storage": "Disable storage pool", "label.disable.storage": "Disable storage pool",
"label.disable.vpc.offering": "Disable VPC offering", "label.disable.vpc.offering": "Disable VPC offering",
"label.disable.vpn": "Disable remote access VPN", "label.disable.vpn": "Disable remote access VPN",
"label.disable.webhook": "Disable Webhook",
"label.disabled": "Disabled", "label.disabled": "Disabled",
"label.disconnected": "Last disconnected", "label.disconnected": "Last disconnected",
"label.disk": "Disk", "label.disk": "Disk",
@ -837,6 +846,7 @@
"label.enable.storage": "Enable storage pool", "label.enable.storage": "Enable storage pool",
"label.enable.vpc.offering": "Enable VPC offering", "label.enable.vpc.offering": "Enable VPC offering",
"label.enable.vpn": "Enable remote access VPN", "label.enable.vpn": "Enable remote access VPN",
"label.enable.webhook": "Enable Webhook",
"label.enabled": "Enabled", "label.enabled": "Enabled",
"label.encrypt": "Encrypt", "label.encrypt": "Encrypt",
"label.encryptroot": "Encrypt Root Disk", "label.encryptroot": "Encrypt Root Disk",
@ -872,6 +882,8 @@
"label.esppolicy": "ESP policy", "label.esppolicy": "ESP policy",
"label.esx.host": "ESX/ESXi host", "label.esx.host": "ESX/ESXi host",
"label.event": "Event", "label.event": "Event",
"label.eventid": "Event",
"label.eventtype": "Event type",
"label.event.archived": "Event(s) archived", "label.event.archived": "Event(s) archived",
"label.event.deleted": "Event(s) deleted", "label.event.deleted": "Event(s) deleted",
"label.event.timeline": "Event timeline", "label.event.timeline": "Event timeline",
@ -987,6 +999,7 @@
"label.hardware": "Hardware", "label.hardware": "Hardware",
"label.hasrules":"FW rules defined", "label.hasrules":"FW rules defined",
"label.hastate": "HA state", "label.hastate": "HA state",
"label.headers": "Headers",
"label.header.backup.schedule": "You can set up recurring backup schedules by selecting from the available options below and applying your policy preference.", "label.header.backup.schedule": "You can set up recurring backup schedules by selecting from the available options below and applying your policy preference.",
"label.header.volume.snapshot": "You can set up recurring Snapshot schedules by selecting from the available options below and applying your policy preference.", "label.header.volume.snapshot": "You can set up recurring Snapshot schedules by selecting from the available options below and applying your policy preference.",
"label.header.volume.take.snapshot": "Please confirm that you want to take a Snapshot of this volume.", "label.header.volume.take.snapshot": "Please confirm that you want to take a Snapshot of this volume.",
@ -1281,6 +1294,8 @@
"label.managed.volumes": "Managed Volumes", "label.managed.volumes": "Managed Volumes",
"label.managedstate": "Managed state", "label.managedstate": "Managed state",
"label.management": "Management", "label.management": "Management",
"label.managementserverid": "Management server",
"label.managementservername": "Management server",
"label.management.ips": "Management IP addresses", "label.management.ips": "Management IP addresses",
"label.management.server": "Management server", "label.management.server": "Management server",
"label.management.servers": "Management servers", "label.management.servers": "Management servers",
@ -1562,6 +1577,7 @@
"label.patp": "Palo Alto threat profile", "label.patp": "Palo Alto threat profile",
"label.pavr": "Virtual router", "label.pavr": "Virtual router",
"label.payload": "Payload", "label.payload": "Payload",
"label.payloadurl": "Payload URL",
"label.pcidevice": "GPU", "label.pcidevice": "GPU",
"label.pending.jobs": "Pending Jobs", "label.pending.jobs": "Pending Jobs",
"label.per.account": "Per Account", "label.per.account": "Per Account",
@ -1709,9 +1725,11 @@
"label.readonly": "Read-Only", "label.readonly": "Read-Only",
"label.reason": "Reason", "label.reason": "Reason",
"label.reboot": "Reboot", "label.reboot": "Reboot",
"label.recent.deliveries": "Recent deliveries",
"label.receivedbytes": "Bytes received", "label.receivedbytes": "Bytes received",
"label.recover.vm": "Recover Instance", "label.recover.vm": "Recover Instance",
"label.recovering": "Recovering", "label.recovering": "Recovering",
"label.redeliver": "Redeliver",
"label.redirect": "Redirect to:", "label.redirect": "Redirect to:",
"label.redirecturi": "Redirect URI", "label.redirecturi": "Redirect URI",
"label.redundantrouter": "Redundant router", "label.redundantrouter": "Redundant router",
@ -1790,6 +1808,7 @@
"label.resources": "Resources", "label.resources": "Resources",
"label.resourcestate": "Resource state", "label.resourcestate": "Resource state",
"label.resourcetype": "Resource type", "label.resourcetype": "Resource type",
"label.response": "Response",
"label.restart.network": "Restart Network", "label.restart.network": "Restart Network",
"label.restart.vpc": "Restart VPC", "label.restart.vpc": "Restart VPC",
"label.restartrequired": "Restart required", "label.restartrequired": "Restart required",
@ -1998,6 +2017,7 @@
"label.sshkeypair": "New SSH key pair", "label.sshkeypair": "New SSH key pair",
"label.sshkeypairs": "SSH key pairs", "label.sshkeypairs": "SSH key pairs",
"label.sslcertificates": "SSL certificates", "label.sslcertificates": "SSL certificates",
"label.sslverification": "SSL verification",
"label.standard.us.keyboard": "Standard (US) keyboard", "label.standard.us.keyboard": "Standard (US) keyboard",
"label.start": "Start", "label.start": "Start",
"label.start.date.and.time": "Start date and time", "label.start.date.and.time": "Start date and time",
@ -2118,6 +2138,8 @@
"label.templatetype": "Template type", "label.templatetype": "Template type",
"label.templateversion": "Template version", "label.templateversion": "Template version",
"label.term.type": "Term type", "label.term.type": "Term type",
"label.test": "Test",
"label.test.webhook.delivery": "Test Webhook Delivery",
"label.tftpdir": "TFTP root directory", "label.tftpdir": "TFTP root directory",
"label.theme.alert": "The setting is only visible to the current browser. To apply the setting, please download the JSON file and replace its content in the `theme` section of the `config.json` file under the path: `/public/config.json`", "label.theme.alert": "The setting is only visible to the current browser. To apply the setting, please download the JSON file and replace its content in the `theme` section of the `config.json` file under the path: `/public/config.json`",
"label.theme.color": "Theme color", "label.theme.color": "Theme color",
@ -2221,6 +2243,7 @@
"label.update.to": "updated to", "label.update.to": "updated to",
"label.update.traffic.label": "Update traffic labels", "label.update.traffic.label": "Update traffic labels",
"label.update.vmware.datacenter": "Update VMWare datacenter", "label.update.vmware.datacenter": "Update VMWare datacenter",
"label.update.webhook": "Update Webhook",
"label.updating": "Updating", "label.updating": "Updating",
"label.upgrade.router.newer.template": "Upgrade router to use newer Template", "label.upgrade.router.newer.template": "Upgrade router to use newer Template",
"label.upgrading": "Upgrading", "label.upgrading": "Upgrading",
@ -2399,6 +2422,10 @@
"label.warn": "Warn", "label.warn": "Warn",
"label.warn.upper": "WARN", "label.warn.upper": "WARN",
"label.warning": "Warning", "label.warning": "Warning",
"label.webhook": "Webhook",
"label.webhooks": "Webhooks",
"label.webhookname": "Webhook",
"label.webhook.deliveries": "Webhook deliveries",
"label.wednesday": "Wednesday", "label.wednesday": "Wednesday",
"label.weekly": "Weekly", "label.weekly": "Weekly",
"label.welcome": "Welcome", "label.welcome": "Welcome",
@ -2547,6 +2574,7 @@
"message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule", "message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule",
"message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...", "message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...",
"message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule", "message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule",
"message.redeliver.webhook.delivery": "Redeliver this Webhook delivery",
"message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule", "message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule",
"message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...", "message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...",
"message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule", "message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule",
@ -2658,12 +2686,14 @@
"message.confirm.disable.provider": "Please confirm that you would like to disable this provider.", "message.confirm.disable.provider": "Please confirm that you would like to disable this provider.",
"message.confirm.disable.storage": "Please confirm that you want to disable the storage pool.", "message.confirm.disable.storage": "Please confirm that you want to disable the storage pool.",
"message.confirm.disable.vpc.offering": "Are you sure you want to disable this VPC offering?", "message.confirm.disable.vpc.offering": "Are you sure you want to disable this VPC offering?",
"message.confirm.disable.webhook": "Please confirm that you want to disable this webhook.",
"message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscale Instance group.", "message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscale Instance group.",
"message.confirm.enable.host": "Please confirm that you want to enable the host.", "message.confirm.enable.host": "Please confirm that you want to enable the host.",
"message.confirm.enable.network.offering": "Are you sure you want to enable this Network offering?", "message.confirm.enable.network.offering": "Are you sure you want to enable this Network offering?",
"message.confirm.enable.provider": "Please confirm that you would like to enable this provider.", "message.confirm.enable.provider": "Please confirm that you would like to enable this provider.",
"message.confirm.enable.storage": "Please confirm that you want to enable the storage pool.", "message.confirm.enable.storage": "Please confirm that you want to enable the storage pool.",
"message.confirm.enable.vpc.offering": "Are you sure you want to enable this VPC offering?", "message.confirm.enable.vpc.offering": "Are you sure you want to enable this VPC offering?",
"message.confirm.enable.webhook": "Please confirm that you want to enable this webhook.",
"message.confirm.remove.firewall.rule": "Please confirm that you want to delete this Firewall Rule?", "message.confirm.remove.firewall.rule": "Please confirm that you want to delete this Firewall Rule?",
"message.confirm.remove.ip.range": "Please confirm that you would like to remove this IP range.", "message.confirm.remove.ip.range": "Please confirm that you would like to remove this IP range.",
"message.confirm.remove.network.offering": "Are you sure you want to remove this Network offering?", "message.confirm.remove.network.offering": "Are you sure you want to remove this Network offering?",
@ -2745,6 +2775,8 @@
"message.delete.vpn.connection": "Please confirm that you want to delete VPN connection.", "message.delete.vpn.connection": "Please confirm that you want to delete VPN connection.",
"message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN customer gateway.", "message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN customer gateway.",
"message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway.", "message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway.",
"message.delete.webhook": "Please confirm that you want to delete this Webhook.",
"message.delete.webhook.delivery": "Please confirm that you want to delete this Webhook delivery.",
"message.deleting.firewall.policy": "Deleting Firewall Policy", "message.deleting.firewall.policy": "Deleting Firewall Policy",
"message.deleting.node": "Deleting Node", "message.deleting.node": "Deleting Node",
"message.deleting.vm": "Deleting Instance", "message.deleting.vm": "Deleting Instance",
@ -2782,6 +2814,7 @@
"message.disable.vpn": "Are you sure you want to disable VPN?", "message.disable.vpn": "Are you sure you want to disable VPN?",
"message.disable.vpn.failed": "Failed to disable VPN.", "message.disable.vpn.failed": "Failed to disable VPN.",
"message.disable.vpn.processing": "Disabling VPN...", "message.disable.vpn.processing": "Disabling VPN...",
"message.disable.webhook.ssl.verification": "Disabling SSL verification is not recommended",
"message.discovering.feature": "Discovering features, please wait...", "message.discovering.feature": "Discovering features, please wait...",
"message.disk.offering.created": "Disk offering created:", "message.disk.offering.created": "Disk offering created:",
"message.disk.usage.info.data.points": "Each data point represents the difference in read/write data since the last data point.", "message.disk.usage.info.data.points": "Each data point represents the difference in read/write data since the last data point.",
@ -2956,6 +2989,9 @@
"message.error.zone.type": "Please select zone type.", "message.error.zone.type": "Please select zone type.",
"message.error.linstor.resourcegroup": "Please enter the Linstor Resource-Group.", "message.error.linstor.resourcegroup": "Please enter the Linstor Resource-Group.",
"message.error.fixed.offering.kvm": "It's not possible to scale up Instances that utilize KVM hypervisor with a fixed compute offering.", "message.error.fixed.offering.kvm": "It's not possible to scale up Instances that utilize KVM hypervisor with a fixed compute offering.",
"message.error.create.webhook.local.account": "Account must be provided for creating a Webhook with Local scope.",
"message.error.create.webhook.name": "Name must be provided for creating a Webhook.",
"message.error.create.webhook.payloadurl": "Payload URL must be provided for creating a Webhook.",
"message.fail.to.delete": "Failed to delete.", "message.fail.to.delete": "Failed to delete.",
"message.failed.to.add": "Failed to add", "message.failed.to.add": "Failed to add",
"message.failed.to.assign.vms": "Failed to assign Instances", "message.failed.to.assign.vms": "Failed to assign Instances",
@ -3215,6 +3251,7 @@
"message.success.change.affinity.group": "Successfully changed affinity groups", "message.success.change.affinity.group": "Successfully changed affinity groups",
"message.success.change.offering": "Successfully changed offering", "message.success.change.offering": "Successfully changed offering",
"message.success.change.password": "Successfully changed password for User", "message.success.change.password": "Successfully changed password for User",
"message.success.clear.webhook.deliveries": "Successfully cleared webhook deliveries",
"message.success.config.backup.schedule": "Successfully configured Instance backup schedule", "message.success.config.backup.schedule": "Successfully configured Instance backup schedule",
"message.success.config.health.monitor": "Successfully Configure Health Monitor", "message.success.config.health.monitor": "Successfully Configure Health Monitor",
"message.success.config.sticky.policy": "Successfully configured sticky policy", "message.success.config.sticky.policy": "Successfully configured sticky policy",
@ -3231,6 +3268,7 @@
"message.success.create.template": "Successfully created Template", "message.success.create.template": "Successfully created Template",
"message.success.create.user": "Successfully created User", "message.success.create.user": "Successfully created User",
"message.success.create.volume": "Successfully created volume", "message.success.create.volume": "Successfully created volume",
"message.success.create.webhook": "Successfully created Webhook",
"message.success.delete": "Successfully deleted", "message.success.delete": "Successfully deleted",
"message.success.delete.acl.rule": "Successfully removed ACL rule", "message.success.delete.acl.rule": "Successfully removed ACL rule",
"message.success.delete.backup.schedule": "Successfully deleted configure Instance backup schedule", "message.success.delete.backup.schedule": "Successfully deleted configure Instance backup schedule",
@ -3316,6 +3354,7 @@
"message.update.autoscale.vm.profile.failed": "Failed to update autoscale Instance profile", "message.update.autoscale.vm.profile.failed": "Failed to update autoscale Instance profile",
"message.update.condition.failed": "Failed to update condition", "message.update.condition.failed": "Failed to update condition",
"message.update.condition.processing": "Updating condition...", "message.update.condition.processing": "Updating condition...",
"message.test.webhook.delivery": "Test delivery to the Webhook with an optional payload",
"message.two.factor.authorization.failed": "Unable to verify 2FA with provided code, please retry.", "message.two.factor.authorization.failed": "Unable to verify 2FA with provided code, please retry.",
"message.two.fa.auth": "Open the two-factor authentication app on your mobile device to view your authentication code.", "message.two.fa.auth": "Open the two-factor authentication app on your mobile device to view your authentication code.",
"message.two.fa.auth.register.account": "Open the two-factor authentication application and scan the QR code add the User Account.", "message.two.fa.auth.register.account": "Open the two-factor authentication application and scan the QR code add the User Account.",
@ -3404,6 +3443,7 @@
"message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.", "message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.",
"message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported.", "message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported.",
"message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Networks MTU settings", "message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Networks MTU settings",
"message.webhook.deliveries.time.filter": "Webhook deliveries list can be filtered based on date-time. Select 'Custom' for specifying start and end date range.",
"message.zone.creation.complete": "Zone creation complete.", "message.zone.creation.complete": "Zone creation complete.",
"message.zone.detail.description": "Populate zone details.", "message.zone.detail.description": "Populate zone details.",
"message.zone.detail.hint": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", "message.zone.detail.hint": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",

View File

@ -22,12 +22,12 @@
:model="form" :model="form"
@finish="handleSubmit" @finish="handleSubmit"
layout="vertical"> layout="vertical">
<div v-show="!(!showStartDate || !showEndDate) || (!showStartDate && !showEndDate)"> <div v-show="showAllDataOption && (!(!showStartDate || !showEndDate) || (!showStartDate && !showEndDate))">
<a-form-item :label="$t('label.all.available.data')" ref="allData" name="allData"> <a-form-item :label="$t('label.all.available.data')" ref="allData" name="allData">
<a-switch v-model:checked="allDataIsChecked" @change="onToggleAllData"/> <a-switch v-model:checked="allDataIsChecked" @change="onToggleAllData"/>
</a-form-item> </a-form-item>
<div v-show="showAllDataAlert"> <div v-show="showAllDataAlert">
<a-alert :message="$t('message.alert.show.all.stats.data')" banner /> <a-alert :message="allDataMessage" banner />
</div> </div>
</div> </div>
<div v-show="showStartDate"> <div v-show="showStartDate">
@ -64,7 +64,7 @@ import { ref, reactive, toRaw } from 'vue'
import moment from 'moment' import moment from 'moment'
export default { export default {
name: 'FilterStats', name: 'DateTimeFilter',
emits: ['closeAction', 'onSubmit'], emits: ['closeAction', 'onSubmit'],
props: { props: {
startDateProp: { startDateProp: {
@ -74,6 +74,14 @@ export default {
endDateProp: { endDateProp: {
type: [Date, String, Number], type: [Date, String, Number],
required: false required: false
},
showAllDataOption: {
type: Boolean,
default: true
},
allDataMessage: {
type: String,
value: ''
} }
}, },
computed: { computed: {

View File

@ -103,6 +103,12 @@
<div v-else-if="$route.meta.name === 'computeoffering' && offeringDetails.includes(item)"> <div v-else-if="$route.meta.name === 'computeoffering' && offeringDetails.includes(item)">
{{ dataResource.serviceofferingdetails[item] }} {{ dataResource.serviceofferingdetails[item] }}
</div> </div>
<div v-else-if="item === 'headers'" style="white-space: pre-line;">
{{ dataResource[item] }}
</div>
<div v-else-if="item === 'payload'" style="white-space: pre-wrap;">
{{ JSON.stringify(JSON.parse(dataResource[item]), null, 4) || dataResource[item] }}
</div>
<div v-else>{{ dataResource[item] }}</div> <div v-else>{{ dataResource[item] }}</div>
</div> </div>
</a-list-item> </a-list-item>
@ -120,6 +126,13 @@
<div>{{ dataResource[item] }}</div> <div>{{ dataResource[item] }}</div>
</div> </div>
</a-list-item> </a-list-item>
<a-list-item v-else-if="['startdate', 'enddate'].includes(item)">
<div>
<strong>{{ $t('label.' + item.replace('date', '.date.and.time'))}}</strong>
<br/>
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
</div>
</a-list-item>
</template> </template>
<HostInfo :resource="dataResource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" /> <HostInfo :resource="dataResource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
<DedicateData :resource="dataResource" v-if="dedicatedSectionActive" /> <DedicateData :resource="dataResource" v-if="dedicatedSectionActive" />
@ -174,7 +187,12 @@ export default {
}, },
computed: { computed: {
customDisplayItems () { customDisplayItems () {
return ['ip6routes', 'privatemtu', 'publicmtu', 'provider'] var items = ['ip6routes', 'privatemtu', 'publicmtu', 'provider']
if (this.$route.meta.name === 'webhookdeliveries') {
items.push('startdate')
items.push('enddate')
}
return items
}, },
vnfAccessMethods () { vnfAccessMethods () {
if (this.resource.templatetype === 'VNF' && ['vm', 'vnfapp'].includes(this.$route.meta.name)) { if (this.resource.templatetype === 'VNF' && ['vm', 'vnfapp'].includes(this.$route.meta.name)) {

View File

@ -140,6 +140,12 @@
<status class="status" :text="resource.resourcestate" displayText/> <status class="status" :text="resource.resourcestate" displayText/>
</div> </div>
</div> </div>
<div class="resource-detail-item" v-if="('success' in resource) && $route.meta.name === 'webhookdeliveries'">
<div class="resource-detail-item__label">{{ $t('label.success') }}</div>
<div class="resource-detail-item__details">
<status class="status" :text="resource.success ? 'success' : 'error'"/>
</div>
</div>
<div class="resource-detail-item" v-if="resource.id"> <div class="resource-detail-item" v-if="resource.id">
<div class="resource-detail-item__label">{{ $t('label.id') }}</div> <div class="resource-detail-item__label">{{ $t('label.id') }}</div>
@ -672,6 +678,22 @@
<span v-else>{{ resource.domain || resource.domainid }}</span> <span v-else>{{ resource.domain || resource.domainid }}</span>
</div> </div>
</div> </div>
<div class="resource-detail-item" v-if="resource.payloadurl">
<div class="resource-detail-item__label">{{ $t('label.payloadurl') }}</div>
<div class="resource-detail-item__details">
<link-outlined/>
<a v-if="!isStatic" :href="resource.payloadurl">{{ resource.payloadurl }}</a>
<span v-else>{{ resource.payloadurl }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.webhookid">
<div class="resource-detail-item__label">{{ $t('label.webhook') }}</div>
<div class="resource-detail-item__details">
<node-index-outlined />
<router-link v-if="!isStatic && $router.resolve('/webhook/' + resource.webhookid).matched[0].redirect !== '/exception/404'" :to="{ path: '/webhook/' + resource.webhookid }">{{ resource.webhookname || resource.webhookid }}</router-link>
<span v-else>{{ resource.webhookname || resource.webhookid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.managementserverid"> <div class="resource-detail-item" v-if="resource.managementserverid">
<div class="resource-detail-item__label">{{ $t('label.management.servers') }}</div> <div class="resource-detail-item__label">{{ $t('label.management.servers') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">

View File

@ -23,7 +23,7 @@
:dataSource="items" :dataSource="items"
:rowKey="(record, idx) => record.id || record.name || record.usageType || idx + '-' + Math.random()" :rowKey="(record, idx) => record.id || record.name || record.usageType || idx + '-' + Math.random()"
:pagination="false" :pagination="false"
:rowSelection=" enableGroupAction() || $route.name === 'event' ? {selectedRowKeys: selectedRowKeys, onChange: onSelectChange, columnWidth: 30} : null" :rowSelection="explicitlyAllowRowSelection || enableGroupAction() || $route.name === 'event' ? {selectedRowKeys: selectedRowKeys, onChange: onSelectChange, columnWidth: 30} : null"
:rowClassName="getRowClassName" :rowClassName="getRowClassName"
style="overflow-y: auto" style="overflow-y: auto"
> >
@ -364,12 +364,47 @@
<status :text="record.enabled ? record.enabled.toString() : 'false'" /> <status :text="record.enabled ? record.enabled.toString() : 'false'" />
{{ record.enabled ? 'Enabled' : 'Disabled' }} {{ record.enabled ? 'Enabled' : 'Disabled' }}
</template> </template>
<template v-if="['created', 'sent'].includes(column.key)"> <template v-if="['created', 'sent'].includes(column.key) || (['startdate'].includes(column.key) && ['webhook'].includes($route.path.split('/')[1]))">
{{ $toLocaleDate(text) }} {{ $toLocaleDate(text) }}
</template> </template>
<template v-if="['startdate', 'enddate'].includes(column.key) && ['vm', 'vnfapp'].includes($route.path.split('/')[1])"> <template v-if="['startdate', 'enddate'].includes(column.key) && ['vm', 'vnfapp'].includes($route.path.split('/')[1])">
{{ getDateAtTimeZone(text, record.timezone) }} {{ getDateAtTimeZone(text, record.timezone) }}
</template> </template>
<template v-if="column.key === 'payloadurl'">
<copy-label :label="text" />
</template>
<template v-if="column.key === 'eventtype'">
<router-link v-if="$router.resolve('/event/' + record.eventid).matched[0].redirect !== '/exception/404'" :to="{ path: '/event/' + record.eventid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</template>
<template v-if="column.key === 'managementservername'">
<router-link v-if="$router.resolve('/managementserver/' + record.managementserverid).matched[0].redirect !== '/exception/404'" :to="{ path: '/managementserver/' + record.managementserverid }">{{ text }}</router-link>
<router-link v-else-if="$router.resolve('/managementservers/' + record.managementserverid).matched[0].redirect !== '/exception/404'" :to="{ path: '/managementservers/' + record.managementserverid }">{{ text }}</router-link>
<span v-else>{{ text }}</span>
</template>
<template v-if="column.key === 'payload'">
<router-link v-if="$router.resolve('/webhookdeliveries/' + record.id).matched[0].redirect !== '/exception/404'" :to="{ path: '/webhookdeliveries/' + record.id }">{{ getTrimmedText(text, 48) }}</router-link>
<span v-else> {{ getTrimmedText(text, 48) }} </span>
<QuickView
style="margin-left: 5px"
:actions="actions"
:resource="record"
:enabled="quickViewEnabled() && actions.length > 0"
@exec-action="$parent.execAction"/>
</template>
<template v-if="column.key === 'webhookname'">
<router-link v-if="$router.resolve('/webhook/' + record.webhookid).matched[0].redirect !== '/exception/404'" :to="{ path: '/webhook/' + record.webhookid }">{{ text }}</router-link>
<span v-else> {{ text }} </span>
</template>
<template v-if="column.key === 'success'">
<status :text="text ? 'success' : 'error'" />
</template>
<template v-if="column.key === 'response'">
<span> {{ getTrimmedText(text, 48) }} </span>
</template>
<template v-if="column.key === 'duration' && ['webhook', 'webhookdeliveries'].includes($route.path.split('/')[1])">
<span> {{ getDuration(record.startdate, record.enddate) }} </span>
</template>
<template v-if="column.key === 'order'"> <template v-if="column.key === 'order'">
<div class="shift-btns"> <div class="shift-btns">
<a-tooltip :name="text" placement="top"> <a-tooltip :name="text" placement="top">
@ -526,6 +561,10 @@ export default {
actions: { actions: {
type: Array, type: Array,
default: () => [] default: () => []
},
explicitlyAllowRowSelection: {
type: Boolean,
default: false
} }
}, },
inject: ['parentFetchData', 'parentToggleLoading'], inject: ['parentFetchData', 'parentToggleLoading'],
@ -598,14 +637,15 @@ export default {
'/project', '/account', 'buckets', 'objectstore', '/project', '/account', 'buckets', 'objectstore',
'/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation', '/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation',
'/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering', '/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering',
'/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping'].join('|')) '/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping', '/webhook', 'webhookdeliveries'].join('|'))
.test(this.$route.path) .test(this.$route.path)
}, },
enableGroupAction () { enableGroupAction () {
return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 'autoscalevmgroup', 'volume', 'snapshot', return ['vm', 'alert', 'vmgroup', 'ssh', 'userdata', 'affinitygroup', 'autoscalevmgroup', 'volume', 'snapshot',
'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp', 'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering', 'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets' 'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets',
'webhook', 'webhookdeliveries'
].includes(this.$route.name) ].includes(this.$route.name)
}, },
getDateAtTimeZone (date, timezone) { getDateAtTimeZone (date, timezone) {
@ -898,6 +938,19 @@ export default {
case 'SecondaryStorageVm': return '/systemvm/' case 'SecondaryStorageVm': return '/systemvm/'
default: return '/vm/' default: return '/vm/'
} }
},
getTrimmedText (text, length) {
if (!text) {
return ''
}
return (text.length <= length) ? text : (text.substring(0, length - 3) + '...')
},
getDuration (startdate, enddate) {
if (!startdate || !enddate) {
return ''
}
var duration = Date.parse(enddate) - Date.parse(startdate)
return (duration > 0 ? duration / 1000.0 : 0) + ''
} }
} }
} }

View File

@ -85,6 +85,9 @@
</span> </span>
<block-outlined v-else style="margin-right: 5px" /> <block-outlined v-else style="margin-right: 5px" />
</span> </span>
<span v-if="(field.name.startsWith('managementserver'))">
<status :text="opt.state" />
</span>
{{ $t(opt.path || opt.name) }} {{ $t(opt.path || opt.name) }}
</div> </div>
</a-select-option> </a-select-option>
@ -154,14 +157,17 @@
<script> <script>
import { ref, reactive, toRaw } from 'vue' import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api' import { api } from '@/api'
import { isAdmin } from '@/role'
import TooltipButton from '@/components/widgets/TooltipButton' import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon' import ResourceIcon from '@/components/view/ResourceIcon'
import Status from '@/components/widgets/Status'
export default { export default {
name: 'SearchView', name: 'SearchView',
components: { components: {
TooltipButton, TooltipButton,
ResourceIcon ResourceIcon,
Status
}, },
props: { props: {
searchFilters: { searchFilters: {
@ -206,13 +212,7 @@ export default {
if (to && to.query && 'q' in to.query) { if (to && to.query && 'q' in to.query) {
this.searchQuery = to.query.q this.searchQuery = to.query.q
} }
this.isFiltered = false this.updateIsFiltered()
this.searchFilters.some(item => {
if (this.searchParams[item]) {
this.isFiltered = true
return true
}
})
} }
}, },
mounted () { mounted () {
@ -220,6 +220,7 @@ export default {
if (this.$route && this.$route.query && 'q' in this.$route.query) { if (this.$route && this.$route.query && 'q' in this.$route.query) {
this.searchQuery = this.$route.query.q this.searchQuery = this.$route.query.q
} }
this.updateIsFiltered()
}, },
computed: { computed: {
styleSearch () { styleSearch () {
@ -285,7 +286,7 @@ export default {
} }
if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level', if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level',
'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider', 'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider',
'type'].includes(item) 'type', 'scope', 'managementserverid'].includes(item)
) { ) {
type = 'list' type = 'list'
} else if (item === 'tags') { } else if (item === 'tags') {
@ -319,6 +320,13 @@ export default {
} }
} }
if (arrayField.includes('scope')) {
const scopeIndex = this.fields.findIndex(item => item.name === 'scope')
this.fields[scopeIndex].loading = true
this.fields[scopeIndex].opts = this.fetchScope()
this.fields[scopeIndex].loading = false
}
if (arrayField.includes('state')) { if (arrayField.includes('state')) {
const stateIndex = this.fields.findIndex(item => item.name === 'state') const stateIndex = this.fields.findIndex(item => item.name === 'state')
this.fields[stateIndex].loading = true this.fields[stateIndex].loading = true
@ -397,6 +405,7 @@ export default {
let podIndex = -1 let podIndex = -1
let clusterIndex = -1 let clusterIndex = -1
let groupIndex = -1 let groupIndex = -1
let managementServerIdIndex = -1
if (arrayField.includes('type')) { if (arrayField.includes('type')) {
if (this.$route.path === '/alert') { if (this.$route.path === '/alert') {
@ -464,6 +473,12 @@ export default {
promises.push(await this.fetchInstanceGroups(searchKeyword)) promises.push(await this.fetchInstanceGroups(searchKeyword))
} }
if (arrayField.includes('managementserverid')) {
managementServerIdIndex = this.fields.findIndex(item => item.name === 'managementserverid')
this.fields[managementServerIdIndex].loading = true
promises.push(await this.fetchManagementServers(searchKeyword))
}
Promise.all(promises).then(response => { Promise.all(promises).then(response => {
if (typeIndex > -1) { if (typeIndex > -1) {
const types = response.filter(item => item.type === 'type') const types = response.filter(item => item.type === 'type')
@ -525,6 +540,12 @@ export default {
this.fields[groupIndex].opts = this.sortArray(groups[0].data) this.fields[groupIndex].opts = this.sortArray(groups[0].data)
} }
} }
if (managementServerIdIndex > -1) {
const managementServers = response.filter(item => item.type === 'managementserverid')
if (managementServers && managementServers.length > 0) {
this.fields[managementServerIdIndex].opts = this.sortArray(managementServers[0].data)
}
}
}).finally(() => { }).finally(() => {
if (typeIndex > -1) { if (typeIndex > -1) {
this.fields[typeIndex].loading = false this.fields[typeIndex].loading = false
@ -550,6 +571,9 @@ export default {
if (groupIndex > -1) { if (groupIndex > -1) {
this.fields[groupIndex].loading = false this.fields[groupIndex].loading = false
} }
if (managementServerIdIndex > -1) {
this.fields[managementServerIdIndex].loading = false
}
this.fillFormFieldValues() this.fillFormFieldValues()
}) })
}, },
@ -757,6 +781,19 @@ export default {
}) })
} }
}, },
fetchManagementServers (searchKeyword) {
return new Promise((resolve, reject) => {
api('listManagementServers', { listAll: true, keyword: searchKeyword }).then(json => {
const managementservers = json.listmanagementserversresponse.managementserver
resolve({
type: 'managementserverid',
data: managementservers
})
}).catch(error => {
reject(error.response.headers['x-description'])
})
})
},
fetchGuestNetworkTypes () { fetchGuestNetworkTypes () {
const types = [] const types = []
if (this.apiName.indexOf('listNetworks') > -1) { if (this.apiName.indexOf('listNetworks') > -1) {
@ -877,9 +914,30 @@ export default {
} }
return types return types
}, },
fetchScope () {
const scope = []
if (this.apiName.indexOf('listWebhooks') > -1) {
scope.push({
id: 'Local',
name: 'label.local'
})
scope.push({
id: 'Domain',
name: 'label.domain'
})
if (isAdmin()) {
scope.push({
id: 'Global',
name: 'label.global'
})
}
}
return scope
},
fetchState () { fetchState () {
var state = []
if (this.apiName.includes('listVolumes')) { if (this.apiName.includes('listVolumes')) {
return [ state = [
{ {
id: 'Allocated', id: 'Allocated',
name: 'label.allocated' name: 'label.allocated'
@ -906,7 +964,7 @@ export default {
} }
] ]
} else if (this.apiName.includes('listKubernetesClusters')) { } else if (this.apiName.includes('listKubernetesClusters')) {
return [ state = [
{ {
id: 'Created', id: 'Created',
name: 'label.created' name: 'label.created'
@ -956,8 +1014,19 @@ export default {
name: 'label.error' name: 'label.error'
} }
] ]
} else if (this.apiName.indexOf('listWebhooks') > -1) {
state = [
{
id: 'Enabled',
name: 'label.enabled'
},
{
id: 'Disabled',
name: 'label.disabled'
}
]
} }
return [] return state
}, },
fetchEntityType () { fetchEntityType () {
const entityType = [] const entityType = []
@ -1052,6 +1121,13 @@ export default {
}, },
changeFilter (filter) { changeFilter (filter) {
this.$emit('change-filter', filter) this.$emit('change-filter', filter)
},
updateIsFiltered () {
this.isFiltered = this.searchFilters.some(item => {
if (this.searchParams[item]) {
return true
}
})
} }
} }
} }

View File

@ -22,9 +22,10 @@
:title="$t('label.select.period')" :title="$t('label.select.period')"
:maskClosable="false" :maskClosable="false"
:footer="null"> :footer="null">
<filter-stats <date-time-filter
:startDateProp="startDate" :startDateProp="startDate"
:endDateProp="endDate" :endDateProp="endDate"
:allDataMessage="$t('message.alert.show.all.stats.data')"
@closeAction="closeAction" @closeAction="closeAction"
@onSubmit="handleSubmit"/> @onSubmit="handleSubmit"/>
</a-modal> </a-modal>
@ -251,7 +252,7 @@
import { api } from '@/api' import { api } from '@/api'
import moment from 'moment' import moment from 'moment'
import 'chartjs-adapter-moment' import 'chartjs-adapter-moment'
import FilterStats from './stats/FilterStats' import DateTimeFilter from './DateTimeFilter'
import ResourceStatsInfo from './stats/ResourceStatsInfo' import ResourceStatsInfo from './stats/ResourceStatsInfo'
import ResourceStatsLineChart from './stats/ResourceStatsLineChart' import ResourceStatsLineChart from './stats/ResourceStatsLineChart'
@ -267,7 +268,7 @@ export default {
} }
}, },
components: { components: {
FilterStats, DateTimeFilter,
ResourceStatsInfo, ResourceStatsInfo,
ResourceStatsLineChart ResourceStatsLineChart
}, },

View File

@ -0,0 +1,278 @@
// 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.
<template>
<div class="form-layout">
<div v-if="(resource || payloadUrl)">
<a-divider />
<a-collapse
v-model:activeKey="collapseKey">
<a-collapse-panel
:showArrow="isResponseNotEmpty"
key="1"
:collapsible="(resource || payloadUrl) ? 'enabled' : 'disabled'">
<template #header>
<a-row style="width: 100%;">
<a-col :span="22">
<tooltip-label :title="$t('label.status')" v-if="isNotShowStatus"/>
<status class="status" :text="response.success ? 'success' : 'error'" displayText v-else/>
</a-col>
<a-col :span="2">
<div v-if="showActions">
<a-spin :spinning="loading" size="small" v-if="loading" />
<div v-else>
<a-button
type="primary"
size="small"
@click.stop="testWebhookDelivery">
<render-icon icon="reload-outlined" />
<div class="ant-btn__progress-overlay" :style="computedOverlayStyle"></div>
</a-button>
</div>
</div>
</a-col>
</a-row>
</template>
<div v-if="isResponseNotEmpty">
<a-row>
<a-col :span="8">
<div class="response-detail-item" v-if="('success' in response)">
<div class="response-detail-item__label"><strong>{{ $t('label.success') }}</strong></div>
<div class="response-detail-item__details">
<status class="status" :text="response.success ? 'success' : 'error'"/>
</div>
</div>
</a-col>
<a-col :span="8">
<div class="response-detail-item" v-if="response.startdate && response.enddate">
<div class="response-detail-item__label"><strong>{{ $t('label.duration') }}</strong></div>
<div class="response-detail-item__details">
{{ responseDuration }}
</div>
</div>
</a-col>
<a-col :span="8">
<div class="response-detail-item" v-if="response.managementserverid">
<div class="response-detail-item__label"><strong>{{ $t('label.managementserverid') }}</strong></div>
<div class="response-detail-item__details">
{{ response.managementservername }}
</div>
</div>
</a-col>
</a-row>
<a-alert
:type="response.success ? 'success' : 'error'"
:showIcon="true"
:message="$t('label.response')"
:description="response.response ? response.response : 'Empty response'" />
</div>
</a-collapse-panel>
</a-collapse>
<a-divider />
</div>
</div>
</template>
<script>
import { api } from '@/api'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import Status from '@/components/widgets/Status'
export default {
name: 'TestWebhookDeliveryView',
components: {
TooltipLabel,
Status
},
props: {
resource: {
type: Object
},
payload: {
type: String
},
payloadUrl: {
type: String
},
sslVerification: {
type: Boolean,
default: false
},
secretKey: {
type: String
},
showActions: {
type: Boolean,
default: false
}
},
data () {
return {
response: {},
collapseKey: undefined,
loading: false,
testDeliveryInterval: null,
testDeliveryIntervalCouter: 0
}
},
beforeCreate () {
this.timedDeliveryWait = 4000
},
beforeUnmount () {
this.resetTestDeliveryInterval()
},
computed: {
isResponseNotEmpty () {
return this.response && Object.keys(this.response).length > 0
},
isNotShowStatus () {
return !this.isResponseNotEmpty ||
this.collapseKey === '1' ||
(Array.isArray(this.collapseKey) &&
this.collapseKey.length > 0 &&
this.collapseKey[0] === '1')
},
responseDuration () {
if (!this.response.startdate || !this.response.enddate) {
return ''
}
var duration = Date.parse(this.response.enddate) - Date.parse(this.response.startdate)
return (duration > 0 ? duration / 1000.0 : 0) + ''
},
computedOverlayStyle () {
var opacity = this.testDeliveryIntervalCouter <= 10.0 ? 0 : 0.3
var width = this.testDeliveryIntervalCouter
return 'opacity: ' + opacity + '; width: ' + width + '%;'
}
},
methods: {
validateUrl (url) {
const urlPattern = /^(http|https):\/\/[^ "]+$/
urlPattern.test(url)
},
resetTestDeliveryInterval () {
if (this.testDeliveryInterval) {
clearInterval(this.testDeliveryInterval)
}
this.testDeliveryIntervalCouter = 0
},
testWebhookDelivery () {
this.resetTestDeliveryInterval()
this.response = {}
this.loading = true
this.$emit('change-loading', this.loading)
var params = {}
if (this.resource) {
params.webhookid = this.resource.id
}
if (this.payload) {
params.payload = this.payload
}
if (this.payloadUrl) {
params.payloadUrl = this.payloadUrl
}
if (this.sslVerification) {
params.payload = this.sslVerification
}
if (this.secretKey) {
params.secretKey = this.secretKey
}
api('executeWebhookDelivery', params).then(response => {
this.response = response.executewebhookdeliveryresponse.webhookdelivery
this.$emit('update-success', response.success)
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
this.$emit('change-loading', this.loading)
})
},
getNormalizedPayloadUrl () {
if (!this.payloadUrl || this.payloadUrl === '' || this.validateUrl(this.payloadUrl)) {
return this.payloadUrl
}
return 'http://' + this.payloadUrl
},
executeTestWebhookDeliveryOrReset () {
const url = this.getNormalizedPayloadUrl()
if (url) {
this.testWebhookDelivery()
return
}
this.resetTestDeliveryInterval()
},
timedTestWebhookDelivery () {
const url = this.getNormalizedPayloadUrl()
this.resetTestDeliveryInterval()
this.testDeliveryInterval = setInterval(() => {
if (!url) {
this.resetTestDeliveryInterval()
return
}
this.testDeliveryIntervalCouter = this.testDeliveryIntervalCouter + 1
if (this.testDeliveryIntervalCouter >= 100) {
this.executeTestWebhookDeliveryOrReset()
}
}, this.timedDeliveryWait / 100)
}
}
}
</script>
<style scoped lang="scss">
.response-detail-item {
margin-bottom: 20px;
word-break: break-all;
&__details {
display: flex;
align-items: center;
&--start {
align-items: flex-start;
i {
margin-top: 4px;
}
}
}
.anticon {
margin-right: 10px;
}
&__label {
margin-bottom: 5px;
font-weight: bold;
}
}
.ant-btn .ant-btn__progress-overlay {
position: absolute;
width: 100%;
height: 100%;
z-index: 5;
opacity: 0.3;
transition: all 0s ease;
position: absolute;
left: 0;
top: 0;
background-color: #666;
}
</style>

View File

@ -0,0 +1,526 @@
// 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.
<template>
<div>
<a-modal
v-model:visible="showTimeFilterModal"
:title="$t('label.select.period')"
:maskClosable="false"
:footer="null">
<date-time-filter
:startDateProp="startDate"
:endDateProp="endDate"
:showAllDataOption="false"
@closeAction="closeTimeFilterAction"
@onSubmit="handleSubmitDateTimeFilter"/>
</a-modal>
<div class="filter-row">
<a-row>
<a-col :xs="24" :md="12">
<a-space direction="vertical">
<div>
<a-radio-group
v-model:value="durationSelectorValue"
buttonStyle="solid"
@change="handleTimeFilterChange">
<a-radio-button value="day">
{{ $t('label.duration.24hours') }}
</a-radio-button>
<a-radio-button value="all">
{{ $t('All') }}
</a-radio-button>
<a-radio-button value="custom">
{{ $t('label.duration.custom') }}
</a-radio-button>
</a-radio-group>
<InfoCircleOutlined class="info-icon" :title="$t('message.webhook.deliveries.time.filter')"/>
</div>
<div class="ant-tag" v-if="durationSelectorValue==='custom'">
<a-button @click="openTimeFilter()">
<FilterOutlined/>
</a-button>
<span v-html="formatedPeriod"></span>
</div>
</a-space>
</a-col>
<a-col :xs="24" :md="12" style="text-align: right; padding-right: 10px;">
<span>
<search-view
:searchFilters="searchFilters"
:searchParams="searchParams"
:apiName="'listWebhookDeliveries'"
@search="searchDelivieries"
/>
</span>
</a-col>
</a-row>
</div>
<a-button
v-if="('deleteWebhookDelivery' in $store.getters.apis) && ((selectedRowKeys && selectedRowKeys.length > 0) || (durationSelectorValue === 'all' && searchParamsIsEmpty))"
type="danger"
danger
style="width: 100%; margin-bottom: 15px"
@click="clearOrDeleteDeliveriesConfirmation()">
<template #icon><delete-outlined /></template>
{{ (selectedRowKeys && selectedRowKeys.length > 0) ? $t('label.action.delete.webhook.deliveries') : $t('label.action.clear.webhook.deliveries') }}
</a-button>
<list-view
:tabLoading="tabLoading"
:columns="columns"
:items="deliveries"
:actions="actions"
:columnKeys="columnKeys"
:explicitlyAllowRowSelection="true"
:selectedColumns="selectedColumnKeys"
ref="listview"
@update-selected-columns="updateSelectedColumns"
@refresh="this.fetchData"
@selection-change="updateSelectedRows"/>
<a-pagination
class="row-element"
style="margin-top: 10px"
size="small"
:current="page"
:pageSize="pageSize"
:total="totalCount"
:showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page-1)*pageSize))}-${Math.min(page*pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
:pageSizeOptions="pageSizeOptions"
@change="changePage"
@showSizeChange="changePage"
showSizeChanger
showQuickJumper>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
</div>
</template>
<script>
import { api } from '@/api'
import { isAdmin } from '@/role'
import { genericCompare } from '@/utils/sort.js'
import moment from 'moment'
import DateTimeFilter from './DateTimeFilter'
import SearchView from '@/components/view/SearchView'
import ListView from '@/components/view/ListView'
export default {
name: 'WebhookDeliveriesTab',
components: {
DateTimeFilter,
SearchView,
ListView
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
required: true
}
},
data () {
return {
tabLoading: false,
columnKeys: ['payload', 'eventtype', 'success', 'response', 'startdate', 'duration'],
selectedColumnKeys: ['payload', 'eventtype', 'success', 'duration'],
selectedRowKeys: [],
columns: [],
cols: [],
deliveries: [],
actions: [
{
api: 'executeWebhookDelivery',
icon: 'retweet-outlined',
label: 'label.redeliver',
message: 'message.redeliver.webhook.delivery',
dataView: true,
popup: true
},
{
api: 'deleteWebhookDelivery',
icon: 'delete-outlined',
label: 'label.delete.webhook.delivery',
message: 'message.redeliver.webhook.delivery',
dataView: true,
popup: true
}
],
page: 1,
pageSize: 20,
totalCount: 0,
durationSelectorValue: 'day',
showTimeFilterModal: false,
endDate: null,
startDate: this.get24hrStartDate(),
formatedPeriod: null,
searchFilters: [
'eventtype'
],
searchParams: {}
}
},
computed: {
pageSizeOptions () {
var sizes = [20, 50, 100, 200, this.$store.getters.defaultListViewPageSize]
if (this.device !== 'desktop') {
sizes.unshift(10)
}
return [...new Set(sizes)].sort(function (a, b) {
return a - b
}).map(String)
},
searchParamsIsEmpty () {
if (!this.searchParams) {
return true
}
return Object.keys(this.searchParams).length === 0
}
},
created () {
const routeQuery = this.$route.query
const usefulQueryParams = ['keyword', 'managementserverid', 'eventtype']
usefulQueryParams.forEach(queryParam => {
if (routeQuery[queryParam]) {
this.searchParams[queryParam] = routeQuery[queryParam]
}
})
if (isAdmin()) {
this.columnKeys.splice(2, 0, 'managementservername')
this.selectedColumnKeys.splice(2, 0, 'managementservername')
this.searchFilters.push('managementserverid')
}
this.updateColumns()
this.pageSize = this.pageSizeOptions[0] * 1
this.fetchData()
},
watch: {
resource: {
handler () {
this.fetchData()
}
}
},
methods: {
fetchData () {
if ('listview' in this.$refs && this.$refs.listview) {
this.$refs.listview.resetSelection()
}
this.fetchDeliveries()
},
fetchDeliveries () {
this.deliveries = []
if (!this.resource.id) {
return
}
const params = {
page: this.page,
pagesize: this.pageSize,
webhookid: this.resource.id,
listall: true
}
if (this.startDate) {
params.startDate = moment(this.startDate).format()
}
if (this.endDate) {
params.endDate = moment(this.endDate).format()
}
if (this.searchParams?.searchQuery) {
params.keyword = this.searchParams.searchQuery
}
if (this.searchParams?.managementserverid) {
params.managementserverid = this.searchParams.managementserverid
}
if (this.searchParams?.eventtype) {
params.eventtype = this.searchParams.eventtype
}
this.tabLoading = true
api('listWebhookDeliveries', params).then(json => {
this.deliveries = []
this.totalCount = json?.listwebhookdeliveriesresponse?.count || 0
this.deliveries = json?.listwebhookdeliveriesresponse?.webhookdelivery || []
this.formatTimeFilterPeriod()
this.tabLoading = false
})
},
changePage (page, pageSize) {
this.page = page
this.pageSize = pageSize
this.fetchDeliveries()
},
updateSelectedColumns (key) {
if (this.selectedColumnKeys.includes(key)) {
this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)
} else {
this.selectedColumnKeys.push(key)
}
this.updateColumns()
},
updateColumns () {
this.columns = []
for (var columnKey of this.columnKeys) {
const key = columnKey
if (!this.selectedColumnKeys.includes(key)) continue
var title = this.$t('label.' + String(key).toLowerCase())
if (key === 'eventtype') {
title = this.$t('label.event')
}
this.columns.push({
key: key,
title: title,
dataIndex: key,
sorter: (a, b) => { return genericCompare(a[key] || '', b[key] || '') }
})
}
if (this.columns.length > 0) {
this.columns[this.columns.length - 1].customFilterDropdown = true
}
},
updateSelectedRows (keys) {
this.selectedRowKeys = keys
},
clearOrDeleteDeliveriesConfirmation () {
const self = this
const title = (this.selectedRowKeys && this.selectedRowKeys.length > 0) ? this.$t('label.action.delete.webhook.deliveries') : this.$t('label.action.clear.webhook.deliveries')
this.$confirm({
title: title,
okText: this.$t('label.ok'),
okType: 'danger',
cancelText: this.$t('label.cancel'),
onOk () {
if (self.selectedRowKeys && self.selectedRowKeys.length > 0) {
self.deletedSelectedDeliveries()
return
}
self.clearDeliveries()
}
})
},
deletedSelectedDeliveries () {
const promises = []
this.selectedRowKeys.forEach(id => {
const params = {
id: id
}
promises.push(new Promise((resolve, reject) => {
api('deleteWebhookDelivery', params).then(json => {
return resolve(id)
}).catch(error => {
return reject(error)
})
}))
})
const msg = this.$t('label.action.delete.webhook.deliveries')
this.$message.info({
content: msg,
duration: 3
})
this.tabLoading = true
Promise.all(promises).finally(() => {
this.tabLoading = false
this.fetchData()
})
},
clearDeliveries () {
const params = {
webhookid: this.resource.id
}
this.tabLoading = true
api('deleteWebhookDelivery', params).then(json => {
this.$message.success(this.$t('message.success.clear.webhook.deliveries'))
this.fetchData()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.tabLoading = false
})
},
redeliverDeliveryConfirmation (item) {
const self = this
this.$confirm({
title: this.$t('label.redeliver') + ' ' + item.eventtype,
okText: this.$t('label.ok'),
okType: 'primary',
cancelText: this.$t('label.cancel'),
onOk () {
self.redeliverDelivery(item)
}
})
},
redeliverDelivery (item) {
const params = {
id: item.id
}
this.tabLoading = true
api('executeWebhookDelivery', params).then(json => {
this.$message.success(this.$t('message.success.redeliver.webhook.delivery'))
this.fetchData()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.tabLoading = false
})
},
deleteDeliveryConfirmation (item) {
const self = this
this.$confirm({
title: this.$t('label.delete') + ' ' + item.eventtype,
okText: this.$t('label.ok'),
okType: 'primary',
cancelText: this.$t('label.cancel'),
onOk () {
self.deleteDelivery(item)
}
})
},
deleteDelivery (item) {
const params = {
id: item.id
}
this.tabLoading = true
api('deleteWebhookDelivery', params).then(json => {
const message = `${this.$t('message.success.delete')} ${this.$t('label.webhook.delivery')}`
this.$message.success(message)
this.fetchData()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.tabLoading = false
})
},
execAction (action) {
if (action.api === 'executeWebhookDelivery') {
this.redeliverDeliveryConfirmation(action.resource)
} else if (action.api === 'deleteWebhookDelivery') {
this.deleteDeliveryConfirmation(action.resource)
}
},
get24hrStartDate () {
const start = new Date()
start.setDate(start.getDate() - 1)
return start
},
handleTimeFilterChange () {
var start = this.startDate
var end = this.endDate
switch (this.durationSelectorValue) {
case 'day':
start = this.get24hrStartDate()
end = null
break
case 'all':
start = null
end = null
break
}
if (start !== this.startDate || end !== this.endDate) {
this.startDate = start
this.endDate = end
this.fetchData()
}
},
openTimeFilter () {
this.showTimeFilterModal = true
},
formatTimeFilterPeriod () {
var formatedStartDate = null
var formatedEndDate = null
if (this.startDate) {
formatedStartDate = moment(this.startDate).format('MMM DD, YYYY') + ' at ' + moment(this.startDate).format('HH:mm:ss')
}
if (this.endDate) {
formatedEndDate = moment(this.endDate).format('MMM DD, YYYY') + ' at ' + moment(this.endDate).format('HH:mm:ss')
}
if (formatedStartDate && formatedEndDate) {
this.formatedPeriod = ' ' + this.$t('label.datetime.filter.period', { startDate: formatedStartDate, endDate: formatedEndDate })
} else if (formatedStartDate && !formatedEndDate) {
this.formatedPeriod = ' ' + this.$t('label.datetime.filter.starting', { startDate: formatedStartDate })
} else if (!formatedStartDate && formatedEndDate) {
this.formatedPeriod = ' ' + this.$t('label.datetime.filter.up.to', { endDate: formatedEndDate })
} else {
this.formatedPeriod = ' <b>' + this.$t('label.all.available.data') + '</b>'
}
},
handleSubmitDateTimeFilter (values) {
if (values.startDate) {
this.startDate = new Date(values.startDate)
} else {
this.startDate = null
}
if (values.endDate) {
this.endDate = new Date(values.endDate)
} else {
this.endDate = null
}
this.showTimeFilterModal = false
this.fetchData()
},
closeTimeFilterAction () {
this.showTimeFilterModal = false
},
searchDelivieries (params) {
const query = Object.assign({}, this.$route.query)
for (const key in this.searchParams) {
delete query[key]
}
delete query.keyword
delete query.q
this.searchParams = params
if ('searchQuery' in this.searchParams) {
query.keyword = this.searchParams.searchQuery
} else {
Object.assign(query, this.searchParams)
}
this.fetchData()
if (JSON.stringify(query) === JSON.stringify(this.$route.query)) {
return
}
history.pushState(
{},
null,
'#' + this.$route.path + '?' + Object.keys(query).map(key => {
return (
encodeURIComponent(key) + '=' + encodeURIComponent(query[key])
)
}).join('&')
)
}
}
}
</script>
<style lang="scss" scoped>
.ant-tag {
padding: 0 7px 0 0;
}
.ant-select {
margin-left: 10px;
}
.info-icon {
margin: 0 10px 0 5px;
}
.filter-row {
margin-bottom: 2.5%;
}
.filter-row-inner {
margin-top: 3%;
}
</style>

View File

@ -15,6 +15,7 @@
// specific language governing permissions and limitations // specific language governing permissions and limitations
// under the License. // under the License.
import store from '@/store' import store from '@/store'
import { shallowRef, defineAsyncComponent } from 'vue'
export default { export default {
name: 'tools', name: 'tools',
@ -77,6 +78,154 @@ export default {
resourceType: 'UserVm', resourceType: 'UserVm',
permission: ['listInfrastructure', 'listVolumesForImport'], permission: ['listInfrastructure', 'listVolumesForImport'],
component: () => import('@/views/tools/ManageVolumes.vue') component: () => import('@/views/tools/ManageVolumes.vue')
},
{
name: 'webhook',
title: 'label.webhooks',
icon: 'node-index-outlined',
docHelp: 'adminguide/webhooks.html',
permission: ['listWebhooks'],
columns: () => {
const cols = ['name', 'payloadurl', 'state', 'created']
if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
cols.splice(3, 0, 'account', 'domain', 'scope')
}
if (store.getters.listAllProjects) {
cols.push('project')
}
return cols
},
details: ['name', 'id', 'description', 'scope', 'payloadurl', 'sslverification', 'secretkey', 'state', 'account', 'domainid'],
searchFilters: () => {
var filters = ['state']
if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
filters.push('scope', 'domainid', 'account')
}
return filters
},
tabs: [
{
name: 'details',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'recent.deliveries',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/WebhookDeliveriesTab.vue')))
}
],
actions: [
{
api: 'createWebhook',
icon: 'plus-outlined',
label: 'label.create.webhook',
docHelp: 'adminguide/events.html#creating-webhooks',
listView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/tools/CreateWebhook.vue')))
},
{
api: 'updateWebhook',
icon: 'edit-outlined',
label: 'label.update.webhook',
dataView: true,
popup: true,
args: ['name', 'description', 'payloadurl', 'sslverification', 'secretkey', 'state'],
mapping: {
state: {
options: ['Enabled', 'Disabled']
}
}
},
{
api: 'updateWebhook',
icon: 'play-circle-outlined',
label: 'label.enable.webhook',
message: 'message.confirm.enable.webhook',
dataView: true,
groupAction: true,
popup: true,
defaultArgs: { state: 'Enabled' },
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
show: (record) => { return ['Disabled'].includes(record.state) }
},
{
api: 'updateWebhook',
icon: 'pause-circle-outlined',
label: 'label.disable.webhook',
message: 'message.confirm.disable.webhook',
dataView: true,
groupAction: true,
popup: true,
defaultArgs: { state: 'Disabled' },
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
show: (record) => { return ['Enabled'].includes(record.state) }
},
{
api: 'executeWebhookDelivery',
icon: 'right-square-outlined',
label: 'label.test.webhook.delivery',
message: 'message.test.webhook.delivery',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/tools/TestWebhookDelivery.vue')))
},
{
api: 'deleteWebhook',
icon: 'delete-outlined',
label: 'label.delete.webhook',
message: 'message.delete.webhook',
dataView: true,
groupAction: true,
popup: true,
groupShow: (selectedItems, storegetters) => {
if (['Admin'].includes(storegetters.userInfo.roletype)) {
return true
}
},
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
}
]
},
{
name: 'webhookdeliveries',
title: 'label.webhook.deliveries',
icon: 'gateway-outlined',
hidden: true,
permission: ['listWebhookDeliveries'],
columns: () => {
const cols = ['payload', 'eventtype', 'webhookname', 'success', 'response', 'duration']
if (['Admin'].includes(store.getters.userInfo.roletype)) {
cols.splice(3, 0, 'managementservername')
}
return cols
},
details: () => {
const fields = ['id', 'eventid', 'eventtype', 'headers', 'payload', 'success', 'response', 'startdate', 'enddate']
if (['Admin'].includes(store.getters.userInfo.roletype)) {
fields.splice(1, 0, 'managementserverid', 'managementservername')
}
return fields
},
actions: [
{
api: 'executeWebhookDelivery',
icon: 'retweet-outlined',
label: 'label.redeliver',
message: 'message.redeliver.webhook.delivery',
dataView: true,
popup: true
},
{
api: 'deleteWebhookDelivery',
icon: 'delete-outlined',
label: 'label.delete.webhook.delivery',
message: 'message.delete.webhook.delivery',
dataView: true,
groupAction: true,
popup: true,
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
}
]
} }
] ]
} }

View File

@ -120,6 +120,7 @@ import {
MinusSquareOutlined, MinusSquareOutlined,
MobileOutlined, MobileOutlined,
MoreOutlined, MoreOutlined,
NodeIndexOutlined,
NotificationOutlined, NotificationOutlined,
NumberOutlined, NumberOutlined,
LaptopOutlined, LaptopOutlined,
@ -139,7 +140,9 @@ import {
ReconciliationOutlined, ReconciliationOutlined,
RedoOutlined, RedoOutlined,
ReloadOutlined, ReloadOutlined,
RetweetOutlined,
RightCircleOutlined, RightCircleOutlined,
RightSquareOutlined,
RocketOutlined, RocketOutlined,
SafetyCertificateOutlined, SafetyCertificateOutlined,
SafetyOutlined, SafetyOutlined,
@ -281,6 +284,7 @@ export default {
app.component('MinusSquareOutlined', MinusSquareOutlined) app.component('MinusSquareOutlined', MinusSquareOutlined)
app.component('MobileOutlined', MobileOutlined) app.component('MobileOutlined', MobileOutlined)
app.component('MoreOutlined', MoreOutlined) app.component('MoreOutlined', MoreOutlined)
app.component('NodeIndexOutlined', NodeIndexOutlined)
app.component('NotificationOutlined', NotificationOutlined) app.component('NotificationOutlined', NotificationOutlined)
app.component('NumberOutlined', NumberOutlined) app.component('NumberOutlined', NumberOutlined)
app.component('LaptopOutlined', LaptopOutlined) app.component('LaptopOutlined', LaptopOutlined)
@ -300,7 +304,9 @@ export default {
app.component('ReconciliationOutlined', ReconciliationOutlined) app.component('ReconciliationOutlined', ReconciliationOutlined)
app.component('RedoOutlined', RedoOutlined) app.component('RedoOutlined', RedoOutlined)
app.component('ReloadOutlined', ReloadOutlined) app.component('ReloadOutlined', ReloadOutlined)
app.component('RetweetOutlined', RetweetOutlined)
app.component('RightCircleOutlined', RightCircleOutlined) app.component('RightCircleOutlined', RightCircleOutlined)
app.component('RightSquareOutlined', RightSquareOutlined)
app.component('RocketOutlined', RocketOutlined) app.component('RocketOutlined', RocketOutlined)
app.component('SafetyCertificateOutlined', SafetyCertificateOutlined) app.component('SafetyCertificateOutlined', SafetyCertificateOutlined)
app.component('SafetyOutlined', SafetyOutlined) app.component('SafetyOutlined', SafetyOutlined)

View File

@ -798,7 +798,7 @@ export default {
this.projectView = Boolean(store.getters.project && store.getters.project.id) this.projectView = Boolean(store.getters.project && store.getters.project.id)
this.hasProjectId = ['vm', 'vmgroup', 'ssh', 'affinitygroup', 'userdata', 'volume', 'snapshot', 'vmsnapshot', 'guestnetwork', this.hasProjectId = ['vm', 'vmgroup', 'ssh', 'affinitygroup', 'userdata', 'volume', 'snapshot', 'vmsnapshot', 'guestnetwork',
'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes', 'vpc', 'securitygroups', 'publicip', 'vpncustomergateway', 'template', 'iso', 'event', 'kubernetes',
'autoscalevmgroup', 'vnfapp'].includes(this.$route.name) 'autoscalevmgroup', 'vnfapp', 'webhook'].includes(this.$route.name)
if ((this.$route && this.$route.params && this.$route.params.id) || this.$route.query.dataView) { if ((this.$route && this.$route.params && this.$route.params.id) || this.$route.query.dataView) {
this.dataView = true this.dataView = true
@ -1553,13 +1553,16 @@ export default {
continue continue
} }
if (input === undefined || input === null || if (input === undefined || input === null ||
(input === '' && !['updateStoragePool', 'updateHost', 'updatePhysicalNetwork', 'updateDiskOffering', 'updateNetworkOffering', 'updateServiceOffering', 'updateZone', 'updateAccount'].includes(action.api))) { (input === '' && !['updateStoragePool', 'updateHost', 'updatePhysicalNetwork',
'updateDiskOffering', 'updateNetworkOffering', 'updateServiceOffering',
'updateZone', 'updateAccount', 'updateWebhook'].includes(action.api))) {
if (param.type === 'boolean') { if (param.type === 'boolean') {
params[key] = false params[key] = false
} }
break break
} }
if (input === '' && !['tags', 'hosttags', 'storagetags', 'dns2', 'ip6dns1', 'ip6dns2', 'internaldns2', 'networkdomain'].includes(key)) { if (input === '' && !['tags', 'hosttags', 'storagetags', 'dns2', 'ip6dns1',
'ip6dns2', 'internaldns2', 'networkdomain', 'secretkey'].includes(key)) {
break break
} }
if (action.mapping && key in action.mapping && action.mapping[key].options) { if (action.mapping && key in action.mapping && action.mapping[key].options) {

View File

@ -0,0 +1,357 @@
// 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.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical">
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-model:value="form.name"
:placeholder="apiParams.name.description"
v-focus="true" />
</a-form-item>
<a-form-item name="description" ref="description">
<template #label>
<tooltip-label :title="$t('label.description')" :tooltip="apiParams.description.description"/>
</template>
<a-input
v-model:value="form.description"
:placeholder="apiParams.description.description"/>
</a-form-item>
<a-form-item name="scope" ref="scope" v-if="isAdminOrDomainAdmin">
<template #label>
<tooltip-label :title="$t('label.scope')" :tooltip="apiParams.scope.description"/>
</template>
<a-radio-group
v-model:value="form.scope"
buttonStyle="solid"
@change="handleScopeChange">
<a-radio-button value="Local">
{{ $t('label.local') }}
</a-radio-button>
<a-radio-button value="Domain">
{{ $t('label.domain') }}
</a-radio-button>
<a-radio-button value="Global" v-if="isAdmin">
{{ $t('label.global') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="isAdminOrDomainAdmin && ['Domain', 'Local'].includes(form.scope)">
<template #label :title="apiParams.domainid.description">
{{ $t('label.domainid') }}
<a-tooltip>
<info-circle-outlined style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</template>
<a-select
id="domain-selection"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domainLoading"
:placeholder="apiParams.domainid.description"
@change="val => { handleDomainChanged(val) }">
<a-select-option v-for="opt in domains" :key="opt.id" :label="opt.path || opt.name || opt.description || ''">
{{ opt.path || opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="account" ref="account" v-if="isAdminOrDomainAdmin && ['Local'].includes(form.scope) && form.domainid">
<template #label>
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
</template>
<a-select
v-model:value="form.account"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="accountLoading"
:placeholder="apiParams.account.description">
<a-select-option v-for="opt in accounts" :key="opt.id" :label="opt.name">
{{ opt.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="payloadurl" ref="payloadurl">
<template #label>
<tooltip-label :title="$t('label.payloadurl')" :tooltip="apiParams.payloadurl.description"/>
</template>
<a-input
v-model:value="form.payloadurl"
:placeholder="apiParams.payloadurl.description"
@change="handleParamUpdate"/>
</a-form-item>
<a-form-item name="sslverification" ref="sslverification" v-if="isPayloadUrlHttps">
<template #label>
<tooltip-label :title="$t('label.sslverification')" :tooltip="apiParams.sslverification.description"/>
</template>
<a-alert
v-if="!form.sslverification"
class="ssl-alert"
type="warning"
:message="$t('message.disable.webhook.ssl.verification')" />
<a-switch
v-model:checked="form.sslverification"
@change="handleParamUpdate" />
</a-form-item>
<a-form-item name="secretkey" ref="secretkey">
<template #label>
<tooltip-label :title="$t('label.secretkey')" :tooltip="apiParams.secretkey.description"/>
</template>
<a-input
v-model:value="form.secretkey"
:placeholder="apiParams.secretkey.description"
@change="handleParamUpdate"/>
</a-form-item>
<test-webhook-delivery-view
ref="dispatchview"
:payloadUrl="form.payloadurl"
:sslVerification="form.sslverification"
:secretKey="form.secretkey"
:showActions="!(!form.payloadurl)" />
<a-form-item name="state" ref="state">
<template #label>
<tooltip-label :title="$t('label.enabled')" :tooltip="apiParams.state.description"/>
</template>
<a-switch v-model:checked="form.state" />
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import _ from 'lodash'
import { mixinForm } from '@/utils/mixin'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import TestWebhookDeliveryView from '@/components/view/TestWebhookDeliveryView'
export default {
name: 'CreateWebhook',
mixins: [mixinForm],
components: {
TooltipLabel,
TestWebhookDeliveryView
},
props: {},
data () {
return {
domains: [],
domainLoading: false,
accounts: [],
accountLoading: false,
loading: false,
testDeliveryAllowed: false,
testDeliveryLoading: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('createWebhook')
},
created () {
this.initForm()
if (['Domain', 'Local'].includes(this.form.scope)) {
this.fetchDomainData()
}
},
computed: {
isAdminOrDomainAdmin () {
return ['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
isAdmin () {
return ['Admin'].includes(this.$store.getters.userInfo.roletype)
},
isPayloadUrlHttps () {
if (this.form.payloadurl) {
return this.form.payloadurl.toLowerCase().startsWith('https://')
}
return false
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
scope: 'Local',
state: true,
sslverification: true
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.create.webhook.name') }],
payloadurl: [{ required: true, message: this.$t('message.error.create.webhook.payloadurl') }]
})
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
updateTestDeliveryLoading (value) {
this.testDeliveryLoading = value
},
fetchDomainData () {
this.domainLoading = true
this.domains = [
{
id: null,
name: ''
}
]
this.form.domainid = null
this.form.account = null
api('listDomains', {}).then(json => {
const listdomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listdomains)
}).finally(() => {
this.domainLoading = false
if (this.arrayHasItems(this.domains)) {
this.form.domainid = null
}
})
},
fetchAccountData () {
this.accounts = []
this.form.account = null
if (!this.form.domainid) {
return
}
this.accountLoading = true
var params = {
domainid: this.form.domainid
}
api('listAccounts', params).then(json => {
const listAccounts = json.listaccountsresponse.account || []
this.accounts = listAccounts
}).finally(() => {
this.accountLoading = false
if (this.arrayHasItems(this.accounts)) {
this.form.account = this.accounts[0].id
}
})
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
const params = {
name: values.name,
description: values.description,
payloadurl: values.payloadurl,
state: values.state ? 'Enabled' : 'Disabled'
}
if (this.isValidValueForKey(values, 'scope')) {
params.scope = values.scope
}
if (this.isValidValueForKey(values, 'sslverification')) {
params.sslverification = values.sslverification
}
if (this.isValidValueForKey(values, 'secretkey')) {
params.secretkey = values.secretkey
}
if (values.domainid) {
params.domainid = values.domainid
}
if (values.scope === 'Local' && values.domainid && !values.account) {
this.$notification.error({
message: this.$t('message.request.failed'),
description: this.$t('message.error.create.webhook.local.account')
})
return
}
if (values.account) {
const accountItem = _.find(this.accounts, (option) => option.id === values.account)
if (accountItem) {
params.account = accountItem.name
}
}
this.loading = true
api('createWebhook', params).then(json => {
this.$emit('refresh-data')
this.$notification.success({
message: this.$t('label.create.webhook'),
description: `${this.$t('message.success.create.webhook')} ${params.name}`
})
this.closeAction()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
},
closeAction () {
this.$emit('close-action')
},
handleParamUpdate (e) {
this.$refs.dispatchview.timedTestWebhookDelivery()
},
handleScopeChange (e) {
if (['Domain', 'Local'].includes(this.form.scope)) {
this.fetchDomainData()
}
},
handleDomainChanged (domainid) {
if (domainid) {
this.fetchAccountData()
}
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 80vw;
@media (min-width: 700px) {
width: 650px;
}
}
.ssl-alert {
margin: 0 0 10px 0;
}
</style>

Some files were not shown because too many files have changed in this diff Show More