This is an automated email from the ASF dual-hosted git repository.
dimuthuupe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git
The following commit(s) were added to refs/heads/master by this push:
new 99432b15 Initial commit for distributed workload manager
99432b15 is described below
commit 99432b1503304404b5bff18b3ae944f05fd6b4bb
Author: DImuthuUpe <[email protected]>
AuthorDate: Fri Nov 28 09:52:34 2025 -0500
Initial commit for distributed workload manager
---
distributed-workqueue/work-client/README.md | 21 ++
distributed-workqueue/work-client/go.mod | 3 +
distributed-workqueue/work-client/main.go | 90 +++++++
distributed-workqueue/work-orchestrator/.gitignore | 33 +++
distributed-workqueue/work-orchestrator/mvnw | 295 +++++++++++++++++++++
distributed-workqueue/work-orchestrator/mvnw.cmd | 189 +++++++++++++
.../work/orchestrator/WorkOrchestratorMain.java | 13 +
.../controller/WorkloadController.java | 80 ++++++
.../work/orchestrator/core/RegistryValidator.java | 133 ++++++++++
.../airavata/work/orchestrator/model/WorkItem.java | 108 ++++++++
.../work/orchestrator/model/WorkItemStatus.java | 8 +
.../airavata/work/orchestrator/model/Workload.java | 69 +++++
.../work/orchestrator/model/WorkloadStatus.java | 8 +
.../repository/WorkItemRepository.java | 11 +
.../repository/WorkloadRepository.java | 20 ++
.../work/orchestrator/service/WorkloadService.java | 93 +++++++
.../src/main/resources/application.properties | 1 +
.../orchestrator/WorkOrchestratorMainTests.java | 13 +
18 files changed, 1188 insertions(+)
diff --git a/distributed-workqueue/work-client/README.md
b/distributed-workqueue/work-client/README.md
new file mode 100644
index 00000000..6a7ecd78
--- /dev/null
+++ b/distributed-workqueue/work-client/README.md
@@ -0,0 +1,21 @@
+# Development build
+go run main.go
+
+# Build for your current platform
+go build -o work-client
+
+# Build for specific platforms by setting GOOS and GOARCH
+# Linux (64-bit)
+GOOS=linux GOARCH=amd64 go build -o work-client-linux-amd64
+
+# Windows (64-bit)
+GOOS=windows GOARCH=amd64 go build -o work-client-windows-amd64.exe
+
+# macOS (Intel)
+GOOS=darwin GOARCH=amd64 go build -o work-client-darwin-amd64
+
+# macOS (Apple Silicon)
+GOOS=darwin GOARCH=arm64 go build -o work-client-darwin-arm64
+
+# Linux (ARM64)
+GOOS=linux GOARCH=arm64 go build -o work-client-linux-arm64
\ No newline at end of file
diff --git a/distributed-workqueue/work-client/go.mod
b/distributed-workqueue/work-client/go.mod
new file mode 100644
index 00000000..02ab05df
--- /dev/null
+++ b/distributed-workqueue/work-client/go.mod
@@ -0,0 +1,3 @@
+module work-client
+
+go 1.24.2
diff --git a/distributed-workqueue/work-client/main.go
b/distributed-workqueue/work-client/main.go
new file mode 100644
index 00000000..c5fd4e59
--- /dev/null
+++ b/distributed-workqueue/work-client/main.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+)
+
+// Response represents the workload lease API response
+type Response struct {
+ AiravataExperimentID string `json:"airavataExperimentId"`
+ AiravataExperimentStatus *string `json:"airavataExperimentStatus"`
+ ID string `json:"id"`
+ LastUpdated int64 `json:"lastUpdated"`
+ Status string `json:"status"`
+ WorkConfig string `json:"workConfig"`
+ WorkloadID string `json:"workloadId"`
+}
+
+func main() {
+ // Read experiment ID from environment variable or use default
+ experimentId := os.Getenv("EXPERIMENT_ID")
+ if experimentId == "" {
+ experimentId = "exp001"
+ }
+
+ // Read workload ID from environment variable or use default
+ workloadId := os.Getenv("WORKLOAD_ID")
+ if workloadId == "" {
+ workloadId = "ff8c561f-aa49-4920-b324-c30fe0610610"
+ }
+
+ // Read base URL from environment variable or use default
+ baseURL := os.Getenv("BASE_URL")
+ if baseURL == "" {
+ baseURL = "http://localhost:8080"
+ }
+
+ apiURL := baseURL + "/api/workload/lease/" + workloadId + "/" +
experimentId
+
+ // Make the HTTP GET request
+ resp, err := http.Get(apiURL)
+ if err != nil {
+ log.Fatalf("Error making request: %v", err)
+ }
+ defer resp.Body.Close()
+
+ // Check if the request was successful
+ if resp.StatusCode == http.StatusNoContent {
+ log.Fatalf("Error: Resource not found (204 No Content)")
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ log.Fatalf("Request failed with status: %s", resp.Status)
+ }
+
+ // Read the response body
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ log.Fatalf("Error reading response: %v", err)
+ }
+
+ // Print raw JSON response
+ fmt.Println("Raw JSON Response:")
+ fmt.Println(string(body))
+ fmt.Println()
+
+ // Parse JSON into struct
+ var data Response
+ if err := json.Unmarshal(body, &data); err != nil {
+ log.Fatalf("Error parsing JSON: %v", err)
+ }
+
+ // Print parsed data
+ fmt.Println("Parsed Data:")
+ fmt.Printf("ID: %s\n", data.ID)
+ fmt.Printf("Workload ID: %s\n", data.WorkloadID)
+ fmt.Printf("Airavata Experiment ID: %s\n", data.AiravataExperimentID)
+ fmt.Printf("Status: %s\n", data.Status)
+ fmt.Printf("Work Config: %s\n", data.WorkConfig)
+ fmt.Printf("Last Updated: %d\n", data.LastUpdated)
+ if data.AiravataExperimentStatus != nil {
+ fmt.Printf("Airavata Experiment Status: %s\n",
*data.AiravataExperimentStatus)
+ } else {
+ fmt.Println("Airavata Experiment Status: null")
+ }
+}
diff --git a/distributed-workqueue/work-orchestrator/.gitignore
b/distributed-workqueue/work-orchestrator/.gitignore
new file mode 100644
index 00000000..667aaef0
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/distributed-workqueue/work-orchestrator/mvnw
b/distributed-workqueue/work-orchestrator/mvnw
new file mode 100755
index 00000000..bd8896bf
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.4
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via
java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script;
others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is
touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so
mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\"
or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME
set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and
linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in
more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires
.mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in
$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use
pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern:
~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] ||
distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string
"$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec
$MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or
maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q
__MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL=''
__MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD=''
;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O
"$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch
$distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o
"$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl:
Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv(
"MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0]
).toURL().openStream(), java.nio.file.Paths.get( args[1]
).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and
java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed
to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")"
Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from
your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" |
sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" |
shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or
'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing
'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven
distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified
distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"}
"$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed
to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"}
"$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed
to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename
!= directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for
snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl"
>"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d
"$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/distributed-workqueue/work-orchestrator/mvnw.cmd
b/distributed-workqueue/work-orchestrator/mvnw.cmd
new file mode 100644
index 00000000..92450f93
--- /dev/null
+++ b/distributed-workqueue/work-orchestrator/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM
----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
----------------------------------------------------------------------------
+
+@REM
----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM
----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "&
{$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock
([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE
(echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw
"$scriptDir/.mvn/wrapper/maven-wrapper.properties" |
ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in
$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace
'-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern:
~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else
{ "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl
-replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$',''
-replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME =
([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl)
| ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq
$distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but
found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path
"$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object
System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl,
"$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw
"$scriptDir/.mvn/wrapper/maven-wrapper.properties" |
ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease
disable validation by removing 'distributionSha256Sum' from your
maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function
Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm
SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your
Maven distribution might be compromised. If you updated your Maven version, you
need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath
"$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename
!= directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path
$expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for
snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted
archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory:
$actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName
$MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination
$MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
new file mode 100644
index 00000000..4bc5e831
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMain.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.work.orchestrator;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class WorkOrchestratorMain {
+
+ public static void main(String[] args) {
+ SpringApplication.run(WorkOrchestratorMain.class, args);
+ }
+
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
new file mode 100644
index 00000000..5ed3e2a0
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/controller/WorkloadController.java
@@ -0,0 +1,80 @@
+package org.apache.airavata.work.orchestrator.controller;
+
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.service.WorkloadService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/workload")
+public class WorkloadController {
+
+ @Autowired
+ private WorkloadService workloadService;
+
+ @PostMapping("/workitem")
+ public ResponseEntity<WorkItem> createWorkItem(@RequestBody WorkItem
workItem) {
+ WorkItem createdItem = workloadService.createWorkItem(workItem);
+ return new ResponseEntity<>(createdItem, HttpStatus.CREATED);
+ }
+
+ @GetMapping("/workitem/{id}")
+ public ResponseEntity<WorkItem> getWorkItem(@PathVariable String id) {
+ Optional<WorkItem> item = workloadService.getWorkItem(id);
+ return ResponseEntity.ok(item.orElse(null));
+ }
+
+ @GetMapping("/workitem")
+ public ResponseEntity<?> getAllWorkItems() {
+ return ResponseEntity.ok(workloadService.getAllWorkItems());
+ }
+
+ @DeleteMapping("/workitem/{id}")
+ public ResponseEntity<Void> deleteWorkItem(@PathVariable String id) {
+ workloadService.deleteWorkItem(id);
+ return ResponseEntity.noContent().build();
+ }
+
+ @PostMapping
+ public ResponseEntity<Workload> createWorkload(@RequestBody Workload
workload) {
+ Workload createdWorkload = workloadService.createWorkload(workload);
+ return new ResponseEntity<>(createdWorkload, HttpStatus.CREATED);
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity<Workload> getWorkload(@PathVariable String id) {
+ Optional<Workload> workload = workloadService.getWorkload(id);
+ return ResponseEntity.ok(workload.orElse(null));
+ }
+
+ @GetMapping
+ public ResponseEntity<Iterable<Workload>> getAllWorkloads() {
+ return ResponseEntity.ok(workloadService.getAllWorkloads());
+ }
+
+ @DeleteMapping("/{id}")
+ public ResponseEntity<Void> deleteWorkload(@PathVariable String id) {
+ workloadService.deleteWorkload(id);
+ return ResponseEntity.noContent().build();
+ }
+ @GetMapping("/lease/{workloadId}/{airavataExperimentId}")
+ public ResponseEntity<WorkItem> leaseWorkItem(@PathVariable String
workloadId, @PathVariable String airavataExperimentId) {
+ Optional<WorkItem> leasedItem =
workloadService.leaseWorkItem(workloadId, airavataExperimentId);
+ return leasedItem.map(item -> ResponseEntity.ok(item))
+ .orElseGet(() ->
ResponseEntity.status(HttpStatus.NO_CONTENT).build());
+ }
+
+ @PutMapping("/workitem/{workItemId}/status")
+ public ResponseEntity<WorkItem> updateWorkItemStatus(@PathVariable String
workItemId, @RequestParam("status") String status) {
+ Optional<WorkItem> updatedItem =
workloadService.updateWorkItemStatus(workItemId,
Enum.valueOf(WorkItemStatus.class, status));
+ return updatedItem.map(item -> ResponseEntity.ok(item))
+ .orElseGet(() ->
ResponseEntity.status(HttpStatus.NOT_FOUND).build());
+ }
+
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
new file mode 100644
index 00000000..9df0f75a
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/core/RegistryValidator.java
@@ -0,0 +1,133 @@
+package org.apache.airavata.work.orchestrator.core;
+
+import org.apache.airavata.common.exception.AiravataException;
+import org.apache.airavata.common.utils.AiravataUtils;
+import org.apache.airavata.common.utils.ServerSettings;
+import org.apache.airavata.common.utils.ThriftClientPool;
+import org.apache.airavata.messaging.core.*;
+import org.apache.airavata.messaging.core.impl.RabbitMQPublisher;
+import
org.apache.airavata.model.appcatalog.appinterface.ApplicationInterfaceDescription;
+import
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupComputeResourcePreference;
+import
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupResourceProfile;
+import org.apache.airavata.model.experiment.ExperimentCleanupStrategy;
+import org.apache.airavata.model.experiment.ExperimentModel;
+import org.apache.airavata.model.experiment.ExperimentType;
+import org.apache.airavata.model.experiment.UserConfigurationDataModel;
+import org.apache.airavata.model.messaging.event.ExperimentSubmitEvent;
+import org.apache.airavata.model.messaging.event.MessageType;
+import
org.apache.airavata.model.scheduling.ComputationalResourceSchedulingModel;
+import org.apache.airavata.model.workspace.Gateway;
+import org.apache.airavata.registry.api.RegistryService;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.apache.thrift.TException;
+
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public class RegistryValidator {
+ private static ThriftClientPool<RegistryService.Client> registryClientPool;
+ private static Publisher experimentPublisher;
+
+ private static <T> GenericObjectPoolConfig<T>
createGenericObjectPoolConfig() {
+
+ GenericObjectPoolConfig<T> poolConfig = new
GenericObjectPoolConfig<T>();
+ poolConfig.setMaxTotal(100);
+ poolConfig.setMinIdle(5);
+ poolConfig.setBlockWhenExhausted(true);
+ poolConfig.setTestOnBorrow(true);
+ poolConfig.setTestWhileIdle(true);
+ // must set timeBetweenEvictionRunsMillis since eviction doesn't run
unless that is positive
+ poolConfig.setTimeBetweenEvictionRunsMillis(5L * 60L * 1000L);
+ poolConfig.setNumTestsPerEvictionRun(10);
+ poolConfig.setMaxWaitMillis(3000);
+ return poolConfig;
+ }
+
+ private static RabbitMQProperties getProperties() {
+ return (new RabbitMQProperties())
+ .setBrokerUrl("amqp://guest:guest@localhost:5672/develop")
+ .setDurable(false)
+ .setPrefetchCount(200)
+ .setAutoRecoveryEnable(true)
+ .setConsumerTag("default")
+ .setExchangeName("experiment_exchange")
+ .setExchangeType(RabbitMQProperties.EXCHANGE_TYPE.TOPIC);
+ }
+
+ public static void main(String args[]) throws TException,
AiravataException {
+ registryClientPool = new ThriftClientPool<>(
+ tProtocol -> new RegistryService.Client(tProtocol),
+ createGenericObjectPoolConfig(),
+ "localhost",8970);
+
+ RabbitMQProperties rProperties = getProperties();
+ experimentPublisher = new RabbitMQPublisher(rProperties,
(messageContext) -> rProperties.getExchangeName());
+
+ RegistryService.Client resource = registryClientPool.getResource();
+ List<Gateway> allGateways = resource.getAllGateways();
+ String expName = "VizfoldRepr-" + UUID.randomUUID().toString();
+ ExperimentModel experimentModel = new ExperimentModel();
+
+ experimentModel.setExperimentName(expName);
+
experimentModel.setProjectId("Default_Project_93d9a30a-4299-44dd-9e48-a7b8961464dc");
+ experimentModel.setUserName("default-admin");
+ experimentModel.setGatewayId("default");
+
experimentModel.setExecutionId("VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9");
+
+ String groupResourceProfileId =
"02881ab4-51c6-462d-a4dd-1b3a0d0fa921"; // Validate the access
+
+ ComputationalResourceSchedulingModel
computationalResourceSchedulingModel =
+ new ComputationalResourceSchedulingModel();
+
+ List<GroupResourceProfile> groupResourceList =
resource.getGroupResourceList("default",
Collections.singletonList(groupResourceProfileId));
+ GroupResourceProfile grp = groupResourceList.get(0);
+ Optional<GroupComputeResourcePreference> grpCrp =
grp.getComputePreferences().stream().filter(pre ->
pre.getComputeResourceId().equals("NCSADelta_e75b0d04-8b4b-417b-8ab4-da76bbd835f5")).findFirst();
+ GroupComputeResourcePreference groupCompResourcePref = grpCrp.get();
+
+ computationalResourceSchedulingModel.setQueueName("gpuA100x4");
+ computationalResourceSchedulingModel.setNodeCount(1);
+ computationalResourceSchedulingModel.setTotalCPUCount(8);
+ computationalResourceSchedulingModel.setWallTimeLimit(10);
+ computationalResourceSchedulingModel.setTotalPhysicalMemory(0);
+
computationalResourceSchedulingModel.setResourceHostId(groupCompResourcePref.getComputeResourceId());
+
computationalResourceSchedulingModel.setOverrideScratchLocation(groupCompResourcePref.getScratchLocation());
+
computationalResourceSchedulingModel.setOverrideAllocationProjectNumber("bbol-delta-gpu");
+
computationalResourceSchedulingModel.setOverrideLoginUserName(groupCompResourcePref.getLoginUserName());
+
+ UserConfigurationDataModel userConfigurationDataModel = new
UserConfigurationDataModel();
+
userConfigurationDataModel.setComputationalResourceScheduling(computationalResourceSchedulingModel);
+ userConfigurationDataModel.setAiravataAutoSchedule(false);
+ userConfigurationDataModel.setOverrideManualScheduledParams(false);
+
userConfigurationDataModel.setInputStorageResourceId("default_9c15d8af-3d36-4c3c-a07a-0f3b4bb5b903");
+
userConfigurationDataModel.setOutputStorageResourceId("vizfold_9c15d8af-3d36-4c3c-a07a-0f3b4bb5b904");
+ String experimentDataDir = "/reprs/" + expName;
+ userConfigurationDataModel.setExperimentDataDir(experimentDataDir);
+
userConfigurationDataModel.setGroupResourceProfileId(groupCompResourcePref.getGroupResourceProfileId());
+
+ experimentModel.setUserConfigurationData(userConfigurationDataModel);
+
+ String appInterfaceId =
"VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9";
+
+ ApplicationInterfaceDescription applicationInterface =
resource.getApplicationInterface("VizfoldRepresentation_c2c025a7-36f7-4c7d-b7b2-970f02de40f9");
+
+
experimentModel.setExperimentInputs(applicationInterface.getApplicationInputs());
+
experimentModel.setExperimentOutputs(applicationInterface.getApplicationOutputs());
+
+ experimentModel.setExperimentType(ExperimentType.SINGLE_APPLICATION);
+
experimentModel.setCleanUpStrategy(ExperimentCleanupStrategy.ONLY_COMPLETED);
+
+ String experimentId = resource.createExperiment("default",
experimentModel);
+
+
+ ExperimentSubmitEvent event = new ExperimentSubmitEvent(experimentId,
"default");
+ MessageContext messageContext = new MessageContext(
+ event, MessageType.EXPERIMENT, "LAUNCH.EXP-" +
UUID.randomUUID().toString(), "default");
+ messageContext.setUpdatedTime(AiravataUtils.getCurrentTimestamp());
+
+ experimentPublisher.publish(messageContext);
+ }
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
new file mode 100644
index 00000000..4a191ffc
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItem.java
@@ -0,0 +1,108 @@
+package org.apache.airavata.work.orchestrator.model;
+
+import org.hibernate.annotations.UuidGenerator;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "work_item")
+public class WorkItem {
+
+ @Id
+ @GeneratedValue
+ @UuidGenerator
+ @Column(nullable = false, updatable = false, length = 48)
+ private String id;
+
+ @Column(name = "status")
+ private WorkItemStatus status;
+
+ @Column(name = "last_updated")
+ private long lastUpdated;
+
+ @Column(name = "work_config")
+ private String workConfig;
+
+ @Column(name = "airavata_experiment_id")
+ private String airavataExperimentId;
+
+ @Column(name = "airavata_experiment_status")
+ private String airavataExperimentStatus;
+
+ @JsonIgnore
+ @ManyToOne
+ @JoinColumn(name = "workload_id")
+ private Workload workload;
+
+ @Column(name = "workload_id", insertable = false, updatable = false)
+ private String workloadId;
+
+ public String getId() {
+ return id;
+ }
+
+ public WorkItemStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(WorkItemStatus status) {
+ this.status = status;
+ }
+
+ public long getLastUpdated() {
+ return lastUpdated;
+ }
+
+ public void setLastUpdated(long lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public String getWorkConfig() {
+ return workConfig;
+ }
+
+ public void setWorkConfig(String workConfig) {
+ this.workConfig = workConfig;
+ }
+
+ public String getAiravataExperimentId() {
+ return airavataExperimentId;
+ }
+
+ public void setAiravataExperimentId(String airavataExperimentId) {
+ this.airavataExperimentId = airavataExperimentId;
+ }
+
+ public String getAiravataExperimentStatus() {
+ return airavataExperimentStatus;
+ }
+
+ public void setAiravataExperimentStatus(String airavataExperimentStatus) {
+ this.airavataExperimentStatus = airavataExperimentStatus;
+ }
+
+ public Workload getWorkload() {
+ return workload;
+ }
+
+ public void setWorkload(Workload workload) {
+ this.workload = workload;
+ }
+
+ public String getWorkloadId() {
+ return workloadId;
+ }
+
+ public void setWorkloadId(String workloadId) {
+ this.workloadId = workloadId;
+ }
+
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
new file mode 100644
index 00000000..68b1083a
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkItemStatus.java
@@ -0,0 +1,8 @@
+package org.apache.airavata.work.orchestrator.model;
+
+public enum WorkItemStatus {
+ NOT_ASSIGNED,
+ IN_PROGRESS,
+ COMPLETED,
+ FAILED
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
new file mode 100644
index 00000000..b5ce7020
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/Workload.java
@@ -0,0 +1,69 @@
+package org.apache.airavata.work.orchestrator.model;
+
+import java.util.List;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "workload")
+public class Workload {
+ @Id
+ @GeneratedValue
+ @Column(nullable = false, updatable = false, length = 48)
+ private String id;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = "status")
+ private WorkloadStatus status;
+
+ @Column(name = "last_updated")
+ private long lastUpdated;
+
+ @OneToMany(mappedBy = "workload", cascade = CascadeType.ALL, fetch =
FetchType.LAZY)
+ private List<WorkItem> workItems;
+
+ public String getId() {
+ return id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List<WorkItem> getWorkItems() {
+ return workItems;
+ }
+
+ public void setWorkItems(List<WorkItem> workItems) {
+ this.workItems = workItems;
+ }
+
+ public WorkloadStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(WorkloadStatus status) {
+ this.status = status;
+ }
+
+ public long getLastUpdated() {
+ return lastUpdated;
+ }
+
+ public void setLastUpdated(long lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
new file mode 100644
index 00000000..dd651058
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/model/WorkloadStatus.java
@@ -0,0 +1,8 @@
+package org.apache.airavata.work.orchestrator.model;
+
+public enum WorkloadStatus {
+ NOT_STARTED,
+ IN_PROGRESS,
+ COMPLETED,
+ FAILED
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
new file mode 100644
index 00000000..8ee8e625
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkItemRepository.java
@@ -0,0 +1,11 @@
+package org.apache.airavata.work.orchestrator.repository;
+
+import java.util.List;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.springframework.data.repository.CrudRepository;
+
+public interface WorkItemRepository extends CrudRepository<WorkItem, String> {
+ public List<WorkItem> findByWorkloadIdAndStatus(String workloadId,
WorkItemStatus status);
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
new file mode 100644
index 00000000..efb87e2a
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/repository/WorkloadRepository.java
@@ -0,0 +1,20 @@
+package org.apache.airavata.work.orchestrator.repository;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.model.WorkloadStatus;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface WorkloadRepository extends CrudRepository<Workload, String> {
+
+ public Optional<Workload> findById(String id);
+
+ public List<Workload> findByStatus(WorkloadStatus status);
+
+ public List<Workload> findAll();
+
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
new file mode 100644
index 00000000..b81c0066
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/java/org/apache/airavata/work/orchestrator/service/WorkloadService.java
@@ -0,0 +1,93 @@
+package org.apache.airavata.work.orchestrator.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.airavata.work.orchestrator.model.WorkItem;
+import org.apache.airavata.work.orchestrator.model.WorkItemStatus;
+import org.apache.airavata.work.orchestrator.model.Workload;
+import org.apache.airavata.work.orchestrator.repository.WorkItemRepository;
+import org.apache.airavata.work.orchestrator.repository.WorkloadRepository;
+import org.springframework.stereotype.Service;
+
+@Service
+public class WorkloadService {
+
+ private WorkItemRepository workItemRepository;
+ private WorkloadRepository workloadRepository;
+
+ public WorkloadService(WorkItemRepository workItemRepository,
WorkloadRepository workloadRepository) {
+ this.workItemRepository = workItemRepository;
+ this.workloadRepository = workloadRepository;
+ }
+
+
+ public Workload createWorkload(Workload workload) {
+ return workloadRepository.save(workload);
+ }
+
+ public Optional<Workload> getWorkload(String id) {
+ return workloadRepository.findById(id);
+ }
+
+ public Iterable<Workload> getAllWorkloads() {
+ return workloadRepository.findAll();
+ }
+
+ public void deleteWorkload(String id) {
+ workloadRepository.deleteById(id);
+ }
+
+ public WorkItem createWorkItem(WorkItem workItem) {
+ // If workloadId is set but workload relationship isn't, fetch and set
it
+ if (workItem.getWorkloadId() != null && workItem.getWorkload() ==
null) {
+ Optional<Workload> workloadOpt =
workloadRepository.findById(workItem.getWorkloadId());
+ if (workloadOpt.isPresent()) {
+ workItem.setWorkload(workloadOpt.get());
+ } else {
+ throw new RuntimeException("Workload not found with id: " +
workItem.getWorkloadId());
+ }
+ }
+ return workItemRepository.save(workItem);
+ }
+
+
+ public Optional<WorkItem> getWorkItem(String id) {
+ return workItemRepository.findById(id);
+ }
+
+ public Iterable<WorkItem> getAllWorkItems() {
+ return workItemRepository.findAll();
+ }
+
+ public void deleteWorkItem(String id) {
+ workItemRepository.deleteById(id);
+ }
+
+ public Optional<WorkItem> leaseWorkItem(String workloadId, String
airavataExperimentId) {
+
+ synchronized(this) { // Make sure only one work item is leased at a
time
+ List<WorkItem> workItemOpt =
workItemRepository.findByWorkloadIdAndStatus(workloadId,
WorkItemStatus.NOT_ASSIGNED);
+ if (!workItemOpt.isEmpty()) {
+ WorkItem workItem = workItemOpt.get(0);
+ workItem.setStatus(WorkItemStatus.IN_PROGRESS);
+ workItem.setAiravataExperimentId(airavataExperimentId);
+ workItemRepository.save(workItem);
+ return Optional.of(workItem);
+ }
+ return Optional.empty();
+ }
+ }
+
+ public Optional<WorkItem> updateWorkItemStatus(String workItemId,
WorkItemStatus status) {
+ Optional<WorkItem> workItemOpt =
workItemRepository.findById(workItemId);
+ if (workItemOpt.isPresent()) {
+ WorkItem workItem = workItemOpt.get();
+ workItem.setStatus(status);
+ workItemRepository.save(workItem);
+ return Optional.of(workItem);
+ }
+ return Optional.empty();
+ }
+
+}
diff --git
a/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
b/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
new file mode 100644
index 00000000..2109a440
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=demo
diff --git
a/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
b/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
new file mode 100644
index 00000000..46366909
--- /dev/null
+++
b/distributed-workqueue/work-orchestrator/src/test/java/org/apache/airavata/work/orchestrator/WorkOrchestratorMainTests.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.work.orchestrator;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class WorkOrchestratorMainTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}