Migrates existing schedules to SCDF 2.3.0

resolves #121

This app pulls in data from the PCFScheduler, AppSummary, AppManifest, the SCDF Task Definition Schema
to construct the new schedule for the migration.

You will note that for the App Summary, the code was pulled directly from the CF-Deployer, the reason
this was done was to obtain additional schedule information required for the migration, without having to make
another call to retrieve the app summary (i.e. obtain this detail while its still there).

As discussed in the README, you will see that not all properties could be obtained from the
available resources and thus have to be populated by a property that user can establish at runtime.
However this also means this affects all schedules to be migrated.

Updated to allow user to setup maven repository

Updated instructions to include how to execute migration

Updated docs

Updated to support deployer format for tasks

Also updated deployers to current release
This commit is contained in:
Glenn Renfro
2019-11-14 09:14:36 -05:00
committed by Michael Minella
parent f6a75bac23
commit c3f5c9e65c
29 changed files with 3147 additions and 0 deletions

3
.gitignore vendored
View File

@@ -34,3 +34,6 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
**/.idea/*
rebel.xml
**/.vscode/
dataflow-migrate-schedules/manifest.yml

View File

@@ -0,0 +1,118 @@
/*
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
https://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.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Properties;
public class MavenWrapperDownloader {
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if (mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
}
catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
}
finally {
try {
if (mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
}
catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: : " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if (!outputFile.getParentFile().exists()) {
if (!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
}
catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip

View File

@@ -0,0 +1,125 @@
= Cloud Scheduler Migration
The purpose of this project is to migrate existing schedules created with Spring
Cloud Data Flow 2.2.x and before to the new 2.3.0 format and stage the
SchedulerTaskLauncher. This is a Spring Boot application that utilizes Spring Batch to create a workflow
to migrate the schedules. This is a single step Spring Batch Job that does the following:
* Read - Retrieves all schedules from scheduler.
* Process - Enriches the Schedule request with App and Deployer properties from the scheduler (or deployed app)
as well as data from the TaskDefinition.
* Write - Deploys artifacts if required and creates the new schedule. Once the migrated
schedule has been created, the old schedule is destroyed.
In the case that the application fails with an exception. You can re-execute the
application and it will pick up where it left off. Thanks Spring Batch! :-)
== Build the project
=== Services Required
In order to migrate the existing schedules to the 2.3.x Spring Cloud Data Flow Scheduling format, the Schedule Migrator requires the following services:
1. Access to the database where Spring Cloud Data Flow stores its Task Definitions. For Cloud Foundry we need to bind the database to the Schedule Migrator.
2. Access to the Scheduling Agent. For Cloud Foundry we need to bind the PCF Scheduler to the Scheduler Migrator.
=== Running the maven command
```
./mvnw clean package
```
== Running The Project For Cloud Foundry
=== Prerequisites
Spring Cloud Data Flow 2.3+ must be installed and running prior to launching the Cloud Scheduler Migration app.
=== Launching your migration
1) Create a manifest.yml file in a work directory.
```
---
applications:
- name: schedulemigrator
host: schedulemigrator
memory: 1G
disk_quota: 1G
instances: 0
path: <location of the jar>
env:
SPRING_APPLICATION_NAME: schedulemigrator
spring_cloud_deployer_cloudfoundry_url: <CF API URL>
spring_cloud_deployer_cloudfoundry_org: <Your org>
spring_cloud_deployer_cloudfoundry_space: <your space>
spring_cloud_deployer_cloudfoundry_username: <Your CF user name>
spring_cloud_deployer_cloudfoundry_password: <Your CF password>
spring_cloud_deployer_cloudfoundry_skipSslValidation: <true/false>
spring_cloud_deployer_cloudfoundry_services: <your SCDF database service,your scheduler service>
spring_cloud_scheduler_cloudfoundry_schedulerUrl: <URL to scheduler service>
spring_profiles_active: cf
spring.cloud.deployer.cloudfoundry.healthCheckTimeout: 300
spring.cloud.deployer.cloudfoundry.apiTimeout: 300
dataflowServerUri: <Your SCDF version 2.3+ server URI>
spring_cloud_task_closecontextEnabled: true
remoteRepositories_repo1_url: https://repo.spring.io/libs-snapshot
services:
- <Your SCDF database service>
- <Your SCDF scheduler service>
```
2) From the command line use the cf cli to log into your org and space for which you will migrate your schedules
```
cf login -a <your CF API endpoint>
```
-or if you need to skip ssl validation-
```
cf login -a <your CF API endpoint> --skip-ssl-validation
```
3) Now push the schedulemigrator from the directory where the manifest.yml is present:
```
cf push
```
3) To start the migration:
From the `dataflow-migrate-schedules` directory launch the `runMigration.sh` using the commands below:
```
chmod +x scripts/runMigration.sh
./scripts/runMigration.sh
```
=== Picking which schedules to migrate
Use the `scheduleNamesToMigrate` property to specify a comma delimited list of
the schedules you wish to migrate. If you don't specify this property
all schedules will be migrated. For example:
```
./scripts/runMigration.sh --scheduleNamesToMigrate=task-job3,task-job1
```
=== Limiting one Scheduler to run at a time
If there is a requirement that only one `schedulemigrator` should run at a time you can set the `spring.cloud.task.single-instance-enabled` property to true. This will stop other executions of the schedulemigrator till the currently running instance completes.
To enable this feature use the `runMigration.sh` script as follows.
```
./scripts/runMigration.sh --spring.cloud.task.single-instance-enabled=true
```
=== Configuring Your Deployer Properties
The following deployer properties will affect all schedules to be migrated.
If a property is not set then the default will be used.
==== Deployer properties to be applied to all migrated schedules:
* healthCheckTimeout
* apiTimeout
* statusTimeout
* stagingTimeout
* startupTimeout
* maximumConcurrentTasks
* javaOpts
NOTE: Descriptions of these properties can be found : https://github.com/cppwfs/spring-cloud-dataflow-samples/blob/SCDF-121/dataflow-migrate-schedules/src/main/java/io/spring/migrateschedule/service/MigrateProperties.java[here]
=== Supported Databases
The database supported are enumerated https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#configuration-local-rdbms[here].
=== Previously Pushed Apps
The Cloud Schedule Migration app does not delete previously scheduled applications.
If these apps are no longer needed it is up to the user to delete them.

286
dataflow-migrate-schedules/mvnw vendored Executable file
View File

@@ -0,0 +1,286 @@
#!/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
#
# https://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
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"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
wget "$jarUrl" -O "$wrapperJarPath"
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
curl -o "$wrapperJarPath" "$jarUrl"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

161
dataflow-migrate-schedules/mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,161 @@
@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 https://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 Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.spring</groupId>
<artifactId>migrateschedule</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<name>Schedule Migrator</name>
<description>Migrates SCDF Schedules to the 2.3 format</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-data-flow.version>2.3.0.RC1</spring-cloud-data-flow.version>
<deployer.version>2.1.0.RELEASE</deployer.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-spi</artifactId>
<version>${deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-maven</artifactId>
<version>${deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-task</artifactId>
</dependency>
<!-- If you are using a database other than MYSql please provide the dependency for that database. -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-cloudfoundry</artifactId>
<version>${deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-docker</artifactId>
<version>${deployer.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dataflow-core</artifactId>
<version>${spring-cloud-data-flow.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dataflow-registry</artifactId>
<version>${spring-cloud-data-flow.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,2 @@
#!/bin/bash
cf rt schedulemigrator "JAVA_OPTS=\"-agentpath:\$PWD/.java-buildpack/open_jdk_jre/bin/jvmkill-1.16.0_RELEASE=printHeapHistogram=1 -Djava.io.tmpdir=\$TMPDIR -XX:ActiveProcessorCount=\$(nproc) -Djava.ext.dirs=\$PWD/.java-buildpack/container_security_provider:\$PWD/.java-buildpack/open_jdk_jre/lib/ext -Djava.security.properties=\$PWD/.java-buildpack/java_security/java.security \$JAVA_OPTS\" && CALCULATED_MEMORY=\$(\$PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-3.13.0_RELEASE -totMemory=\$MEMORY_LIMIT -loadedClasses=26092 -poolType=metaspace -stackThreads=250 -vmOptions=\"\$JAVA_OPTS\") && echo JVM Memory Configuration: \$CALCULATED_MEMORY && JAVA_OPTS=\"\$JAVA_OPTS \$CALCULATED_MEMORY\" && MALLOC_ARENA_MAX=2 SERVER_PORT=\$PORT eval exec \$PWD/.java-buildpack/open_jdk_jre/bin/java \$JAVA_OPTS -cp \$PWD/. org.springframework.boot.loader.JarLauncher $*"

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MigrateScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(MigrateScheduleApplication.class, args);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.batch;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateProperties;
import io.spring.migrateschedule.service.MigrateScheduleService;
import io.spring.migrateschedule.service.ScheduleProcessedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
/**
* Enriches the {@link ConvertScheduleInfo} with information obtained from the platform.
* The new name for the schedule is established and the properties as well as commandline args
* so that the SchedulerTaskLauncher can process the entries.
*
* @author Glenn Renfro
*/
public class SchedulerProcessor<T, C extends ScheduleInfo> implements ItemProcessor<ConvertScheduleInfo, ConvertScheduleInfo>{
private static final Logger logger = LoggerFactory.getLogger(SchedulerProcessor.class);
private MigrateScheduleService migrateScheduleService;
private MigrateProperties migrateProperties;
public SchedulerProcessor(MigrateScheduleService migrateScheduleService, MigrateProperties migrateProperties) {
this.migrateScheduleService = migrateScheduleService;
this.migrateProperties = migrateProperties;
}
@Override
public ConvertScheduleInfo process(ConvertScheduleInfo scheduleInfo){
if(scheduleInfo.getScheduleName().contains(migrateProperties.getSchedulerToken())) {
throw new ScheduleProcessedException(scheduleInfo.getScheduleName());
}
logger.info(String.format("Processing Schedule %s", scheduleInfo.getScheduleName()));
return this.migrateScheduleService.enrichScheduleMetadata(scheduleInfo);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.batch;
import java.util.List;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateScheduleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
import org.springframework.util.Assert;
/**
* Retrieves all of the available schedules.
*
* @author Glenn Renfro
*/
public class SchedulerReader<C extends ScheduleInfo> extends AbstractItemCountingItemStreamItemReader<ConvertScheduleInfo> {
private static final Logger logger = LoggerFactory.getLogger(SchedulerReader.class);
private List<ConvertScheduleInfo> scheduleInfoList;
private MigrateScheduleService migrateScheduleService;
public SchedulerReader(MigrateScheduleService migrateScheduleService) {
Assert.notNull(migrateScheduleService, "convertScheduleService must not be null");
logger.info("Retrieving schedules from PCF Scheduler");
this.migrateScheduleService = migrateScheduleService;
}
@Override
protected ConvertScheduleInfo doRead(){
return this.scheduleInfoList.get(this.getCurrentItemCount()-1);
}
@Override
protected void doOpen() {
this.scheduleInfoList = migrateScheduleService.scheduleInfoList();
this.setMaxItemCount(this.scheduleInfoList.size());
this.setName("scheduler-reader");
}
@Override
protected void doClose(){
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.batch;
import java.util.List;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateScheduleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
/**
* Migrates the original schedule to the new scheduler format required for SCDF
* and stages the SchedulerTaskLauncher.
*
* @author Glenn Renfro
*/
public class SchedulerWriter<C extends ScheduleInfo> implements ItemWriter<ConvertScheduleInfo> {
private static final Logger logger = LoggerFactory.getLogger(SchedulerWriter.class);
private Scheduler scheduler;
private MigrateScheduleService scheduleService;
public SchedulerWriter (MigrateScheduleService scheduleService, Scheduler scheduler) {
this.scheduleService = scheduleService;
this.scheduler = scheduler;
}
@Override
public void write(List<? extends ConvertScheduleInfo> list) {
for(ConvertScheduleInfo scheduleInfo : list) {
logger.info(String.format("Migrating Schedule %s ", scheduleInfo.getScheduleName()));
this.scheduleService.migrateSchedule(this.scheduler, scheduleInfo);
logger.info(String.format("Migrated Schedule %s ", scheduleInfo.getScheduleName()));
};
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.configuration;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateScheduleService;
import io.spring.migrateschedule.service.MigrateProperties;
import io.spring.migrateschedule.batch.SchedulerProcessor;
import io.spring.migrateschedule.batch.SchedulerReader;
import io.spring.migrateschedule.batch.SchedulerWriter;
import io.spring.migrateschedule.service.ScheduleProcessedException;
import io.spring.migrateschedule.service.SchedulerSkipPolicy;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Glenn Renfro
*/
@Configuration
@EnableBatchProcessing
@EnableTask
public class BatchConfiguration {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Bean
public Job importUserJob(Step migrationStep) {
return this.jobBuilderFactory.get("migrationJob")
.incrementer(new RunIdIncrementer())
.start(migrationStep)
.build();
}
@Bean
public Step migrationStep(SchedulerReader itemReader,
SchedulerProcessor schedulerProcessor, SchedulerWriter writer) {
return this.stepBuilderFactory.get("migrationStep")
.<ScheduleInfo, ScheduleInfo> chunk(1)
.reader(itemReader)
.processor(schedulerProcessor)
.writer(writer)
.faultTolerant()
.skip(ScheduleProcessedException.class)
.skipPolicy(new SchedulerSkipPolicy())
.build();
}
@Bean
public SchedulerReader<ConvertScheduleInfo> itemReader(MigrateScheduleService scheduler) {
return new SchedulerReader(scheduler);
}
@Bean
public SchedulerWriter<ConvertScheduleInfo> itemWriter(Scheduler scheduler, MigrateScheduleService scheduleService) {
return new SchedulerWriter(scheduleService, scheduler);
}
@Bean
public SchedulerProcessor<ConvertScheduleInfo, ConvertScheduleInfo> itemProcessor(MigrateScheduleService migrateScheduleService, MigrateProperties migrateProperties) {
return new SchedulerProcessor(migrateScheduleService, migrateProperties);
}
@Bean
@ConfigurationProperties
public MigrateProperties converterProperties() {
return new MigrateProperties();
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.configuration;
import io.pivotal.reactor.scheduler.ReactorSchedulerClient;
import io.pivotal.scheduler.SchedulerClient;
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
import io.spring.migrateschedule.service.MigrateProperties;
import io.spring.migrateschedule.service.MigrateScheduleService;
import io.spring.migrateschedule.service.TaskDefinitionRepository;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.reactor.ConnectionContext;
import org.cloudfoundry.reactor.TokenProvider;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;
import org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.CloudFoundryAppScheduler;
import org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.CloudFoundrySchedulerProperties;
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* @author Glenn Renfro
*/
@Configuration
@EntityScan({
"org.springframework.cloud.dataflow.core"
})
public class CFMigrateScheduleConfiguration {
@Bean
public ReactorSchedulerClient reactorSchedulerClient(ConnectionContext context,
TokenProvider passwordGrantTokenProvider,
CloudFoundrySchedulerProperties properties) {
return ReactorSchedulerClient.builder()
.connectionContext(context)
.tokenProvider(passwordGrantTokenProvider)
.root(Mono.just(properties.getSchedulerUrl()))
.build();
}
@Bean
public CloudFoundrySchedulerProperties cloudFoundrySchedulerProperties() {
return new CloudFoundrySchedulerProperties();
}
@Bean
public CFMigrateSchedulerService scheduleService(CloudFoundryOperations cloudFoundryOperations,
SchedulerClient schedulerClient,
CloudFoundryConnectionProperties properties, MigrateProperties migrateProperties,
TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
return new CFMigrateSchedulerService(cloudFoundryOperations,
schedulerClient, properties, migrateProperties, taskDefinitionRepository, mavenProperties);
}
@Bean
public CloudFoundryAppScheduler scheduler(SchedulerClient client, CloudFoundryOperations operations,
CloudFoundryConnectionProperties properties, TaskLauncher taskLauncher,
CloudFoundrySchedulerProperties schedulerProperties) {
return new CloudFoundryAppScheduler(client, operations, properties, (CloudFoundryTaskLauncher) taskLauncher, schedulerProperties);
}
@Bean
@ConfigurationProperties
public MavenProperties mavenProperties() {
return new MavenProperties();
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.springframework.cloud.dataflow.core.TaskDefinition;
import org.springframework.cloud.dataflow.registry.support.AppResourceCommon;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* Abstract class containing methods that will be required for both Cloud Foundry
* and the Kubernetes Impls.
*
* @author Glenn Renfro
*/
public abstract class AbstractMigrateService implements MigrateScheduleService {
private final static String DATA_FLOW_URI_KEY = "spring.cloud.dataflow.client.serverUri";
private final static String COMMAND_ARGUMENT_PREFIX = "cmdarg.";
protected final static String APP_PREFIX = "app.";
protected final static String DEPLOYER_PREFIX = "deployer.";
protected MigrateProperties migrateProperties;
private TaskDefinitionRepository taskDefinitionRepository;
private MavenProperties mavenProperties;
public AbstractMigrateService(MigrateProperties migrateProperties, TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
this.migrateProperties = migrateProperties;
this.taskDefinitionRepository = taskDefinitionRepository;
this.mavenProperties = mavenProperties;
}
public TaskDefinition findTaskDefinitionByName(String taskDefinitionName) {
return this.taskDefinitionRepository.findByTaskName(taskDefinitionName);
}
protected String getSchedulePrefixDefinitionName(String taskDefinitionName) {
return this.migrateProperties.getSchedulerToken() + taskDefinitionName;
}
/**
* Retain only properties that are meant for the <em>scheduler</em> of a given task(those
* that start with {@code scheduler.}and qualify all
* property values with the {@code spring.cloud.scheduler.} prefix.
*
* @param input the scheduler properties
* @return scheduler properties for the task
*/
protected static Map<String, String> extractAndQualifySchedulerProperties(Map<String, String> input) {
final String prefix = "spring.cloud.scheduler.";
Map<String, String> result = new TreeMap<>(input).entrySet().stream()
.filter(kv -> kv.getKey().startsWith(prefix))
.collect(Collectors.toMap(kv -> kv.getKey(), kv -> kv.getValue(),
(fromWildcard, fromApp) -> fromApp));
return result;
}
/**
* Retrieve the resource for the SchedulerTaskLauncher and verify the URI.
* @return {@link Resource} for the SchedulerTaskLauncher.
*/
protected Resource getTaskLauncherResource() {
final URI url;
try {
new URI(this.migrateProperties.getSchedulerTaskLauncherUrl()); //verify url
}
catch (URISyntaxException uriSyntaxException) {
throw new IllegalArgumentException(uriSyntaxException);
}
AppResourceCommon appResourceCommon = new AppResourceCommon(this.mavenProperties, new DefaultResourceLoader());
return appResourceCommon.getResource(this.migrateProperties.getSchedulerTaskLauncherUrl());
}
/**
* Add the appropriate tags to the command line args so that the SchedulerTaskLauncher can
* extract them.
* @param args the command line args to be tagged.
* @return the tagged command line args.
*/
protected List<String> tagCommandLineArgs(List<String> args) {
List<String> taggedArgs = new ArrayList<>();
for(String arg : args) {
if(arg.contains("spring.cloud.task.name")) {
continue;
}
String updatedArg = arg;
if (!arg.startsWith(DATA_FLOW_URI_KEY) && !"--".concat(arg).startsWith(DATA_FLOW_URI_KEY)) {
updatedArg = COMMAND_ARGUMENT_PREFIX +
this.migrateProperties.getTaskLauncherPrefix() + "." + arg;
}
taggedArgs.add(updatedArg);
}
return taggedArgs;
}
/**
* Add the appropriate tags to the command line args so that the SchedulerTaskLauncher can
* extract them.
* @param appName the name of the application to be associated with the property
* @param appProperties the properties to be tagged
* @param prefix the prefix to mark the property as to be used by the SchedulerTaskLauncher.
* @return the tagged command line args.
*/
protected Map<String, String> tagProperties(String appName, Map<String, String> appProperties, String prefix) {
Map<String, String> taggedAppProperties = new HashMap<>(appProperties.size());
for(String key : appProperties.keySet()) {
if(key.contains("spring.cloud.task.name")) {
continue;
}
String updatedKey = key;
if (!key.startsWith(DATA_FLOW_URI_KEY)) {
if (StringUtils.hasText(appName)) {
updatedKey = this.migrateProperties.getTaskLauncherPrefix() + "." +
prefix + appName + "." + key;
}
else {
updatedKey = this.migrateProperties.getTaskLauncherPrefix() + "." +
prefix + key;
}
}
taggedAppProperties.put(updatedKey, appProperties.get(key));
}
return taggedAppProperties;
}
/**
* Add the required SchedulerTaskLauncher properties.
* @param properties the map of properties in which to add the SchedulerTaskLauncher properties.
* @return updated properties.
*/
protected Map<String, String> addSchedulerAppProps(Map<String, String> properties) {
Map<String, String> appProperties = new HashMap<>(properties);
appProperties.put("spring.cloud.dataflow.client.serverUri", this.migrateProperties.getDataflowServerUri());
return appProperties;
}
}

View File

@@ -0,0 +1,402 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.pivotal.scheduler.SchedulerClient;
import io.pivotal.scheduler.v1.jobs.ListJobsRequest;
import io.pivotal.scheduler.v1.jobs.ListJobsResponse;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
import org.cloudfoundry.operations.applications.Route;
import org.cloudfoundry.operations.spaces.SpaceSummary;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.dataflow.core.TaskDefinition;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import org.springframework.cloud.deployer.spi.scheduler.SchedulerException;
import org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;
import org.springframework.util.StringUtils;
/**
* Services required to migrate schedules to the 2.3.0 format in Cloud Foundry
* and stage the SchedulerTaskLauncher.
*
* @author Glenn Renfro
*/
public class CFMigrateSchedulerService extends AbstractMigrateService {
public static final String JAR_LAUNCHER = "org.springframework.boot.loader.JarLauncher";
private static final int JAR_LAUNCHER_LENGTH = JAR_LAUNCHER.length();
private static final Logger logger = LoggerFactory.getLogger(CFMigrateSchedulerService.class);
private static final String CLOUD_FOUNDRY_PREFIX = "cloudfoundry";
private final static int PCF_PAGE_START_NUM = 1; //First PageNum for PCFScheduler starts at 1.
private final static String SCHEDULER_SERVICE_ERROR_MESSAGE = "Scheduler Service returned a null response.";
private CloudFoundryOperations cloudFoundryOperations;
private SchedulerClient schedulerClient;
private CloudFoundryConnectionProperties properties;
public CFMigrateSchedulerService(CloudFoundryOperations cloudFoundryOperations,
SchedulerClient schedulerClient,
CloudFoundryConnectionProperties properties, MigrateProperties migrateProperties,
TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
super(migrateProperties, taskDefinitionRepository, mavenProperties);
this.cloudFoundryOperations = cloudFoundryOperations;
this.schedulerClient = schedulerClient;
this.properties = properties;
}
@Override
public List<ConvertScheduleInfo> scheduleInfoList() {
List<ConvertScheduleInfo> result = new ArrayList<>();
int pageCount = getJobPageCount();
for (int i = PCF_PAGE_START_NUM; i <= pageCount; i++) {
logger.info(String.format("Reading Schedules Page %s of %s ", i, pageCount ));
List<ConvertScheduleInfo> scheduleInfoPage = getSchedules(i);
if(scheduleInfoPage == null) {
throw new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);
}
result.addAll(scheduleInfoPage);
}
Collections.sort(result);
return result;
}
public List<ConvertScheduleInfo> getSchedules(int page) {
Flux<ApplicationSummary> applicationSummaries = cacheAppSummaries();
return this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {
return this.schedulerClient.jobs().list(ListJobsRequest.builder()
.spaceId(requestSummary.getId())
.page(page)
.detailed(true).build());
})
.flatMapIterable(jobs -> jobs.getResources())// iterate over the resources returned.
.flatMap(job -> {
return getApplication(applicationSummaries,
job.getApplicationId()) // get the application name for each job.
.map(optionalApp -> {
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setScheduleName(job.getName());
scheduleInfo.setTaskDefinitionName(optionalApp.getName());
String jobCommandLine = job.getCommand();
String commandArgs = "";
if (jobCommandLine != null && jobCommandLine.length() > JAR_LAUNCHER_LENGTH) {
int locationOfArgs = job.getCommand().indexOf(JAR_LAUNCHER) + JAR_LAUNCHER_LENGTH;
commandArgs = job.getCommand().substring(locationOfArgs);
}
else {
logger.warn(String.format("Job %s does not have commandArgs associated with it.", job.getName()));
}
if (StringUtils.hasText(commandArgs)) {
try {
scheduleInfo.setCommandLineArgs(Arrays.asList(CommandLineUtils.translateCommandline(commandArgs)));
}
catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
if (job.getJobSchedules() != null) {
scheduleInfo.getScheduleProperties().put(SchedulerPropertyKeys.CRON_EXPRESSION,
job.getJobSchedules().get(0).getExpression());
}
else {
logger.warn(String.format("Job %s does not have an associated schedule", job.getName()));
}
return scheduleInfo;
});
})
.filter(job -> isScheduleMigratable(job.getScheduleName()))
.collectList().block();
}
private boolean isScheduleMigratable(String scheduleName) {
boolean result;
if(migrateProperties.getScheduleNamesToMigrate().size() > 0) {
result = migrateProperties.getScheduleNamesToMigrate().contains(scheduleName);
}
else {
result = true;
}
return result;
}
@Override
public ConvertScheduleInfo enrichScheduleMetadata(ConvertScheduleInfo scheduleInfo) {
logger.info(String.format("Retrieving Properties from application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
ApplicationEnvironments environment = this.cloudFoundryOperations.applications().
getEnvironments(GetApplicationEnvironmentsRequest.builder().
name(scheduleInfo.getTaskDefinitionName()).
build()).
block();
logger.info(String.format("Retrieving ApplicationManifest for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
ApplicationManifest applicationManifest = getApplicationManifest(scheduleInfo.getTaskDefinitionName());
if(applicationManifest != null) {
addApplicationManifestPropsToConvertScheduleInfo(scheduleInfo, applicationManifest);
}
if (environment != null) {
for (Map.Entry<String, Object> var : environment.getUserProvided().entrySet()) {
scheduleInfo.getScheduleProperties().put(var.getKey(), (String) var.getValue());
}
}
logger.info(String.format("Tagging command line args for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
List<String> revisedCommandLineArgs = tagCommandLineArgs(scheduleInfo.getCommandLineArgs());
revisedCommandLineArgs.add("--spring.cloud.scheduler.task.launcher.taskName=" + scheduleInfo.getTaskDefinitionName());
scheduleInfo.setCommandLineArgs(revisedCommandLineArgs);
Map<String, String> appProperties = null;
try {
logger.info(String.format("Extracting Spring App Properties for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
appProperties = getSpringAppProperties(scheduleInfo.getScheduleProperties());
if(appProperties.size() > 0) {
scheduleInfo.setUseSpringApplicationJson(true);
}
}
catch (Exception exception) {
throw new IllegalArgumentException("Unable to parse SPRING_APPLICATION_JSON from USER VARIABLES", exception);
}
logger.info(String.format("Retrieving Task Definition for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
TaskDefinition taskDefinition = findTaskDefinitionByName(appProperties.get("spring.cloud.task.name"));
if (appProperties.size() > 0 && taskDefinition == null) {
throw new IllegalStateException(String.format("The schedule %s contains " +
"properties but the task definition %s does not exist and thus can't be migrated",
scheduleInfo.getScheduleName(), scheduleInfo.getTaskDefinitionName()));
}
logger.info(String.format("Tagging app properties for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
appProperties = tagProperties(taskDefinition.getRegisteredAppName(), appProperties, APP_PREFIX);
Map<String, String> deployerProperties = tagProperties(taskDefinition.getRegisteredAppName(),
getDeployerProperties(scheduleInfo), DEPLOYER_PREFIX);
appProperties = addSchedulerAppProps(appProperties);
appProperties.putAll(deployerProperties);
scheduleInfo.setAppProperties(appProperties);
return scheduleInfo;
}
@Override
public void migrateSchedule(Scheduler scheduler, ConvertScheduleInfo scheduleInfo) {
String scheduleName = scheduleInfo.getScheduleName() + "-" + getSchedulePrefixDefinitionName(scheduleInfo.getTaskDefinitionName());
AppDefinition appDefinition = new AppDefinition(scheduleName, scheduleInfo.getAppProperties());
logger.info(String.format("Extracting schedule specific properties for schedule %s", scheduleInfo.getScheduleName()));
Map<String, String> schedulerProperties = extractAndQualifySchedulerProperties(scheduleInfo.getScheduleProperties());
ScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties, new HashMap<>(), scheduleInfo.getCommandLineArgs(), scheduleName, getTaskLauncherResource());
logger.info(String.format("Staging ScheduleTaskLauncher and scheduling %s", scheduleInfo.getScheduleName()));
scheduler.schedule(scheduleRequest);
logger.info(String.format("Unscheduling original %s", scheduleInfo.getScheduleName()));
scheduler.unschedule(scheduleInfo.getScheduleName());
}
/**
* Retrieves the number of pages that can be returned when retrieving a list of jobs.
* @return an int containing the number of available pages.
*/
private int getJobPageCount() {
ListJobsResponse response = this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {
return this.schedulerClient.jobs().list(ListJobsRequest.builder()
.spaceId(requestSummary.getId())
.detailed(false).build());
}).block();
if(response == null) {
throw new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);
}
return response.getPagination().getTotalPages();
}
private Map<String, String> getSpringAppProperties(Map<String, String> properties) throws Exception {
Map<String, String> result;
if(properties.containsKey("SPRING_APPLICATION_JSON")) {
result = new ObjectMapper()
.readValue(properties.get("SPRING_APPLICATION_JSON"), Map.class);
}
else {
result = new HashMap<>();
}
return result;
}
/**
* Retrieve a {@link Mono} containing a {@link SpaceSummary} for the specified name.
*
* @param spaceName the name of space to search.
* @return the {@link SpaceSummary} associated with the spaceName.
*/
private Mono<SpaceSummary> getSpace(String spaceName) {
return requestSpaces()
.cache() //cache results from first call.
.filter(space -> spaceName.equals(space.getName()))
.singleOrEmpty()
.cast(SpaceSummary.class);
}
/**
* Retrieve a {@link Flux} containing the available {@link SpaceSummary}s.
*
* @return {@link Flux} of {@link SpaceSummary}s.
*/
private Flux<SpaceSummary> requestSpaces() {
return this.cloudFoundryOperations.spaces()
.list();
}
/**
* Retrieve a cached {@link Flux} of {@link ApplicationSummary}s.
*/
private Flux<ApplicationSummary> cacheAppSummaries() {
return requestListApplications()
.cache(); //cache results from first call. No need to re-retrieve each time.
}
/**
* Retrieve a {@link Flux} of {@link ApplicationSummary}s.
*/
private Flux<ApplicationSummary> requestListApplications() {
return this.cloudFoundryOperations.applications()
.list();
}
/**
* Retrieve a {@link Mono} containing the {@link ApplicationSummary} associated with the appId.
*
* @param applicationSummaries {@link Flux} of {@link ApplicationSummary}s to filter.
* @param appId the id of the {@link ApplicationSummary} to search.
*/
private Mono<ApplicationSummary> getApplication(Flux<ApplicationSummary> applicationSummaries,
String appId) {
return applicationSummaries
.filter(application -> appId.equals(application.getId()))
.singleOrEmpty();
}
private ApplicationManifest getApplicationManifest(String appName) {
return this.cloudFoundryOperations.applications()
.getApplicationManifest(GetApplicationManifestRequest
.builder().name(appName).build())
.block();
}
private Map<String, String> getDeployerProperties(ConvertScheduleInfo scheduleInfo) {
Map<String, String> result = new HashMap<>();
if (scheduleInfo.getJavaBuildPack() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".buildpack", scheduleInfo.getJavaBuildPack());
}
if (scheduleInfo.getMemoryInMB() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".memory", scheduleInfo.getMemoryInMB() + "m");
}
if (scheduleInfo.getDiskInMB() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".disk", scheduleInfo.getDiskInMB() + "m");
}
if (scheduleInfo.getApplicationHealthCheck() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check", scheduleInfo.getApplicationHealthCheck().getValue());
}
if (scheduleInfo.getHealthCheckEndPoint() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check-http-endpoint", scheduleInfo.getHealthCheckEndPoint());
}
if (scheduleInfo.getServices() != null && scheduleInfo.getServices().size() > 0) {
result.put(CLOUD_FOUNDRY_PREFIX + ".services", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getServices().toArray()));
}
if (scheduleInfo.getDomains() != null && scheduleInfo.getDomains().size() > 0) {
result.put(CLOUD_FOUNDRY_PREFIX + ".domain", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getDomains().toArray()));
}
if (scheduleInfo.getRoutes() != null && scheduleInfo.getRoutes().size() > 0) {
result.put(CLOUD_FOUNDRY_PREFIX + ".route-path", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getRoutes().toArray()));
}
if (scheduleInfo.getHosts() != null && scheduleInfo.getHosts().size() > 0) {
result.put(CLOUD_FOUNDRY_PREFIX + ".host", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getHosts().toArray()));
}
// Global deployer properties;
if (this.migrateProperties.getHealthCheckTimeout() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check-timeout", this.migrateProperties.getHealthCheckTimeout());
}
if (this.migrateProperties.getJavaOptions() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".javaOpts", this.migrateProperties.getJavaOptions());
}
if (this.migrateProperties.getApiTimeout() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".api-timeout", String.valueOf(this.migrateProperties.getApiTimeout()));
}
if (this.migrateProperties.getStatusTimeout() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".status-timeout", String.valueOf(this.migrateProperties.getStatusTimeout()));
}
if (this.migrateProperties.getStagingTimeout() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".staging-timeout", String.valueOf(this.migrateProperties.getStagingTimeout()));
}
if (this.migrateProperties.getStartupTimeout() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".startup-timeout", String.valueOf(this.migrateProperties.getStartupTimeout()));
}
if (this.migrateProperties.getMaximumConcurrentTasks() != null) {
result.put(CLOUD_FOUNDRY_PREFIX + ".maximum-concurrent-tasks", String.valueOf(this.migrateProperties.getMaximumConcurrentTasks()));
}
return result;
}
private ConvertScheduleInfo addApplicationManifestPropsToConvertScheduleInfo(ConvertScheduleInfo scheduleInfo, ApplicationManifest applicationManifest) {
scheduleInfo.setDiskInMB(applicationManifest.getDisk());
scheduleInfo.setMemoryInMB(applicationManifest.getMemory());
scheduleInfo.setApplicationHealthCheck(applicationManifest.getHealthCheckType());
scheduleInfo.setJavaBuildPack(applicationManifest.getBuildpack());
scheduleInfo.setHealthCheckEndPoint(applicationManifest.getHealthCheckHttpEndpoint());
if(applicationManifest.getServices() != null && applicationManifest.getServices().size() > 0) {
scheduleInfo.setServices(applicationManifest.getServices());
}
if(applicationManifest.getDomains() != null && applicationManifest.getDomains().size() > 0) {
scheduleInfo.setDomains(applicationManifest.getDomains());
}
if(applicationManifest.getRoutes() != null && applicationManifest.getRoutes().size() > 0) {
List<String> routes = new ArrayList<>();
for (Route route : applicationManifest.getRoutes()) {
routes.add(route.getRoute());
}
scheduleInfo.setRoutes(routes);
}
if (applicationManifest.getHosts() != null && applicationManifest.getHosts().size() > 0) {
List<String> hosts = new ArrayList<>();
for (String host : applicationManifest.getHosts()) {
hosts.add(host);
}
scheduleInfo.setHosts(hosts);
}
return scheduleInfo;
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
/**
* A child implementation of {@link ScheduleInfo} that adds additional attributes
* required to migrate to the new schedule format.
*
* @author Glenn Renfro
*/
public class ConvertScheduleInfo extends ScheduleInfo implements Comparable{
private List<String> commandLineArgs = new ArrayList<>();
private String registeredAppName;
private Map<String, String> appProperties = new HashMap<>();
private Integer diskInMB;
private Integer memoryInMB;
private String javaBuildPack;
private ApplicationHealthCheck applicationHealthCheck;
private String healthCheckEndPoint;
private Integer maximumConcurrentTasks = 20;
private boolean useSpringApplicationJson;
private List<String> services;
private List<String> domains;
private List<String> routes;
private List<String> hosts;
public List<String> getCommandLineArgs() {
return commandLineArgs;
}
public void setCommandLineArgs(List<String> commandLineArgs) {
this.commandLineArgs = commandLineArgs;
}
public String getRegisteredAppName() {
return registeredAppName;
}
public void setRegisteredAppName(String registeredAppName) {
this.registeredAppName = registeredAppName;
}
public Map<String, String> getAppProperties() {
return appProperties;
}
public void setAppProperties(Map<String, String> appProperties) {
this.appProperties = appProperties;
}
@Override
public int compareTo(Object o) {
if(o instanceof ConvertScheduleInfo) {
return this.getScheduleName().compareTo(((ConvertScheduleInfo) o).getScheduleName());
}
throw new IllegalArgumentException("Can only compare Objects of type ConvertScheduleInfo");
}
public Integer getDiskInMB() {
return diskInMB;
}
public void setDiskInMB(Integer diskInMB) {
this.diskInMB = diskInMB;
}
public Integer getMemoryInMB() {
return memoryInMB;
}
public void setMemoryInMB(Integer memoryInMB) {
this.memoryInMB = memoryInMB;
}
public String getJavaBuildPack() {
return javaBuildPack;
}
public void setJavaBuildPack(String javaBuildPack) {
this.javaBuildPack = javaBuildPack;
}
public ApplicationHealthCheck getApplicationHealthCheck() {
return applicationHealthCheck;
}
public void setApplicationHealthCheck(ApplicationHealthCheck applicationHealthCheck) {
this.applicationHealthCheck = applicationHealthCheck;
}
public boolean isUseSpringApplicationJson() {
return useSpringApplicationJson;
}
public void setUseSpringApplicationJson(boolean useSpringApplicationJson) {
this.useSpringApplicationJson = useSpringApplicationJson;
}
public String getHealthCheckEndPoint() {
return healthCheckEndPoint;
}
public void setHealthCheckEndPoint(String healthCheckEndPoint) {
this.healthCheckEndPoint = healthCheckEndPoint;
}
public Integer getMaximumConcurrentTasks() {
return maximumConcurrentTasks;
}
public void setMaximumConcurrentTasks(Integer maximumConcurrentTasks) {
this.maximumConcurrentTasks = maximumConcurrentTasks;
}
public List<String> getServices() {
return services;
}
public void setServices(List<String> services) {
this.services = services;
}
public List<String> getDomains() {
return domains;
}
public void setDomains(List<String> domains) {
this.domains = domains;
}
public List<String> getRoutes() {
return routes;
}
public void setRoutes(List<String> routes) {
this.routes = routes;
}
public List<String> getHosts() {
return hosts;
}
public void setHosts(List<String> hosts) {
this.hosts = hosts;
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import java.util.ArrayList;
import java.util.List;
/**
* Allows user to configure the migration application.
*
* @author Glenn Renfro
*/
public class MigrateProperties {
private String schedulerTaskLauncherUrl = "maven://org.springframework.cloud:spring-cloud-dataflow-scheduler-task-launcher:2.3.0.BUILD-SNAPSHOT";
/**
* The token for the updated schedules.
*/
private String schedulerToken = "scdf-";
/**
* The prefix to attach to the application properties to be sent to the SchedulerTaskLauncher.
*/
private String taskLauncherPrefix = "tasklauncher";
private String dataflowServerUri = "http://localhost:9393";
/**
* The global Java Options required for the applications to be launched by the schedulerTaskLauncher.
*/
private String javaOptions;
/**
* The global timeout to be assigned to applications to be launched by the schedulerTaskLauncher.
*/
private String healthCheckTimeout;
/**
* The global api timeout to be assigned to applications to be launched by scheduleTaskLauncher.
*/
private Long apiTimeout;
/**
* Timeout for status API operations in milliseconds to be assigned to applications to be launched by scheduleTaskLauncher
*/
private Long statusTimeout;
/**
* If set, the global override the timeout allocated for staging an app launched by the schedulefTaskLauncher.
*/
private Long stagingTimeout;
/**
* If set, the global override the timeout allocated for starting an app launched by scheduleTaskLauncher.
*/
private Long startupTimeout;
/**
* If set, the global override for the maximum number of concurrently running tasks.
*/
private Integer maximumConcurrentTasks;
/**
* The number of seconds to wait for a schedule to complete.
* This excludes the time it takes to stage the application on Cloud Foundry.
*/
private int scheduleTimeoutInSeconds = 30;
/**
* Comma delimited list of schedules to migrate. If empty then all schedules will be migrated.
*/
private List<String> scheduleNamesToMigrate = new ArrayList<>();
public String getSchedulerTaskLauncherUrl() {
return schedulerTaskLauncherUrl;
}
public void setSchedulerTaskLauncherUrl(String schedulerTaskLauncherUrl) {
this.schedulerTaskLauncherUrl = schedulerTaskLauncherUrl;
}
public String getSchedulerToken() {
return schedulerToken;
}
public void setSchedulerToken(String schedulerToken) {
this.schedulerToken = schedulerToken;
}
public String getTaskLauncherPrefix() {
return taskLauncherPrefix;
}
public void setTaskLauncherPrefix(String taskLauncherPrefix) {
this.taskLauncherPrefix = taskLauncherPrefix;
}
public String getDataflowServerUri() {
return dataflowServerUri;
}
public void setDataflowServerUri(String dataflowServerUri) {
this.dataflowServerUri = dataflowServerUri;
}
public int getScheduleTimeoutInSeconds() {
return scheduleTimeoutInSeconds;
}
public void setScheduleTimeoutInSeconds(int scheduleTimeoutInSeconds) {
this.scheduleTimeoutInSeconds = scheduleTimeoutInSeconds;
}
public String getJavaOptions() {
return javaOptions;
}
public void setJavaOptions(String javaOptions) {
this.javaOptions = javaOptions;
}
public String getHealthCheckTimeout() {
return healthCheckTimeout;
}
public void setHealthCheckTimeout(String healthCheckTimeout) {
this.healthCheckTimeout = healthCheckTimeout;
}
public Long getApiTimeout() {
return apiTimeout;
}
public void setApiTimeout(Long apiTimeout) {
this.apiTimeout = apiTimeout;
}
public Long getStatusTimeout() {
return statusTimeout;
}
public void setStatusTimeout(Long statusTimeout) {
this.statusTimeout = statusTimeout;
}
public Long getStagingTimeout() {
return stagingTimeout;
}
public void setStagingTimeout(Long stagingTimeout) {
this.stagingTimeout = stagingTimeout;
}
public Long getStartupTimeout() {
return startupTimeout;
}
public void setStartupTimeout(Long startupTimeout) {
this.startupTimeout = startupTimeout;
}
public Integer getMaximumConcurrentTasks() {
return maximumConcurrentTasks;
}
public void setMaximumConcurrentTasks(Integer maximumConcurrentTasks) {
this.maximumConcurrentTasks = maximumConcurrentTasks;
}
public List<String> getScheduleNamesToMigrate() {
return scheduleNamesToMigrate;
}
public void setScheduleNamesToMigrate(List<String> scheduleNamesToMigrate) {
this.scheduleNamesToMigrate = scheduleNamesToMigrate;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import java.util.List;
import org.springframework.cloud.dataflow.core.TaskDefinition;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
/**
* Interface that establishes the method signatures required to migrate
* schedules to the 2.3.0 format as well as stage the application.
*/
public interface MigrateScheduleService {
/**
* Retrieve all available {@link ScheduleInfo}s.
* @return list of available ScheduleInfos
*/
List<ConvertScheduleInfo> scheduleInfoList();
/**
* Add properties and commandLine args to the {@link ScheduleInfo}
* @return enriched {@link ConvertScheduleInfo}
*/
ConvertScheduleInfo enrichScheduleMetadata(ConvertScheduleInfo scheduleInfo);
/**
* Migrates existing schedule to new SCDF schedule.
* @param scheduler the deployer scheduler to build the new schedule.
* @param scheduleInfo the schedule info containing the existing schedule.
*/
void migrateSchedule(Scheduler scheduler, ConvertScheduleInfo scheduleInfo);
/**
* Retrieve {@link TaskDefinition} for the name provided
* @param taskDefinitionName the name of the {@link TaskDefinition}.
* @return a TaskDefinition
*/
TaskDefinition findTaskDefinitionByName(String taskDefinitionName);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
/**
* Thrown if a schedule has already been processed.
*
* @author Glenn Renfro
*/
public class ScheduleProcessedException extends RuntimeException {
public ScheduleProcessedException(String scheduleName) {
super(String.format("Schedule %s has already been migrated", scheduleName));
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipPolicy;
/**
* Establish that there is no max maximum skip count if {@link ScheduleProcessedException} is thrown.
*
* @author Glenn Renfro
*
*/
public class SchedulerSkipPolicy implements SkipPolicy {
@Override
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
boolean result = false;
if(t instanceof ScheduleProcessedException) {
result = true;
}
return result;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule.service;
import org.springframework.cloud.dataflow.core.TaskDefinition;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Transactional;
/**
* Repository to access {@link org.springframework.cloud.dataflow.core.TaskDefinition}s.
*
* @author Michael Minella
* @author Gunnar Hillert
*/
@Transactional
public interface TaskDefinitionRepository extends PagingAndSortingRepository<org.springframework.cloud.dataflow.core.TaskDefinition, String> {
Page<org.springframework.cloud.dataflow.core.TaskDefinition> findByTaskNameContains(String taskName, Pageable pageable);
/**
* Performs a findByName query and throws an exception if the name is not found.
* @param name the name of the task definition
* @return The task definition instance or NoSuchTaskDefinitionException if not found.
*/
TaskDefinition findByTaskName(String name);
}

View File

@@ -0,0 +1 @@
logging.level.org.springframework.cloud.task=debug

View File

@@ -0,0 +1,6 @@
____ ____ ____ _____ ____ _ _ _ __ __ _ _
/ ___| / ___| | _ \ | ___| / ___| ___ | |__ ___ __| | _ _ | | ___ ___ | \/ | (_) __ _ _ __ __ _ | |_ ___ _ __
\___ \ | | | | | | | |_ \___ \ / __| | '_ \ / _ \ / _` | | | | | | | / _ \ / __| | |\/| | | | / _` | | '__| / _` | | __| / _ \ | '__|
___) | | |___ | |_| | | _| ___) | | (__ | | | | | __/ | (_| | | |_| | | | | __/ \__ \ | | | | | | | (_| | | | | (_| | | |_ | (_) | | |
|____/ \____| |____/ |_| |____/ \___| |_| |_| \___| \__,_| \__,_| |_| \___| |___/ |_| |_| |_| \__, | |_| \__,_| \__| \___/ |_|
|___/

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule;
import org.cloudfoundry.doppler.LogMessage;
import org.cloudfoundry.operations.applications.ApplicationEvent;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSshEnabledRequest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.applications.CopySourceApplicationRequest;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.DisableApplicationSshRequest;
import org.cloudfoundry.operations.applications.EnableApplicationSshRequest;
import org.cloudfoundry.operations.applications.GetApplicationEventsRequest;
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
import org.cloudfoundry.operations.applications.ListApplicationTasksRequest;
import org.cloudfoundry.operations.applications.LogsRequest;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.PushApplicationRequest;
import org.cloudfoundry.operations.applications.RenameApplicationRequest;
import org.cloudfoundry.operations.applications.RestageApplicationRequest;
import org.cloudfoundry.operations.applications.RestartApplicationInstanceRequest;
import org.cloudfoundry.operations.applications.RestartApplicationRequest;
import org.cloudfoundry.operations.applications.RunApplicationTaskRequest;
import org.cloudfoundry.operations.applications.ScaleApplicationRequest;
import org.cloudfoundry.operations.applications.SetApplicationHealthCheckRequest;
import org.cloudfoundry.operations.applications.SetEnvironmentVariableApplicationRequest;
import org.cloudfoundry.operations.applications.StartApplicationRequest;
import org.cloudfoundry.operations.applications.StopApplicationRequest;
import org.cloudfoundry.operations.applications.Task;
import org.cloudfoundry.operations.applications.TerminateApplicationTaskRequest;
import org.cloudfoundry.operations.applications.UnsetEnvironmentVariableApplicationRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public abstract class AbstractApplications implements Applications {
@Override
public Mono<Void> copySource(CopySourceApplicationRequest copySourceApplicationRequest) {
return null;
}
@Override
public Mono<Void> delete(DeleteApplicationRequest deleteApplicationRequest) {
return null;
}
@Override
public Mono<Void> disableSsh(DisableApplicationSshRequest disableApplicationSshRequest) {
return null;
}
@Override
public Mono<Void> enableSsh(EnableApplicationSshRequest enableApplicationSshRequest) {
return null;
}
@Override
public Flux<ApplicationEvent> getEvents(GetApplicationEventsRequest getApplicationEventsRequest) {
return null;
}
@Override
public Flux<ApplicationSummary> list() {
return null;
}
@Override
public Flux<Task> listTasks(ListApplicationTasksRequest listApplicationTasksRequest) {
return null;
}
@Override
public Flux<LogMessage> logs(LogsRequest logsRequest) {
return null;
}
@Override
public Mono<Void> push(PushApplicationRequest pushApplicationRequest) {
return null;
}
@Override
public Mono<Void> pushManifest(PushApplicationManifestRequest pushApplicationManifestRequest) {
return null;
}
@Override
public Mono<Void> rename(RenameApplicationRequest renameApplicationRequest) {
return null;
}
@Override
public Mono<Void> restage(RestageApplicationRequest restageApplicationRequest) {
return null;
}
@Override
public Mono<Void> restart(RestartApplicationRequest restartApplicationRequest) {
return null;
}
@Override
public Mono<Void> restartInstance(RestartApplicationInstanceRequest restartApplicationInstanceRequest) {
return null;
}
@Override
public Mono<Task> runTask(RunApplicationTaskRequest runApplicationTaskRequest) {
return null;
}
@Override
public Mono<Void> terminateTask(TerminateApplicationTaskRequest terminateApplicationTaskRequest) {
return null;
}
@Override
public Mono<Void> scale(ScaleApplicationRequest scaleApplicationRequest) {
return null;
}
@Override
public Mono<Void> setEnvironmentVariable(SetEnvironmentVariableApplicationRequest setEnvironmentVariableApplicationRequest) {
return null;
}
@Override
public Mono<Void> setHealthCheck(SetApplicationHealthCheckRequest setApplicationHealthCheckRequest) {
return null;
}
@Override
public Mono<Boolean> sshEnabled(ApplicationSshEnabledRequest applicationSshEnabledRequest) {
return null;
}
@Override
public Mono<Void> start(StartApplicationRequest startApplicationRequest) {
return null;
}
@Override
public Mono<Void> stop(StopApplicationRequest stopApplicationRequest) {
return null;
}
@Override
public Mono<Void> unsetEnvironmentVariable(UnsetEnvironmentVariableApplicationRequest unsetEnvironmentVariableApplicationRequest) {
return null;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.configuration.BatchConfiguration;
import io.spring.migrateschedule.service.MigrateScheduleService;
import io.spring.migrateschedule.service.TaskDefinitionRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SpringRunner.class)
@SpringBatchTest
@SpringBootTest
@EnableAutoConfiguration(exclude = {CloudFoundryDeployerAutoConfiguration.class})
@ContextConfiguration(classes = { BatchIntegrationTests.BatchTestConfiguration.class, BatchConfiguration.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class BatchIntegrationTests {
public static final String DEFAULT_SCHEDULE_NAME = "defaultScheduleName";
public static final String DEFAULT_TASK_DEFINITION_NAME = "defaultTaskDefinitionName";
public static final String DEFAULT_APP_NAME = "defaultAppName";
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private MigrateScheduleService migrateScheduleService;
@Autowired
private JobExplorer jobExplorer;
@MockBean
private TaskDefinitionRepository taskDefinitionRepository;
@MockBean
private Scheduler scheduler;
@Test
public void baseTest() throws Exception{
final ArgumentCaptor<Scheduler> schedulerArgumentCaptor = ArgumentCaptor.forClass(Scheduler.class);
final ArgumentCaptor<ConvertScheduleInfo> scheduleInfoArgumentCaptor = ArgumentCaptor.forClass(ConvertScheduleInfo.class);
verify(this.migrateScheduleService, times(2)).enrichScheduleMetadata(scheduleInfoArgumentCaptor.capture());
verify(this.migrateScheduleService, times(2)).migrateSchedule(schedulerArgumentCaptor.capture(), scheduleInfoArgumentCaptor.capture());
assertThat(this.jobExplorer.getJobInstanceCount("migrationJob")).isEqualTo(1);
JobInstance jobInstance = this.jobExplorer.getJobInstances("migrationJob",0, 1).get(0);
List<JobExecution> jobExecutions = this.jobExplorer.getJobExecutions(jobInstance);
assertThat(jobExecutions.size()).isEqualTo(1);
assertThat(jobExecutions.get(0).getExitStatus().getExitCode()).isEqualTo("COMPLETED");
}
@Configuration
public static class BatchTestConfiguration {
@Bean
public MigrateScheduleService convertScheduleService() {
MigrateScheduleService migrateScheduleService = mock(MigrateScheduleService.class);
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
List<ConvertScheduleInfo> schedules = new ArrayList<>();
schedules.add(scheduleInfo);
scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME + 1);
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME + 1);
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME + 1);
schedules.add(scheduleInfo);
when(migrateScheduleService.scheduleInfoList()).thenReturn(schedules);
when(migrateScheduleService.enrichScheduleMetadata(any())).thenReturn(scheduleInfo);
return migrateScheduleService;
}
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule;
import java.util.ArrayList;
import java.util.List;
import io.pivotal.scheduler.SchedulerClient;
import io.pivotal.scheduler.v1.jobs.CreateJobRequest;
import io.pivotal.scheduler.v1.jobs.CreateJobResponse;
import io.pivotal.scheduler.v1.jobs.DeleteJobRequest;
import io.pivotal.scheduler.v1.jobs.DeleteJobScheduleRequest;
import io.pivotal.scheduler.v1.jobs.ExecuteJobRequest;
import io.pivotal.scheduler.v1.jobs.ExecuteJobResponse;
import io.pivotal.scheduler.v1.jobs.GetJobRequest;
import io.pivotal.scheduler.v1.jobs.GetJobResponse;
import io.pivotal.scheduler.v1.jobs.Job;
import io.pivotal.scheduler.v1.jobs.JobSchedule;
import io.pivotal.scheduler.v1.jobs.Jobs;
import io.pivotal.scheduler.v1.jobs.ListJobHistoriesRequest;
import io.pivotal.scheduler.v1.jobs.ListJobHistoriesResponse;
import io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesRequest;
import io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesResponse;
import io.pivotal.scheduler.v1.jobs.ListJobSchedulesRequest;
import io.pivotal.scheduler.v1.jobs.ListJobSchedulesResponse;
import io.pivotal.scheduler.v1.jobs.ListJobsRequest;
import io.pivotal.scheduler.v1.jobs.ListJobsResponse;
import io.pivotal.scheduler.v1.jobs.ScheduleJobRequest;
import io.pivotal.scheduler.v1.jobs.ScheduleJobResponse;
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateProperties;
import io.spring.migrateschedule.service.TaskDefinitionRepository;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
import org.cloudfoundry.operations.applications.GetApplicationHealthCheckRequest;
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.spaces.AllowSpaceSshRequest;
import org.cloudfoundry.operations.spaces.CreateSpaceRequest;
import org.cloudfoundry.operations.spaces.DeleteSpaceRequest;
import org.cloudfoundry.operations.spaces.DisallowSpaceSshRequest;
import org.cloudfoundry.operations.spaces.GetSpaceRequest;
import org.cloudfoundry.operations.spaces.RenameSpaceRequest;
import org.cloudfoundry.operations.spaces.SpaceDetail;
import org.cloudfoundry.operations.spaces.SpaceSshAllowedRequest;
import org.cloudfoundry.operations.spaces.SpaceSummary;
import org.cloudfoundry.operations.spaces.Spaces;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import static org.assertj.core.api.Assertions.assertThat;
public class CFGetSchedulesTests {
private static final String DEFAULT_SPACE = "TESTSPACE";
private static final String DEFAULT_APPLICATION_ID = "TEST_APPLICATION_ID";
private static final String DEFAULT_COMMAND_ARG_PROP = "foo=bar";
private static final String DEFAULT_SCHEDULE_EXPRESSION = "*/1 * ? * *";
private static final String DEFAULT_COMMAND_ARG = CFMigrateSchedulerService.JAR_LAUNCHER + " " + DEFAULT_COMMAND_ARG_PROP;
private CFMigrateSchedulerService cfConvertSchedulerService;
private CloudFoundryOperations cloudFoundryOperations;
private CloudFoundryConnectionProperties cloudFoundryConnectionProperties;
private MigrateProperties migrateProperties;
private TaskDefinitionRepository taskDefinitionRepository;
private SchedulerClient schedulerClient;
private Scheduler scheduler;
@BeforeEach
public void setup() {
this.cloudFoundryOperations = Mockito.mock(CloudFoundryOperations.class);
this.schedulerClient = Mockito.mock(SchedulerClient.class);
this.cloudFoundryConnectionProperties = new CloudFoundryConnectionProperties();
this.cloudFoundryConnectionProperties.setSpace(DEFAULT_SPACE);
this.migrateProperties = new MigrateProperties();
this.taskDefinitionRepository = Mockito.mock(TaskDefinitionRepository.class);
this.scheduler = Mockito.mock(Scheduler.class);
this.cfConvertSchedulerService = new CFMigrateSchedulerService(this.cloudFoundryOperations,
this.schedulerClient,
this.cloudFoundryConnectionProperties, this.migrateProperties,
this.taskDefinitionRepository, new MavenProperties()) ;
}
@Test
public void testGetSchedules() {
Mockito.when(this.cloudFoundryOperations.applications()).thenReturn(new TestApplications());
Mockito.when(this.cloudFoundryOperations.spaces()).thenReturn(new TestSpaces());
Mockito.when(this.schedulerClient.jobs()).thenReturn(new TestJobs());
List<ConvertScheduleInfo> convertScheduleInfos = this.cfConvertSchedulerService.getSchedules(1);
assertThat(convertScheduleInfos.size()).isEqualTo(2);
ConvertScheduleInfo convertScheduleInfo = convertScheduleInfos.get(0);
baseTests(convertScheduleInfo,"JOB1", DEFAULT_APPLICATION_ID);
assertThat(convertScheduleInfo.getCommandLineArgs().size()).isEqualTo(1);
assertThat(convertScheduleInfo.getCommandLineArgs().get(0)).isEqualTo(DEFAULT_COMMAND_ARG_PROP);
convertScheduleInfo = convertScheduleInfos.get(1);
assertThat(convertScheduleInfo.getCommandLineArgs().size()).isEqualTo(0);
baseTests(convertScheduleInfo,"JOB2", DEFAULT_APPLICATION_ID);
}
private void baseTests(ConvertScheduleInfo convertScheduleInfo, String scheduleName, String taskDefinitionName) {
assertThat(convertScheduleInfo.getScheduleProperties().get("spring.cloud.scheduler.cron.expression")).isEqualTo("*/1 * ? * *");
assertThat(convertScheduleInfo.getScheduleName()).isEqualTo(scheduleName);
assertThat(convertScheduleInfo.getTaskDefinitionName()).isEqualTo(taskDefinitionName);
}
private static class TestApplications extends AbstractApplications {
@Override
public Mono<ApplicationDetail> get(GetApplicationRequest request) {
return null;
}
@Override
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest request) {
return null;
}
@Override
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest request) {
return null;
}
@Override
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest request) {
return null;
}
@Override
public Flux<ApplicationSummary> list() {
ApplicationSummary applicationSummary = ApplicationSummary.builder().
id(DEFAULT_APPLICATION_ID).
diskQuota(1024).
instances(1).
memoryLimit(1024).
name(DEFAULT_APPLICATION_ID).
requestedState("GOOD").
runningInstances(1).
build();
Flux<ApplicationSummary> applicationSummaries = Flux.just(applicationSummary);
return applicationSummaries;
}
}
private static class TestSpaces implements Spaces {
@Override
public Mono<Void> allowSsh(AllowSpaceSshRequest request) {
return null;
}
@Override
public Mono<Void> create(CreateSpaceRequest request) {
return null;
}
@Override
public Mono<Void> delete(DeleteSpaceRequest request) {
return null;
}
@Override
public Mono<Void> disallowSsh(DisallowSpaceSshRequest request) {
return null;
}
@Override
public Mono<SpaceDetail> get(GetSpaceRequest request) {
return null;
}
@Override
public Flux<SpaceSummary> list() {
SpaceSummary spaceSummary = SpaceSummary.builder().id(DEFAULT_SPACE).name(DEFAULT_SPACE).build();
Flux<SpaceSummary> spaceSummaries = Flux.just(spaceSummary);
return spaceSummaries;
}
@Override
public Mono<Void> rename(RenameSpaceRequest request) {
return null;
}
@Override
public Mono<Boolean> sshAllowed(SpaceSshAllowedRequest request) {
return null;
}
}
public static class TestJobs implements Jobs {
@Override
public Mono<CreateJobResponse> create(CreateJobRequest createJobRequest) {
return null;
}
@Override
public Mono<Void> delete(DeleteJobRequest deleteJobRequest) {
return null;
}
@Override
public Mono<Void> deleteSchedule(DeleteJobScheduleRequest deleteJobScheduleRequest) {
return null;
}
@Override
public Mono<ExecuteJobResponse> execute(ExecuteJobRequest executeJobRequest) {
return null;
}
@Override
public Mono<GetJobResponse> get(GetJobRequest getJobRequest) {
return null;
}
@Override
public Mono<ListJobsResponse> list(ListJobsRequest listJobsRequest) {
Job job = Job.builder().id("JOB1").name("JOB1").
command(DEFAULT_COMMAND_ARG).
applicationId(DEFAULT_APPLICATION_ID).
jobSchedule(JobSchedule.builder().
expression("*/1 * ? * *").
build()).
build();
List<Job> jobList = new ArrayList<>();
jobList.add(job);
job = Job.builder().id("JOB2").name("JOB2").
applicationId(DEFAULT_APPLICATION_ID).
jobSchedule(JobSchedule.builder().
expression(DEFAULT_SCHEDULE_EXPRESSION).
build()).
build();
jobList.add(job);
ListJobsResponse listJobsResponse = ListJobsResponse.builder().addAllResources(jobList).build();
return Mono.just(listJobsResponse);
}
@Override
public Mono<ListJobHistoriesResponse> listHistories(ListJobHistoriesRequest listJobHistoriesRequest) {
return null;
}
@Override
public Mono<ListJobScheduleHistoriesResponse> listScheduleHistories(ListJobScheduleHistoriesRequest listJobScheduleHistoriesRequest) {
return null;
}
@Override
public Mono<ListJobSchedulesResponse> listSchedules(ListJobSchedulesRequest listJobSchedulesRequest) {
return null;
}
@Override
public Mono<ScheduleJobResponse> schedule(ScheduleJobRequest scheduleJobRequest) {
return null;
}
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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 io.spring.migrateschedule;
import java.util.HashMap;
import io.pivotal.scheduler.SchedulerClient;
import io.spring.migrateschedule.service.ConvertScheduleInfo;
import io.spring.migrateschedule.service.MigrateProperties;
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
import io.spring.migrateschedule.service.TaskDefinitionRepository;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
import org.cloudfoundry.operations.applications.GetApplicationHealthCheckRequest;
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import org.springframework.cloud.dataflow.core.TaskDefinition;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class CFMigrateScheduleConfigurationTests {
public static final String DEFAULT_SCHEDULE_NAME = "defaultScheduleName";
public static final String DEFAULT_TASK_DEFINITION_NAME = "defaultTaskDefinitionName";
public static final String DEFAULT_APP_NAME = "defaultAppName";
public static final String DEFAULT_CMD_ARG = "defaultCmd=WOW";
public static final String DEFAULT_BUILD_PACK = "defaultBuildPack";
private CFMigrateSchedulerService cfConvertSchedulerService;
private CloudFoundryOperations cloudFoundryOperations;
private CloudFoundryConnectionProperties cloudFoundryConnectionProperties;
private MigrateProperties migrateProperties;
private TaskDefinitionRepository taskDefinitionRepository;
private SchedulerClient schedulerClient;
private Scheduler scheduler;
@BeforeEach
public void setup() {
this.cloudFoundryOperations = Mockito.mock(CloudFoundryOperations.class);
this.schedulerClient = Mockito.mock(SchedulerClient.class);
this.cloudFoundryConnectionProperties = new CloudFoundryConnectionProperties();
this.migrateProperties = new MigrateProperties();
this.taskDefinitionRepository = Mockito.mock(TaskDefinitionRepository.class);
this.scheduler = Mockito.mock(Scheduler.class);
this.cfConvertSchedulerService = new CFMigrateSchedulerService(this.cloudFoundryOperations,
this.schedulerClient,
this.cloudFoundryConnectionProperties, this.migrateProperties,
this.taskDefinitionRepository, new MavenProperties()) ;
}
@Test
public void testEnrichment() {
ConvertScheduleInfo scheduleInfo = createFoundationConvertScheduleInfo();
assertThat(scheduleInfo.getAppProperties().keySet().size()).isEqualTo(5);
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.app.defaultAppName.foo")).isEqualTo("bar");
assertThat(scheduleInfo.getAppProperties().get("spring.cloud.dataflow.client.serverUri")).isEqualTo("http://localhost:9393");
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.memory")).isEqualTo("1024m");
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.health-check")).isEqualTo("port");
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.disk")).isEqualTo("1024m");
assertThat(scheduleInfo.getCommandLineArgs().size()).isEqualTo(2);
assertThat(scheduleInfo.getCommandLineArgs().get(0)).isEqualTo("cmdarg.tasklauncher.defaultCmd=WOW");
assertThat(scheduleInfo.getCommandLineArgs().get(1)).isEqualTo("--spring.cloud.scheduler.task.launcher.taskName=defaultTaskDefinitionName");
}
@Test
public void testEnrichmentNoProps() {
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new NoPropertyApplication());
TaskDefinition taskDefinition = TaskDefinition.TaskDefinitionBuilder
.from(new TaskDefinition("fooTask", "foo"))
.setTaskName(DEFAULT_TASK_DEFINITION_NAME)
.setRegisteredAppName(DEFAULT_APP_NAME)
.build();
Mockito.when(this.taskDefinitionRepository.findByTaskName(Mockito.any())).thenReturn(taskDefinition);
scheduleInfo = this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo);
assertThat(scheduleInfo.getAppProperties().keySet().size()).isEqualTo(1);
assertThat(scheduleInfo.getAppProperties().get("spring.cloud.dataflow.client.serverUri")).isEqualTo("http://localhost:9393");
assertThat(scheduleInfo.getCommandLineArgs().size()).isEqualTo(1);
assertThat(scheduleInfo.getCommandLineArgs().get(0)).isEqualTo("--spring.cloud.scheduler.task.launcher.taskName=defaultTaskDefinitionName");
}
@Test
public void testNoTaskDefinition() {
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
scheduleInfo.getCommandLineArgs().add(DEFAULT_CMD_ARG);
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new SinglePropertyApplication());
assertThrows(IllegalStateException.class, () -> this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo));
}
@Test
public void testMigrate() {
ConvertScheduleInfo scheduleInfo = createFoundationConvertScheduleInfo();
this.cfConvertSchedulerService.migrateSchedule(this.scheduler, scheduleInfo);
final ArgumentCaptor<ScheduleRequest> scheduleRequestArgument = ArgumentCaptor.forClass(ScheduleRequest.class);
final ArgumentCaptor<String> scheduleNameArg = ArgumentCaptor.forClass(String.class);
verify(this.scheduler, times(1)).schedule(scheduleRequestArgument.capture());
verify(this.scheduler, times(1)).unschedule(scheduleNameArg.capture());
assertThat(scheduleRequestArgument.getValue().getScheduleName()).isEqualTo("defaultScheduleName-scdf-defaultTaskDefinitionName");
assertThat(scheduleNameArg.getValue()).isEqualTo(DEFAULT_SCHEDULE_NAME);
}
private ConvertScheduleInfo createFoundationConvertScheduleInfo() {
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
scheduleInfo.setScheduleProperties(new HashMap<>());
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
scheduleInfo.getCommandLineArgs().add(DEFAULT_CMD_ARG);
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new SinglePropertyApplication());
TaskDefinition taskDefinition = TaskDefinition.TaskDefinitionBuilder
.from(new TaskDefinition("fooTask", "foo"))
.setTaskName(DEFAULT_TASK_DEFINITION_NAME)
.setRegisteredAppName(DEFAULT_APP_NAME)
.build();
Mockito.when(this.taskDefinitionRepository.findByTaskName(Mockito.any())).thenReturn(taskDefinition);
return this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo);
}
public static class SinglePropertyApplication extends AbstractApplications {
@Override
public Mono<ApplicationDetail> get(GetApplicationRequest getApplicationRequest) {
ApplicationDetail applicationDetail = ApplicationDetail.builder()
.buildpack(DEFAULT_BUILD_PACK)
.stack("defaultstack")
.diskQuota(1024)
.id("defaultappidone")
.memoryLimit(1024)
.instances(1)
.name(DEFAULT_TASK_DEFINITION_NAME)
.requestedState("requestedState")
.runningInstances(1)
.build();
return Mono.just(applicationDetail);
}
@Override
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest getApplicationEnvironmentsRequest) {
return Mono.just(ApplicationEnvironments.builder().userProvided("SPRING_APPLICATION_JSON", "{\"foo\":\"bar\"}").build());
}
@Override
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest getApplicationHealthCheckRequest) {
return Mono.just(ApplicationHealthCheck.PORT);
}
@Override
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest getApplicationManifestRequest) {
return Mono.just(ApplicationManifest.builder().name(DEFAULT_TASK_DEFINITION_NAME)
.disk(1024)
.memory(1024)
.healthCheckType(ApplicationHealthCheck.PORT)
.build());
}
}
public static class NoPropertyApplication extends AbstractApplications {
@Override
public Mono<ApplicationDetail> get(GetApplicationRequest getApplicationRequest) {
return Mono.empty();
}
@Override
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest getApplicationEnvironmentsRequest) {
return Mono.empty();
}
@Override
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest getApplicationHealthCheckRequest) {
return Mono.empty();
}
@Override
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest getApplicationManifestRequest) {
return Mono.empty();
}
}
}