This is an automated email from the ASF dual-hosted git repository.

dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new b76ef7482f [INLONG-9136][Agent] Add instance manager (#9142)
b76ef7482f is described below

commit b76ef7482ff3a59cb73cb686b971f99a9b0d6f27
Author: justinwwhuang <hww_jus...@163.com>
AuthorDate: Fri Oct 27 21:32:11 2023 +0800

    [INLONG-9136][Agent] Add instance manager (#9142)
---
 .../org/apache/inlong/agent/plugin/Instance.java   |  56 ++++
 .../agent/core/instance/InstanceManager.java       | 350 +++++++++++++++++++++
 .../inlong/agent/core/instance/MockInstance.java   |  78 +++++
 .../agent/core/instance/TestInstanceManager.java   | 102 ++++++
 4 files changed, 586 insertions(+)

diff --git 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/plugin/Instance.java
 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/plugin/Instance.java
new file mode 100755
index 0000000000..90bac4c94f
--- /dev/null
+++ 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/plugin/Instance.java
@@ -0,0 +1,56 @@
+/*
+ * 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.inlong.agent.plugin;
+
+import org.apache.inlong.agent.conf.InstanceProfile;
+import org.apache.inlong.agent.state.AbstractStateWrapper;
+
+import java.io.IOException;
+
+/**
+ * Instance interface, which generated by task in condition.
+ */
+public abstract class Instance extends AbstractStateWrapper {
+
+    /**
+     * init instance by instance profile
+     *
+     * @throws IOException
+     */
+    public abstract void init(Object instanceManager, InstanceProfile profile);
+
+    /**
+     * destroy instance.
+     */
+    public abstract void destroy();
+
+    /**
+     * get instance profile
+     */
+    public abstract InstanceProfile getProfile();
+
+    /**
+     * get task id
+     */
+    public abstract String getTaskId();
+
+    /**
+     * get instance id
+     */
+    public abstract String getInstanceId();
+}
diff --git 
a/inlong-agent/agent-core/src/main/java/org/apache/inlong/agent/core/instance/InstanceManager.java
 
b/inlong-agent/agent-core/src/main/java/org/apache/inlong/agent/core/instance/InstanceManager.java
new file mode 100644
index 0000000000..a6c8381de3
--- /dev/null
+++ 
b/inlong-agent/agent-core/src/main/java/org/apache/inlong/agent/core/instance/InstanceManager.java
@@ -0,0 +1,350 @@
+/*
+ * 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.inlong.agent.core.instance;
+
+import org.apache.inlong.agent.common.AbstractDaemon;
+import org.apache.inlong.agent.common.AgentThreadFactory;
+import org.apache.inlong.agent.conf.AgentConfiguration;
+import org.apache.inlong.agent.conf.InstanceProfile;
+import org.apache.inlong.agent.constant.AgentConstants;
+import org.apache.inlong.agent.db.Db;
+import org.apache.inlong.agent.db.InstanceDb;
+import org.apache.inlong.agent.plugin.Instance;
+import org.apache.inlong.agent.utils.AgentUtils;
+import org.apache.inlong.agent.utils.ThreadUtils;
+import org.apache.inlong.common.enums.InstanceStateEnum;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * handle the instance created by task, including add, delete, update etc.
+ * the instance info is store in both db and memory.
+ */
+public class InstanceManager extends AbstractDaemon {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(InstanceManager.class);
+    private static final int ACTION_QUEUE_CAPACITY = 100000;
+    public static final int CORE_THREAD_SLEEP_TIME = 100;
+    // task in db
+    private final InstanceDb instanceDb;
+    // task in memory
+    private final ConcurrentHashMap<String, Instance> instanceMap;
+    // instance profile queue.
+    private final BlockingQueue<InstanceAction> actionQueue;
+    // task thread pool;
+    private static final ThreadPoolExecutor EXECUTOR_SERVICE = new 
ThreadPoolExecutor(
+            0, Integer.MAX_VALUE,
+            1L, TimeUnit.SECONDS,
+            new SynchronousQueue<>(),
+            new AgentThreadFactory("instance-manager"));
+
+    private final int taskMaxLimit;
+    private final AgentConfiguration agentConf;
+    private final String taskId;
+    private volatile boolean runAtLeastOneTime = false;
+    private volatile boolean running = false;
+
+    /**
+     * Init task manager.
+     */
+    public InstanceManager(String taskId, Db basicDb) {
+        this.taskId = taskId;
+        instanceDb = new InstanceDb(basicDb);
+        this.agentConf = AgentConfiguration.getAgentConf();
+        instanceMap = new ConcurrentHashMap<>();
+        taskMaxLimit = agentConf.getInt(AgentConstants.JOB_NUMBER_LIMIT, 
AgentConstants.DEFAULT_JOB_NUMBER_LIMIT);
+        actionQueue = new LinkedBlockingQueue<>(ACTION_QUEUE_CAPACITY);
+    }
+
+    public String getTaskId() {
+        return taskId;
+    }
+
+    public Instance getInstance(String instanceId) {
+        return instanceMap.get(instanceId);
+    }
+
+    public InstanceProfile getInstanceProfile(String instanceId) {
+        return instanceDb.getInstance(taskId, instanceId);
+    }
+
+    public boolean submitAction(InstanceAction action) {
+        if (action == null) {
+            return false;
+        }
+        return actionQueue.offer(action);
+    }
+
+    /**
+     * thread for core thread.
+     *
+     * @return runnable profile.
+     */
+    private Runnable coreThread() {
+        return () -> {
+            Thread.currentThread().setName("instance-manager-core-" + taskId);
+            running = true;
+            while (isRunnable()) {
+                try {
+                    AgentUtils.silenceSleepInMs(CORE_THREAD_SLEEP_TIME);
+                    dealWithActionQueue(actionQueue);
+                    keepPaceWithDb();
+                } catch (Throwable ex) {
+                    LOGGER.error("coreThread {}", ex.getMessage());
+                    ThreadUtils.threadThrowableHandler(Thread.currentThread(), 
ex);
+                }
+                runAtLeastOneTime = true;
+            }
+            running = false;
+        };
+    }
+
+    private void keepPaceWithDb() {
+        traverseDbTasksToMemory();
+        traverseMemoryTasksToDb();
+    }
+
+    private void traverseDbTasksToMemory() {
+        instanceDb.getInstances(taskId).forEach((profileFromDb) -> {
+            InstanceStateEnum dbState = profileFromDb.getState();
+            Instance task = instanceMap.get(profileFromDb.getInstanceId());
+            switch (dbState) {
+                case DEFAULT: {
+                    if (task == null) {
+                        LOGGER.info("traverseDbTasksToMemory add instance to 
mem taskId {} instanceId {}",
+                                profileFromDb.getTaskId(), 
profileFromDb.getInstanceId());
+                        addToMemory(profileFromDb);
+                    }
+                    break;
+                }
+                case FINISHED:
+                    DELETE: {
+                        if (task != null) {
+                            LOGGER.info("traverseDbTasksToMemory delete 
instance from mem taskId {} instanceId {}",
+                                    profileFromDb.getTaskId(), 
profileFromDb.getInstanceId());
+                            deleteFromMemory(profileFromDb.getInstanceId());
+                        }
+                        break;
+                    }
+                default: {
+                    LOGGER.error("instance invalid state {} taskId {} 
instanceId {}", dbState,
+                            profileFromDb.getTaskId(),
+                            profileFromDb.getInstanceId());
+                }
+            }
+        });
+    }
+
+    private void traverseMemoryTasksToDb() {
+        instanceMap.values().forEach((instance) -> {
+            InstanceProfile profileFromDb = 
instanceDb.getInstance(instance.getTaskId(), instance.getInstanceId());
+            if (profileFromDb == null) {
+                deleteFromMemory(instance.getInstanceId());
+                return;
+            }
+            InstanceStateEnum stateFromDb = profileFromDb.getState();
+            if (stateFromDb != InstanceStateEnum.DEFAULT) {
+                deleteFromMemory(instance.getInstanceId());
+            }
+        });
+    }
+
+    private void dealWithActionQueue(BlockingQueue<InstanceAction> queue) {
+        while (isRunnable()) {
+            try {
+                InstanceAction action = queue.poll();
+                if (action == null) {
+                    break;
+                }
+                switch (action.getActionType()) {
+                    case ADD:
+                        addInstance(action.getProfile());
+                        break;
+                    case FINISH:
+                        finishInstance(action.getProfile());
+                        break;
+                    case DELETE:
+                        deleteInstance(action.getProfile().getInstanceId());
+                        break;
+                    default:
+                        LOGGER.error("invalid action type for instance 
manager: taskId {} type {}", taskId,
+                                action.getActionType());
+                }
+            } catch (Throwable ex) {
+                LOGGER.error("dealWithActionQueue", ex);
+                ThreadUtils.threadThrowableHandler(Thread.currentThread(), ex);
+            }
+        }
+    }
+
+    @Override
+    public void start() {
+        restoreFromDb();
+        submitWorker(coreThread());
+    }
+
+    @Override
+    public void stop() {
+        waitForTerminate();
+        stopAllInstances();
+    }
+
+    public void waitForTerminate() {
+        super.waitForTerminate();
+        while (running) {
+            AgentUtils.silenceSleepInMs(CORE_THREAD_SLEEP_TIME);
+        }
+    }
+
+    private void restoreFromDb() {
+        List<InstanceProfile> profileList = instanceDb.getInstances(taskId);
+        profileList.forEach((profile) -> {
+            InstanceStateEnum state = profile.getState();
+            if (state == InstanceStateEnum.DEFAULT) {
+                LOGGER.info("instance restoreFromDb addToMem state {} taskId 
{} instanceId {}", state, taskId,
+                        profile.getInstanceId());
+                addToMemory(profile);
+            } else {
+                LOGGER.info("instance restoreFromDb ignore state {} taskId {} 
instanceId {}", state, taskId,
+                        profile.getInstanceId());
+            }
+        });
+    }
+
+    private void addInstance(InstanceProfile profile) {
+        LOGGER.info("addInstance taskId {} instanceId {}", taskId, 
profile.getInstanceId());
+        addToDb(profile);
+        addToMemory(profile);
+    }
+
+    private void finishInstance(InstanceProfile profile) {
+        profile.setState(InstanceStateEnum.FINISHED);
+        profile.setModifyTime(AgentUtils.getCurrentTime());
+        addToDb(profile);
+        deleteFromMemory(profile.getInstanceId());
+        LOGGER.info("finished instance state {} taskId {} instanceId {}", 
profile.getState(),
+                profile.getTaskId(), profile.getInstanceId());
+    }
+
+    private void deleteInstance(String instanceId) {
+        deleteFromDb(instanceId);
+        deleteFromMemory(instanceId);
+    }
+
+    private void deleteFromDb(String instanceId) {
+        instanceDb.deleteInstance(taskId, instanceId);
+        LOGGER.info("delete instance from db: taskId {} instanceId {} result 
{}", taskId,
+                instanceId, instanceDb.getInstance(taskId, instanceId));
+    }
+
+    private void deleteFromMemory(String instanceId) {
+        Instance instance = instanceMap.get(instanceId);
+        if (instance == null) {
+            LOGGER.error("try to delete instance from memory but not found: 
taskId {} instanceId {}", taskId,
+                    instanceId);
+            return;
+        }
+        instance.destroy();
+        instanceMap.remove(instanceId);
+        LOGGER.info("delete instance from memory: taskId {} instanceId {}", 
taskId, instance.getInstanceId());
+    }
+
+    private void addToDb(InstanceProfile profile) {
+        LOGGER.info("add instance to db instanceId {} ", 
profile.getInstanceId());
+        instanceDb.storeInstance(profile);
+    }
+
+    /**
+     * add instance to memory, if there is a record refer to the instance id 
exist we need to destroy it first.
+     */
+    private void addToMemory(InstanceProfile instanceProfile) {
+        Instance oldInstance = 
instanceMap.get(instanceProfile.getInstanceId());
+        if (oldInstance != null) {
+            oldInstance.destroy();
+            instanceMap.remove(instanceProfile.getInstanceId());
+            LOGGER.error("old instance {} should not exist, try stop it first",
+                    instanceProfile);
+        }
+        LOGGER.info("instanceProfile {}", instanceProfile.toJsonStr());
+        try {
+            Class<?> taskClass = 
Class.forName(instanceProfile.getInstanceClass());
+            Instance instance = (Instance) taskClass.newInstance();
+            instance.init(this, instanceProfile);
+            instanceMap.put(instanceProfile.getInstanceId(), instance);
+            EXECUTOR_SERVICE.submit(instance);
+            LOGGER.info(
+                    "add instance to memory instanceId {} instanceMap size {}, 
runningPool instance total {}, runningPool instance active {}",
+                    instance.getInstanceId(), instanceMap.size(), 
EXECUTOR_SERVICE.getTaskCount(),
+                    EXECUTOR_SERVICE.getActiveCount());
+        } catch (Throwable t) {
+            LOGGER.error("add instance error {}", t.getMessage());
+        }
+    }
+
+    private void stopAllInstances() {
+        instanceMap.values().forEach((instance) -> {
+            deleteInstance(instance.getInstanceId());
+        });
+        instanceMap.clear();
+    }
+
+    public boolean shouldAddAgain(String fileName, long lastModifyTime) {
+        InstanceProfile profileFromDb = instanceDb.getInstance(taskId, 
fileName);
+        if (profileFromDb == null) {
+            return true;
+        } else {
+            InstanceStateEnum state = profileFromDb.getState();
+            if (state == InstanceStateEnum.FINISHED && lastModifyTime > 
profileFromDb.getModifyTime()) {
+                return true;
+            }
+            if (state == InstanceStateEnum.DELETE) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public boolean allInstanceFinished() {
+        if (!runAtLeastOneTime) {
+            return false;
+        }
+        if (!instanceMap.isEmpty()) {
+            return false;
+        }
+        if (!actionQueue.isEmpty()) {
+            return false;
+        }
+        List<InstanceProfile> instances = instanceDb.getInstances(taskId);
+        for (int i = 0; i < instances.size(); i++) {
+            InstanceProfile profile = instances.get(i);
+            if (profile.getState() != InstanceStateEnum.FINISHED) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git 
a/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/MockInstance.java
 
b/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/MockInstance.java
new file mode 100644
index 0000000000..ada8cefcdd
--- /dev/null
+++ 
b/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/MockInstance.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.inlong.agent.core.instance;
+
+import org.apache.inlong.agent.conf.InstanceProfile;
+import org.apache.inlong.agent.plugin.Instance;
+
+public class MockInstance extends Instance {
+
+    public static final int INIT_TIME = 100;
+    public static final int RUN_TIME = 101;
+    public static final int DESTROY_TIME = 102;
+    private InstanceProfile profile;
+    private long index = INIT_TIME;
+    public long initTime = 0;
+    public long destroyTime = 0;
+    public long runtime = 0;
+    private InstanceManager instanceManager;
+
+    @Override
+    public void init(Object instanceManager, InstanceProfile profile) {
+        this.instanceManager = (InstanceManager) instanceManager;
+        this.profile = profile;
+        initTime = index++;
+    }
+
+    @Override
+    public void destroy() {
+        destroyTime = index++;
+    }
+
+    @Override
+    public InstanceProfile getProfile() {
+        return profile;
+    }
+
+    @Override
+    public String getTaskId() {
+        return profile.getTaskId();
+    }
+
+    @Override
+    public String getInstanceId() {
+        return profile.getInstanceId();
+    }
+
+    @Override
+    public void addCallbacks() {
+
+    }
+
+    @Override
+    public void run() {
+        runtime = index++;
+    }
+
+    public void sendFinishAction() {
+        InstanceAction action = new InstanceAction();
+        action.setActionType(ActionType.FINISH);
+        action.setProfile(profile);
+        instanceManager.submitAction(action);
+    }
+}
\ No newline at end of file
diff --git 
a/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/TestInstanceManager.java
 
b/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/TestInstanceManager.java
new file mode 100755
index 0000000000..62bfa85d8a
--- /dev/null
+++ 
b/inlong-agent/agent-core/src/test/java/org/apache/inlong/agent/core/instance/TestInstanceManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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.inlong.agent.core.instance;
+
+import org.apache.inlong.agent.conf.InstanceProfile;
+import org.apache.inlong.agent.conf.TaskProfile;
+import org.apache.inlong.agent.core.AgentBaseTestsHelper;
+import org.apache.inlong.agent.core.task.file.TaskManager;
+import org.apache.inlong.agent.db.Db;
+import org.apache.inlong.agent.utils.AgentUtils;
+import org.apache.inlong.common.enums.InstanceStateEnum;
+import org.apache.inlong.common.enums.TaskStateEnum;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+
+public class TestInstanceManager {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(TestInstanceManager.class);
+    private static InstanceManager manager;
+    private static AgentBaseTestsHelper helper;
+    private static TaskProfile taskProfile;
+
+    @BeforeClass
+    public static void setup() {
+        helper = new 
AgentBaseTestsHelper(TestInstanceManager.class.getName()).setupAgentHome();
+        String pattern = helper.getTestRootDir() + "/YYYYMMDD.log_[0-9]+";
+        Db basicDb = TaskManager.initDb("/localdb");
+        taskProfile = helper.getTaskProfile(1, pattern, false, 0L, 0L, 
TaskStateEnum.RUNNING);
+        manager = new InstanceManager("1", basicDb);
+        manager.start();
+    }
+
+    @AfterClass
+    public static void teardown() {
+        manager.stop();
+        helper.teardownAgentHome();
+    }
+
+    @Test
+    public void testInstanceManager() {
+        long timeBefore = AgentUtils.getCurrentTime();
+        InstanceProfile profile = 
taskProfile.createInstanceProfile(MockInstance.class.getCanonicalName(),
+                helper.getTestRootDir() + "/20230927.log_1", "20230927");
+        String instanceId = profile.getInstanceId();
+        InstanceAction action = new InstanceAction();
+        action.setActionType(ActionType.ADD);
+        action.setProfile(profile);
+        // test add action
+        manager.submitAction(action);
+        await().atMost(1, TimeUnit.SECONDS).until(() -> 
manager.getInstance(instanceId) != null);
+        Assert.assertTrue(manager.getInstanceProfile(instanceId).getState() == 
InstanceStateEnum.DEFAULT);
+
+        // test finish action
+        MockInstance instance = (MockInstance) 
manager.getInstance(profile.getInstanceId());
+        instance.sendFinishAction();
+        await().atMost(1, TimeUnit.SECONDS).until(() -> 
manager.getInstance(instanceId) == null);
+        Assert.assertTrue(manager.getInstanceProfile(instanceId).getState() == 
InstanceStateEnum.FINISHED);
+        // test modify before finish
+        Assert.assertFalse(manager.shouldAddAgain(profile.getInstanceId(), 
timeBefore));
+        // test modify after finish
+        Assert.assertTrue(manager.shouldAddAgain(profile.getInstanceId(), 
AgentUtils.getCurrentTime()));
+
+        // test continue
+        action.setActionType(ActionType.ADD);
+        profile.setState(InstanceStateEnum.DEFAULT);
+        manager.submitAction(action);
+        await().atMost(1, TimeUnit.SECONDS).until(() -> 
manager.getInstance(instanceId) != null);
+        Assert.assertTrue(manager.getInstanceProfile(instanceId).getState() == 
InstanceStateEnum.DEFAULT);
+
+        // test delete action
+        action.setActionType(ActionType.DELETE);
+        manager.submitAction(action);
+        await().atMost(1, TimeUnit.SECONDS).until(() -> 
manager.getInstanceProfile(instanceId) == null);
+        Assert.assertTrue(instance.initTime == MockInstance.INIT_TIME);
+        Assert.assertTrue(instance.runtime == MockInstance.RUN_TIME);
+        Assert.assertTrue(instance.destroyTime == MockInstance.DESTROY_TIME);
+    }
+}

Reply via email to