diff --git a/src/main/java/net/lamgc/oracle/sentry/script/groovy/trigger/InstanceStateWatchTrigger.java b/src/main/java/net/lamgc/oracle/sentry/script/groovy/trigger/InstanceStateWatchTrigger.java new file mode 100644 index 0000000..c7f65e0 --- /dev/null +++ b/src/main/java/net/lamgc/oracle/sentry/script/groovy/trigger/InstanceStateWatchTrigger.java @@ -0,0 +1,286 @@ +package net.lamgc.oracle.sentry.script.groovy.trigger; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.oracle.bmc.core.ComputeClient; +import com.oracle.bmc.core.model.Instance; +import com.oracle.bmc.core.requests.GetInstanceRequest; +import groovy.lang.Closure; +import net.lamgc.oracle.sentry.ComputeInstanceManager; +import net.lamgc.oracle.sentry.oci.compute.ComputeInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 实例状态监视触发器. + *
通过轮询实例状态, 当状态为指定的状态时将触发回调执行操作. + *
吐槽: 换个地方轮询(xs).
+ * @author LamGC
+ */
+@TriggerName("InstanceStateWatcher")
+public class InstanceStateWatchTrigger implements GroovyTrigger {
+
+ private final static Logger log = LoggerFactory.getLogger(InstanceStateWatchTrigger.class);
+ private final static ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
+ .setDaemon(true)
+ .setNameFormat("Thread-InstanceStatePolling-%d")
+ .build();
+
+ private final Set 设置计算实例管理器以实现部分功能.
+ * @param instanceManager 实例管理器.
+ */
+ public void setInstanceManager(ComputeInstanceManager instanceManager) {
+ this.instanceManager.set(instanceManager);
+ }
+
+ /**
+ * 添加所有实例.
+ * 调用前需设置实例管理器.
+ */
+ public void allInstances() {
+ addInstance(instanceManager.get().getComputeInstances());
+ }
+
+ /**
+ * 添加需要监控的实例.
+ * 调用前需设置实例管理器.
+ * @param instanceId 要添加的实例 Id.(注意不是实例名称.)
+ */
+ public void addInstance(String instanceId) {
+ this.instances.add(instanceManager.get().getComputeInstanceById(instanceId));
+ }
+
+ /**
+ * 添加需要监控的实例.
+ * @param instance 实例对象.
+ */
+ public void addInstance(ComputeInstance instance) {
+ this.instances.add(instance);
+ }
+
+ /**
+ * 添加需要监控的实例.
+ * @param instances 实例集合.
+ */
+ public void addInstance(Set 该间隔是每轮查询之间的间隔, 并非查询每个实例的间隔. Oracle 对该接口的请求限制很宽松, 且单个帐号的实例有限, 故不再设置查询间隔.
+ * @param interval 间隔时常, 单位: 毫秒.
+ */
+ public void interval(long interval) {
+ this.interval = interval;
+ }
+
+ /**
+ * 要检查的状态.
+ * 当实例处于指定状态时将触发回调.
+ * @param state 状态名.
+ */
+ public void state(String state) {
+ targetStates.add(getState(state));
+ }
+
+ /**
+ * 要检查的状态.
+ * 当实例处于指定状态时将触发回调.
+ * @param states 状态集合.
+ */
+ public void state(String[] states) {
+ Set 所有状态都会触发回调函数.
+ */
+ public void allStates() {
+ targetStates.addAll(Set.of(Instance.LifecycleState.values()));
+ }
+
+ /**
+ * 通过状态名获取对应的 {@link Instance.LifecycleState}.
+ * @param state 状态名.
+ * @return 返回对应值.
+ * @throws IllegalArgumentException 当无法获取指定状态对象时抛出该异常.
+ */
+ private Instance.LifecycleState getState(String state) {
+ try {
+ return LifecycleStateMapping.valueOf(state.toUpperCase()).target;
+ } catch (IllegalArgumentException e) {
+ try {
+ return Instance.LifecycleState.valueOf(toFirstCharUpperCase(state));
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException("Invalid target state: " + state);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void run(final Closure> callback) {
+ if (pollingThreadReference.get() != null) {
+ throw new IllegalStateException("Attempting to start multiple check threads repeatedly, which is not allowed.");
+ }
+ if (targetStates.isEmpty()) {
+ throw new IllegalArgumentException("The target status has not been set.");
+ }
+ if (instances.isEmpty()) {
+ log.warn("尚未设置任何需监视的实例, 已跳过对触发器的启动.", new IllegalStateException("未设置任何需要监视的实例."));
+ return;
+ }
+
+ Thread newPollingThread = THREAD_FACTORY.newThread(new PollingTask(instances, targetStates, callback, interval));
+ newPollingThread.start();
+ pollingThreadReference.set(newPollingThread);
+ }
+
+ @Override
+ public synchronized void shutdown() {
+ if (pollingThreadReference.get() == null) {
+ return;
+ }
+ // 合规的,用于停止线程的方法。(笑)(无歧义)
+ Thread pollingThread = pollingThreadReference.get();
+ pollingThread.interrupt();
+ pollingThreadReference.set(null);
+ }
+
+ /**
+ * 首字母大写.
+ * @param str 要处理的单词.
+ * @return 如果首字母非大写, 就转换成大写.
+ */
+ private static String toFirstCharUpperCase(String str) {
+ char firstChar = str.charAt(0);
+ if (!Character.isUpperCase(firstChar)) {
+ return Character.toUpperCase(firstChar) + str.substring(1);
+ }
+ return str;
+ }
+
+ /**
+ * 轮询任务.
+ */
+ private static class PollingTask implements Runnable {
+
+ private final Set