getJobMono(String jobName, int page) {
return this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {
return this.client
.jobs()
.list(ListJobsRequest.builder()
.spaceId(requestSummary.getId())
.page(page)
.build()); })
.flatMapIterable(jobs -> jobs.getResources())
.filter(job -> job.getName().equals(jobName))
.singleOrEmpty();// iterate over the resources returned.
}
/**
* Retrieve the job id for the specified PCF Job Name.
* @param jobName the name of the job to search.
* @return The job id associated with the job.
*/
private String getJob(String jobName) {
Job result = null;
final int pageCount = getJobPageCount();
for (int pageNum = PCF_PAGE_START_NUM; pageNum <= pageCount; pageNum++) {
result = getJobMono(jobName, pageNum)
.block();
if (result != null) {
break;
}
}
if(result == null) {
throw new UnScheduleException(String.format("schedule %s does not exist.", jobName));
}
return result.getId();
}
private RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
this.deploymentProperties.getScheduleSSLRetryCount(),
Collections.singletonMap(CloudFoundryScheduleSSLException.class, true));
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryScheduleSSLException.java
================================================
/*
* Copyright 2018-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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
/**
* A {@link RuntimeException} that wraps SSL based exceptions.
*
* @author Glenn Renfro
*/
public class CloudFoundryScheduleSSLException extends RuntimeException {
public CloudFoundryScheduleSSLException(String message, Throwable t) {
super(message, t);
}
public CloudFoundryScheduleSSLException(String t) {
super(t);
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundrySchedulerProperties.java
================================================
/*
* Copyright 2018-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
* Holds configuration properties for connecting to a Cloud Foundry Scheduler.
*
* @author Glenn Renfro
*/
@Validated
@ConfigurationProperties(prefix = CloudFoundrySchedulerProperties.CLOUDFOUNDRY_PROPERTIES)
@Deprecated
public class CloudFoundrySchedulerProperties {
/**
* Top level prefix for Cloud Foundry related configuration properties.
*/
public static final String CLOUDFOUNDRY_PROPERTIES = "spring.cloud.scheduler.cloudfoundry";
/**
* Location of the PCF scheduler REST API enpoint ot use.
*/
@NotNull
private String schedulerUrl;
/**
* The number of retries allowed when scheduling a task if an {@link javax.net.ssl.SSLException} is thrown.
*/
private int scheduleSSLRetryCount = 5;
/**
* The number of seconds to wait for a unSchedule to complete.
*/
private int unScheduleTimeoutInSeconds = 30;
/**
* 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;
/**
* The number of seconds to wait for a list of schedules to be returned.
*/
private int listTimeoutInSeconds = 60;
public String getSchedulerUrl() {
return schedulerUrl;
}
public void setSchedulerUrl(String schedulerUrl) {
this.schedulerUrl = schedulerUrl;
}
public int getScheduleSSLRetryCount() {
return scheduleSSLRetryCount;
}
public void setScheduleSSLRetryCount(int scheduleSSLRetryCount) {
this.scheduleSSLRetryCount = scheduleSSLRetryCount;
}
public int getUnScheduleTimeoutInSeconds() {
return unScheduleTimeoutInSeconds;
}
public void setUnScheduleTimeoutInSeconds(int unScheduleTimeoutInSeconds) {
this.unScheduleTimeoutInSeconds = unScheduleTimeoutInSeconds;
}
public int getScheduleTimeoutInSeconds() {
return scheduleTimeoutInSeconds;
}
public void setScheduleTimeoutInSeconds(int scheduleTimeoutInSeconds) {
this.scheduleTimeoutInSeconds = scheduleTimeoutInSeconds;
}
public int getListTimeoutInSeconds() {
return listTimeoutInSeconds;
}
public void setListTimeoutInSeconds(int listTimeoutInSeconds) {
this.listTimeoutInSeconds = listTimeoutInSeconds;
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/expression/QuartzCronExpression.java
================================================
/*
* Copyright 2018-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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.expression;
import java.text.ParseException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeSet;
/**
* Provides a parser and evaluator for unix-like cron expressions. Cron
* expressions provide the ability to specify complex time combinations such as
* "At 8:00am every Monday through Friday" or "At 1:30am every
* last Friday of the month".
*
* Cron expressions are comprised of 6 required fields and one optional field
* separated by white space.
*
* Based on the CronExpression from Quartz.
*
* @author Glenn Renfro
*/
public final class QuartzCronExpression {
private static final int SECOND = 0;
private static final int MINUTE = 1;
private static final int HOUR = 2;
private static final int DAY_OF_MONTH = 3;
private static final int MONTH = 4;
private static final int DAY_OF_WEEK = 5;
private static final int YEAR = 6;
private static final int ALL_SPEC_INT = 99; // '*'
private static final int NO_SPEC_INT = 98; // '?'
private static final Integer ALL_SPEC = ALL_SPEC_INT;
private static final Integer NO_SPEC = NO_SPEC_INT;
protected static final Map monthMap = new HashMap(20);
protected static final Map dayMap = new HashMap(60);
static {
monthMap.put("JAN", 0);
monthMap.put("FEB", 1);
monthMap.put("MAR", 2);
monthMap.put("APR", 3);
monthMap.put("MAY", 4);
monthMap.put("JUN", 5);
monthMap.put("JUL", 6);
monthMap.put("AUG", 7);
monthMap.put("SEP", 8);
monthMap.put("OCT", 9);
monthMap.put("NOV", 10);
monthMap.put("DEC", 11);
dayMap.put("SUN", 1);
dayMap.put("MON", 2);
dayMap.put("TUE", 3);
dayMap.put("WED", 4);
dayMap.put("THU", 5);
dayMap.put("FRI", 6);
dayMap.put("SAT", 7);
}
private final String cronExpression;
private TreeSet seconds;
private TreeSet minutes;
private TreeSet hours;
private TreeSet daysOfMonth;
private TreeSet months;
private TreeSet daysOfWeek;
private TreeSet years;
private int nthdayOfWeek = 0;
private boolean lastdayOfMonth = false;
private int lastdayOffset = 0;
public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
/**
* Constructs a new {@link QuartzCronExpression} based on the specified
* parameter.
*
* @param cronExpression String representation of the cron expression the
* new object should represent
* @throws ParseException
* if the string expression cannot be parsed into a valid
* {@link QuartzCronExpression}
*/
public QuartzCronExpression(String cronExpression) throws ParseException {
if (cronExpression == null) {
throw new IllegalArgumentException("cronExpression cannot be null");
}
this.cronExpression = cronExpression.toUpperCase(Locale.ROOT);
buildExpression(this.cronExpression);
}
/**
* Returns the string representation of the {@link QuartzCronExpression}
*
* @return a string representation of the {@link QuartzCronExpression}
*/
@Override
public String toString() {
return cronExpression;
}
////////////////////////////////////////////////////////////////////////////
//
// Expression Parsing Functions
//
////////////////////////////////////////////////////////////////////////////
protected void buildExpression(String expression) throws ParseException {
try {
if (seconds == null) {
seconds = new TreeSet<>();
}
if (minutes == null) {
minutes = new TreeSet<>();
}
if (hours == null) {
hours = new TreeSet<>();
}
if (daysOfMonth == null) {
daysOfMonth = new TreeSet<>();
}
if (months == null) {
months = new TreeSet<>();
}
if (daysOfWeek == null) {
daysOfWeek = new TreeSet<>();
}
if (years == null) {
years = new TreeSet<>();
}
int exprOn = SECOND;
StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
false);
while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
String expr = exprsTok.nextToken().trim();
// throw an exception if L is used with other days of the month
if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
}
// throw an exception if L is used with other days of the week
if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
}
if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
}
StringTokenizer vTok = new StringTokenizer(expr, ",");
while (vTok.hasMoreTokens()) {
String v = vTok.nextToken();
storeExpressionVals(0, v, exprOn);
}
exprOn++;
}
if (exprOn <= DAY_OF_WEEK) {
throw new ParseException("Unexpected end of expression.",
expression.length());
}
if (exprOn <= YEAR) {
storeExpressionVals(0, "*", YEAR);
}
TreeSet dow = getSet(DAY_OF_WEEK);
TreeSet dom = getSet(DAY_OF_MONTH);
// Copying the logic from the UnsupportedOperationException below
boolean dayOfMSpec = !dom.contains(NO_SPEC);
boolean dayOfWSpec = !dow.contains(NO_SPEC);
if (!dayOfMSpec || dayOfWSpec) {
if (!dayOfWSpec || dayOfMSpec) {
throw new ParseException(
"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
}
}
} catch (ParseException pe) {
throw pe;
} catch (Exception e) {
throw new ParseException("Illegal cron expression format ("
+ e.toString() + ")", 0);
}
}
private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
if (incr > 59 && (type == SECOND || type == MINUTE)) {
throw new ParseException("Increment > 60 : " + incr, idxPos);
} else if (incr > 23 && (type == HOUR)) {
throw new ParseException("Increment > 24 : " + incr, idxPos);
} else if (incr > 31 && (type == DAY_OF_MONTH)) {
throw new ParseException("Increment > 31 : " + incr, idxPos);
} else if (incr > 7 && (type == DAY_OF_WEEK)) {
throw new ParseException("Increment > 7 : " + incr, idxPos);
} else if (incr > 12 && (type == MONTH)) {
throw new ParseException("Increment > 12 : " + incr, idxPos);
}
}
protected int checkNext(int pos, String s, int val, int type)
throws ParseException {
int end = -1;
int i = pos;
if (i >= s.length()) {
addToSet(val, end, -1, type);
return i;
}
char c = s.charAt(pos);
if (c == 'L') {
if (type == DAY_OF_WEEK) {
if(val < 1 || val > 7)
throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
} else {
throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
}
TreeSet set = getSet(type);
set.add(val);
i++;
return i;
}
if (c == 'W') {
if (type != DAY_OF_MONTH) {
throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
}
if(val > 31)
throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
TreeSet set = getSet(type);
set.add(val);
i++;
return i;
}
if (c == '#') {
if (type != DAY_OF_WEEK) {
throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
}
i++;
try {
nthdayOfWeek = Integer.parseInt(s.substring(i));
if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
throw new Exception();
}
} catch (Exception e) {
throw new ParseException(
"A numeric value between 1 and 5 must follow the '#' option",
i);
}
TreeSet set = getSet(type);
set.add(val);
i++;
return i;
}
if (c == '-') {
i++;
c = s.charAt(i);
int v = Integer.parseInt(String.valueOf(c));
end = v;
i++;
if (i >= s.length()) {
addToSet(val, end, 1, type);
return i;
}
c = s.charAt(i);
if (c >= '0' && c <= '9') {
ValueSet vs = getValue(v, s, i);
end = vs.value;
i = vs.pos;
}
if (i < s.length() && ((c = s.charAt(i)) == '/')) {
i++;
c = s.charAt(i);
int v2 = Integer.parseInt(String.valueOf(c));
i++;
if (i >= s.length()) {
addToSet(val, end, v2, type);
return i;
}
c = s.charAt(i);
if (c >= '0' && c <= '9') {
ValueSet vs = getValue(v2, s, i);
int v3 = vs.value;
addToSet(val, end, v3, type);
i = vs.pos;
return i;
} else {
addToSet(val, end, v2, type);
return i;
}
} else {
addToSet(val, end, 1, type);
return i;
}
}
if (c == '/') {
if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
throw new ParseException("'/' must be followed by an integer.", i);
}
i++;
c = s.charAt(i);
int v2 = Integer.parseInt(String.valueOf(c));
i++;
if (i >= s.length()) {
checkIncrementRange(v2, type, i);
addToSet(val, end, v2, type);
return i;
}
c = s.charAt(i);
if (c >= '0' && c <= '9') {
ValueSet vs = getValue(v2, s, i);
int v3 = vs.value;
checkIncrementRange(v3, type, i);
addToSet(val, end, v3, type);
i = vs.pos;
return i;
} else {
throw new ParseException("Unexpected character '" + c + "' after '/'", i);
}
}
addToSet(val, end, 0, type);
i++;
return i;
}
protected int skipWhiteSpace(int i, String s) {
for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
;
}
return i;
}
protected int findNextWhiteSpace(int i, String s) {
for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
;
}
return i;
}
protected void addToSet(int val, int end, int incr, int type)
throws ParseException {
TreeSet set = getSet(type);
if (type == SECOND || type == MINUTE) {
if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
throw new ParseException(
"Minute and Second values must be between 0 and 59",
-1);
}
} else if (type == HOUR) {
if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
throw new ParseException(
"Hour values must be between 0 and 23", -1);
}
} else if (type == DAY_OF_MONTH) {
if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
&& (val != NO_SPEC_INT)) {
throw new ParseException(
"Day of month values must be between 1 and 31", -1);
}
} else if (type == MONTH) {
if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
throw new ParseException(
"Month values must be between 1 and 12", -1);
}
} else if (type == DAY_OF_WEEK) {
if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
&& (val != NO_SPEC_INT)) {
throw new ParseException(
"Day-of-Week values must be between 1 and 7", -1);
}
}
if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
if (val != -1) {
set.add(val);
} else {
set.add(NO_SPEC);
}
return;
}
int startAt = val;
int stopAt = end;
if (val == ALL_SPEC_INT && incr <= 0) {
incr = 1;
set.add(ALL_SPEC); // put in a marker, but also fill values
}
if (type == SECOND || type == MINUTE) {
if (stopAt == -1) {
stopAt = 59;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 0;
}
} else if (type == HOUR) {
if (stopAt == -1) {
stopAt = 23;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 0;
}
} else if (type == DAY_OF_MONTH) {
if (stopAt == -1) {
stopAt = 31;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 1;
}
} else if (type == MONTH) {
if (stopAt == -1) {
stopAt = 12;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 1;
}
} else if (type == DAY_OF_WEEK) {
if (stopAt == -1) {
stopAt = 7;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 1;
}
} else if (type == YEAR) {
if (stopAt == -1) {
stopAt = MAX_YEAR;
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 1970;
}
}
// if the end of the range is before the start, then we need to overflow into
// the next day, month etc. This is done by adding the maximum amount for that
// type, and using modulus max to determine the value being added.
int max = -1;
if (stopAt < startAt) {
switch (type) {
case SECOND:
max = 60;
break;
case MINUTE:
max = 60;
break;
case HOUR:
max = 24;
break;
case MONTH:
max = 12;
break;
case DAY_OF_WEEK:
max = 7;
break;
case DAY_OF_MONTH:
max = 31;
break;
case YEAR:
throw new IllegalArgumentException("Start year must be less than stop year");
default:
throw new IllegalArgumentException("Unexpected type encountered");
}
stopAt += max;
}
for (int i = startAt; i <= stopAt; i += incr) {
if (max == -1) {
// ie: there's no max to overflow over
set.add(i);
} else {
// take the modulus to get the real value
int i2 = i % max;
// 1-indexed ranges should not include 0, and should include their max
if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
i2 = max;
}
set.add(i2);
}
}
}
TreeSet getSet(int type) {
switch (type) {
case SECOND:
return seconds;
case MINUTE:
return minutes;
case HOUR:
return hours;
case DAY_OF_MONTH:
return daysOfMonth;
case MONTH:
return months;
case DAY_OF_WEEK:
return daysOfWeek;
case YEAR:
return years;
default:
return null;
}
}
protected ValueSet getValue(int v, String s, int i) {
char c = s.charAt(i);
StringBuilder s1 = new StringBuilder(String.valueOf(v));
while (c >= '0' && c <= '9') {
s1.append(c);
i++;
if (i >= s.length()) {
break;
}
c = s.charAt(i);
}
ValueSet val = new ValueSet();
val.pos = (i < s.length()) ? i : i + 1;
val.value = Integer.parseInt(s1.toString());
return val;
}
protected int getNumericValue(String s, int i) {
int endOfVal = findNextWhiteSpace(i, s);
String val = s.substring(i, endOfVal);
return Integer.parseInt(val);
}
protected int getMonthNumber(String s) {
Integer integer = monthMap.get(s);
if (integer == null) {
return -1;
}
return integer;
}
protected int getDayOfWeekNumber(String s) {
Integer integer = dayMap.get(s);
if (integer == null) {
return -1;
}
return integer;
}
protected int storeExpressionVals(int pos, String s, int type)
throws ParseException {
int incr = 0;
int i = skipWhiteSpace(pos, s);
if (i >= s.length()) {
return i;
}
char c = s.charAt(i);
if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
String sub = s.substring(i, i + 3);
int sval = -1;
int eval = -1;
if (type == MONTH) {
sval = getMonthNumber(sub) + 1;
if (sval <= 0) {
throw new ParseException("Invalid Month value: '" + sub + "'", i);
}
if (s.length() > i + 3) {
c = s.charAt(i + 3);
if (c == '-') {
i += 4;
sub = s.substring(i, i + 3);
eval = getMonthNumber(sub) + 1;
if (eval <= 0) {
throw new ParseException("Invalid Month value: '" + sub + "'", i);
}
}
}
} else if (type == DAY_OF_WEEK) {
sval = getDayOfWeekNumber(sub);
if (sval < 0) {
throw new ParseException("Invalid Day-of-Week value: '"
+ sub + "'", i);
}
if (s.length() > i + 3) {
c = s.charAt(i + 3);
if (c == '-') {
i += 4;
sub = s.substring(i, i + 3);
eval = getDayOfWeekNumber(sub);
if (eval < 0) {
throw new ParseException(
"Invalid Day-of-Week value: '" + sub
+ "'", i);
}
} else if (c == '#') {
try {
i += 4;
nthdayOfWeek = Integer.parseInt(s.substring(i));
if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
throw new Exception();
}
} catch (Exception e) {
throw new ParseException(
"A numeric value between 1 and 5 must follow the '#' option",
i);
}
} else if (c == 'L') {
i++;
}
}
} else {
throw new ParseException(
"Illegal characters for this position: '" + sub + "'",
i);
}
if (eval != -1) {
incr = 1;
}
addToSet(sval, eval, incr, type);
return (i + 3);
}
if (c == '?') {
i++;
if ((i + 1) < s.length()
&& (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
throw new ParseException("Illegal character after '?': "
+ s.charAt(i), i);
}
if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
throw new ParseException(
"'?' can only be specified for Day-of-Month or Day-of-Week.",
i);
}
if (type == DAY_OF_WEEK && !lastdayOfMonth) {
int val = daysOfMonth.last();
if (val == NO_SPEC_INT) {
throw new ParseException(
"'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
i);
}
}
addToSet(NO_SPEC_INT, -1, 0, type);
return i;
}
if (c == '*' || c == '/') {
if (c == '*' && (i + 1) >= s.length()) {
addToSet(ALL_SPEC_INT, -1, incr, type);
return i + 1;
} else if (c == '/'
&& ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
.charAt(i + 1) == '\t')) {
throw new ParseException("'/' must be followed by an integer.", i);
} else if (c == '*') {
i++;
}
c = s.charAt(i);
if (c == '/') { // is an increment specified?
i++;
if (i >= s.length()) {
throw new ParseException("Unexpected end of string.", i);
}
incr = getNumericValue(s, i);
i++;
if (incr > 10) {
i++;
}
checkIncrementRange(incr, type, i);
} else {
incr = 1;
}
addToSet(ALL_SPEC_INT, -1, incr, type);
return i;
} else if (c == 'L') {
i++;
if (type == DAY_OF_MONTH) {
lastdayOfMonth = true;
}
if (type == DAY_OF_WEEK) {
addToSet(7, 7, 0, type);
}
if(type == DAY_OF_MONTH && s.length() > i) {
c = s.charAt(i);
if(c == '-') {
ValueSet vs = getValue(0, s, i+1);
lastdayOffset = vs.value;
if(lastdayOffset > 30)
throw new ParseException("Offset from last day must be <= 30", i+1);
i = vs.pos;
}
if(s.length() > i) {
c = s.charAt(i);
if(c == 'W') {
i++;
}
}
}
return i;
} else if (c >= '0' && c <= '9') {
int val = Integer.parseInt(String.valueOf(c));
i++;
if (i >= s.length()) {
addToSet(val, -1, -1, type);
} else {
c = s.charAt(i);
if (c >= '0' && c <= '9') {
ValueSet vs = getValue(val, s, i);
val = vs.value;
i = vs.pos;
}
i = checkNext(i, s, val, type);
return i;
}
} else {
throw new ParseException("Unexpected character: " + c, i);
}
return i;
}
public static class ValueSet {
public int value;
public int pos;
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/additional-spring-configuration-metadata.json
================================================
{
"hints": [
{
"name": "spring.cloud.deployer.cloudfoundry.health-check",
"values": [
{
"value": "NONE"
},
{
"value": "HTTP"
},
{
"value": "PROCESS"
},
{
"value": "PORT"
}
]
}
]
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
================================================
org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/spring.factories
================================================
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractAppDeployerTestSupport.java
================================================
/*
* Copyright 2022 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.util.HashMap;
import java.util.Map;
import org.cloudfoundry.logcache.v1.LogCacheClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.ScaleApplicationRequest;
import org.cloudfoundry.operations.services.Services;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.context.ApplicationContext;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
/**
* @author David Turanski
*/
public abstract class AbstractAppDeployerTestSupport {
protected final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();
@Mock(answer = Answers.RETURNS_SMART_NULLS)
protected AppNameGenerator applicationNameGenerator;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
protected Applications applications;
protected CloudFoundryAppDeployer deployer;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
@MockBean
protected CloudFoundryOperations operations;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
protected Services services;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
protected RuntimeEnvironmentInfo runtimeEnvironmentInfo;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
@MockBean
protected LogCacheClient reactorLogCacheClient;
@Autowired
protected ApplicationContext applicationContext;
@Mock
protected ApplicationLogAccessor applicationLogAccessor;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
given(this.operations.applications()).willReturn(this.applications);
given(this.operations.services()).willReturn(this.services);
this.deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator, this.deploymentProperties,
this.operations, this.runtimeEnvironmentInfo, this.applicationLogAccessor);
postSetUp();
}
protected abstract void postSetUp();
protected void givenRequestScaleApplication(String id, Integer count, int memoryLimit, int diskLimit,
Mono response) {
given(this.operations.applications()
.scale(ScaleApplicationRequest.builder().name(id).instances(count).memoryLimit(memoryLimit)
.diskLimit(diskLimit)
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.stagingTimeout(this.deploymentProperties.getStagingTimeout()).build())).willReturn(response);
}
protected void givenRequestDeleteApplication(String id, Mono response) {
given(this.operations.applications()
.delete(DeleteApplicationRequest.builder().deleteRoutes(true).name(id).build())).willReturn(response);
}
@SuppressWarnings("unchecked")
protected void givenRequestGetApplication(String id, Mono response,
Mono... responses) {
given(this.operations.applications().get(GetApplicationRequest.builder().name(id).build())).willReturn(response,
responses);
}
protected void givenRequestPushApplication(PushApplicationManifestRequest request, Mono response) {
given(this.operations.applications()
.pushManifest(any(PushApplicationManifestRequest.class)))
.willReturn(response);
}
protected Map defaultEnvironmentVariables() {
Map environmentVariables = new HashMap<>();
environmentVariables.put("SPRING_APPLICATION_JSON", "{}");
addGuidAndIndex(environmentVariables);
return environmentVariables;
}
protected void addGuidAndIndex(Map environmentVariables) {
environmentVariables.put("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}");
environmentVariables.put("SPRING_CLOUD_APPLICATION_GUID",
"${vcap.application.name}:${vcap.application.instance_index}");
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/ApplicationLogAccessorTests.java
================================================
/*
* Copyright 2023 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import org.cloudfoundry.logcache.v1.Envelope;
import org.cloudfoundry.logcache.v1.EnvelopeBatch;
import org.cloudfoundry.logcache.v1.Log;
import org.cloudfoundry.logcache.v1.LogCacheClient;
import org.cloudfoundry.logcache.v1.ReadResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Base64;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class ApplicationLogAccessorTests {
@Mock
private LogCacheClient logCacheClient;
@Test
void testDefaultCase() {
String sampleData = "foo\nbar\nbaz\nboo";
String exectedResult = "boo\nbaz\nbar\nfoo";
when(logCacheClient.read(any())).thenReturn(getSampleResponse(sampleData));
ApplicationLogAccessor applicationLogAccessor = new ApplicationLogAccessor(this.logCacheClient);
assertThat(applicationLogAccessor.getLog("myDeploymentId", Duration.ofSeconds(5))).isEqualTo(exectedResult);
}
private Mono getSampleResponse(String sampleData) {
Envelope envelope = Envelope.builder().log(getSampleLog(sampleData)).build();
EnvelopeBatch envelopeBatch = EnvelopeBatch.builder().batch(envelope).build();
return Mono.just(ReadResponse.builder().envelopes(envelopeBatch).build());
}
private Log getSampleLog(String sampleData) {
return Log.builder().payload(Base64.getEncoder().encodeToString(sampleData.getBytes())).build();
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvAwareResourceTests.java
================================================
/*
* Copyright 2020-2024 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author David Turanski
*/
public class CfEnvAwareResourceTests {
@Test
public void testCfEnvResolverWithCfEnvJava17() throws IOException {
// this task app is compiled from a spring-cloud-task sample using jdk17
// and cfenv added to build
CfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource("timestamp-task-3.1.2-SNAPSHOT.jar"));
assertThat(resource.hasCfEnv()).isTrue();
}
@Test
public void testCfEnvResolverWithCfEnv() throws IOException {
CfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource("log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar"));
assertThat(resource.hasCfEnv()).isTrue();
}
@Test
public void testCfEnvResolverWithNoCfEnv() throws IOException {
CfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource("batch-job-1.0.0.BUILD-SNAPSHOT.jar"));
assertThat(resource.hasCfEnv()).isFalse();
}
@Test
public void testDoesNothingWithDocker() throws IOException, URISyntaxException {
Resource docker = mock(Resource.class);
when(docker.getURI()).thenReturn(new URI("docker://fake/news"));
assertThat(CfEnvAwareResource.of(docker).hasCfEnv()).isFalse();
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvConfigurerTests.java
================================================
/*
* Copyright 2020-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.util.Collections;
import java.util.Map;
import org.cloudfoundry.util.FluentMap;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CfEnvConfigurerTests {
@Test
public void testCfEnvConfigurerActivateCloudProfile() {
Map env = CfEnvConfigurer.activateCloudProfile(
Collections.emptyMap(), CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN);
assertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,
CfEnvConfigurer.CLOUD_PROFILE_NAME);
assertThat(env).doesNotContainKeys(
CfEnvConfigurer.SPRING_PROFILES_ACTIVE,
CfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);
}
@Test
public void testCfEnvConfigurerDoNotActivateCloudProfileIfNoneExists() {
Map env = CfEnvConfigurer.activateCloudProfile(
Collections.emptyMap(), null);
assertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,
CfEnvConfigurer.SPRING_PROFILES_ACTIVE,
CfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);
}
@Test
public void testCfEnvConfigurerActivateCloudProfileWithEmptyValue() {
Map env = CfEnvConfigurer.activateCloudProfile(
FluentMap.builder()
.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE, " ")
.build(),
CfEnvConfigurer.SPRING_PROFILES_ACTIVE);
assertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE,
CfEnvConfigurer.CLOUD_PROFILE_NAME);
assertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,
CfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);
}
@Test
public void testCfEnvConfigurerAppendToActiveProfiles() {
Map env = CfEnvConfigurer.activateCloudProfile(
FluentMap.builder()
.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "foo,bar")
.build(), null);
assertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "foo,bar,cloud");
assertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE,
CfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);
}
@Test
public void testCfEnvConfigurerDoesNotAppendCloudProfileIfAlreadyThere() {
Map env = CfEnvConfigurer.activateCloudProfile(
FluentMap.builder()
.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "foo,cloud")
.build(), null);
assertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "foo,cloud");
env = CfEnvConfigurer.activateCloudProfile(
FluentMap.builder()
.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "cloud,foo,bar")
.build(), null);
assertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, "cloud,foo,bar");
}
@Test
public void testCloudProfileAppendedToCommandLineArgs() {
assertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg("--spring-profiles-active=foo,bar"))
.isEqualTo("--spring-profiles-active=foo,bar,cloud");
assertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg("--spring.profiles.active=foo"))
.isEqualTo("--spring.profiles.active=foo,cloud");
assertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg("--spring.profiles.active=foo,cloud"))
.isEqualTo("--spring.profiles.active=foo,cloud");
assertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg("--spring.profiles.active=cloud"))
.isEqualTo("--spring.profiles.active=cloud");
assertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg("--baz=foo"))
.isEqualTo("--baz=foo");
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryActuatorTemplateTests.java
================================================
/*
* Copyright 2022-2025 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.InstanceDetail;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.cloud.deployer.spi.app.ActuatorOperations;
import org.springframework.cloud.deployer.spi.app.AppAdmin;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Mono;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
public class CloudFoundryActuatorTemplateTests extends AbstractAppDeployerTestSupport {
@Mock
private RestTemplate restTemplate;
private ActuatorOperations actuatorOperations;
@Override
protected void postSetUp() {
this.actuatorOperations = new CloudFoundryActuatorTemplate(restTemplate, this.deployer, new AppAdmin());
int port = findRandomOpenPort();
givenRequestGetApplication("test-application-id", Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.urls("localhost:" + port) // No longer dynamic
.instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build())
.build()
));
MultiValueMap header = new LinkedMultiValueMap<>();
header.add("X-Cf-App-Instance", "test-application-id:0");
header.add("Accept", "application/json");
header.add("Content-Type", "application/json");
when(restTemplate.exchange(getUrl(port) + "/actuator/info", HttpMethod.GET,new HttpEntity<>(header), Map.class))
.thenReturn(new ResponseEntity<>(Collections.singletonMap("app", Collections.singletonMap("name", "log-sink-rabbit")), HttpStatus.OK));
when(restTemplate.exchange(getUrl(port) + "/actuator/bindings",HttpMethod.GET,new HttpEntity<>(header), List.class))
.thenReturn(new ResponseEntity<>(Collections.singletonList(Collections.singletonMap("bindingName", "input")), HttpStatus.OK));
when(restTemplate.exchange(getUrl(port) + "/actuator/bindings/input",HttpMethod.GET,new HttpEntity<>(header), Map.class))
.thenReturn(new ResponseEntity<>(Collections.singletonMap("bindingName", "input"), HttpStatus.OK));
when(restTemplate.exchange(getUrl(port) + "/actuator/bindings/input", HttpMethod.POST, new HttpEntity<>(Collections.singletonMap("state", "STOPPED"), header), Map.class))
.thenReturn(new ResponseEntity<>(Collections.singletonMap("state", "STOPPED"), HttpStatus.OK));
}
@Test
void actuatorInfo() {
Map info = actuatorOperations
.getFromActuator("test-application-id", "test-application:0", "/info", Map.class);
assertThat(((Map, ?>) info.get("app")).get("name")).isEqualTo("log-sink-rabbit");
}
@Test
void actuatorBindings() {
List> bindings = actuatorOperations
.getFromActuator("test-application-id", "test-application:0", "/bindings", List.class);
assertThat(((Map,?>) (bindings.get(0))).get("bindingName")).isEqualTo("input");
}
@Test
void actuatorBindingInput() {
Map binding = actuatorOperations
.getFromActuator("test-application-id", "test-application:0", "/bindings/input", Map.class);
assertThat(binding.get("bindingName")).isEqualTo("input");
}
@Test
void actuatorPostBindingInput() {
Map state = actuatorOperations
.postToActuator("test-application-id", "test-application:0", "/bindings/input",
Collections.singletonMap("state", "STOPPED"), Map.class);
assertThat(state.get("state")).isEqualTo("STOPPED");
}
public static String getUrl(int port) {
return "http://localhost:" + port;
}
public static int findRandomOpenPort() {
try (ServerSocket socket = new ServerSocket(0)) {
socket.setReuseAddress(true);
return socket.getLocalPort();
} catch (IOException e) {
throw new RuntimeException("Failed to find a random open port", e);
}
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppDeployerIntegrationIT.java
================================================
/*
* Copyright 2016-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;
import org.springframework.cloud.deployer.spi.test.Timeout;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
/**
* Integration tests for CloudFoundryAppDeployer.
*
* @author Eric Bottard
* @author Greg Turnquist
*/
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE,
properties = {"spring.cloud.deployer.cloudfoundry.enableRandomAppNamePrefix=false"})
@ContextConfiguration(classes = CloudFoundryAppDeployerIntegrationIT.Config.class)
public class CloudFoundryAppDeployerIntegrationIT extends AbstractAppDeployerIntegrationJUnit5Tests {
@Autowired
private AppDeployer appDeployer;
@Override
protected AppDeployer provideAppDeployer() {
return appDeployer;
}
/**
* Execution environments may override this default value to have tests wait longer for a deployment, for example if
* running in an environment that is known to be slow.
*/
protected double timeoutMultiplier = 1.0D;
protected int maxRetries = 60;
@BeforeEach
public void init() {
String multiplier = System.getenv("CF_DEPLOYER_TIMEOUT_MULTIPLIER");
if (multiplier != null) {
timeoutMultiplier = Double.parseDouble(multiplier);
}
}
@Override
@Test
@Disabled("Need to look into args escaping better. Disabling for the time being")
public void testCommandLineArgumentsPassing() {
}
@Override
protected String randomName() {
// This will become the hostname part and is limited to 63 chars
String name = super.randomName();
return name.substring(0, Math.min(63, name.length()));
}
@Override
protected Timeout deploymentTimeout() {
return new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));
}
@Override
protected Timeout undeploymentTimeout() {
return new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));
}
/**
* This triggers the use of {@link CloudFoundryDeployerAutoConfiguration}.
*
* @author Eric Bottard
*/
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public static class Config {
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppDeployerTests.java
================================================
/*
* Copyright 2016-2022 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.compress.utils.Sets;
import org.assertj.core.data.MapEntry;
import org.cloudfoundry.client.v2.ClientV2Exception;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.Docker;
import org.cloudfoundry.operations.applications.InstanceDetail;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.Route;
import org.cloudfoundry.operations.applications.StartApplicationRequest;
import org.cloudfoundry.operations.services.BindServiceInstanceRequest;
import org.cloudfoundry.util.FluentMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import reactor.core.publisher.Mono;
import org.springframework.cloud.deployer.resource.docker.DockerResource;
import org.springframework.cloud.deployer.resource.maven.MavenResource;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.app.AppScaleRequest;
import org.springframework.cloud.deployer.spi.app.AppStatus;
import org.springframework.cloud.deployer.spi.app.DeploymentState;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for the {@link CloudFoundryAppDeployer}.
*
* @author Greg Turnquist
* @author Eric Bottard
* @author Ilayaperumal Gopinathan
* @author Ben Hale
* @author David Turanski
*/
public class CloudFoundryAppDeployerTests extends AbstractAppDeployerTestSupport {
@TempDir
public Path folder;
@Override
protected void postSetUp() {
this.deploymentProperties.setServices(new HashSet<>(Arrays.asList("test-service-1", "test-service-2")));
this.deploymentProperties.setEnv(Collections.singletonMap("SOME_GLOBAL_PROPERTY", "someGlobalValue"));
this.deploymentProperties.getAppAdmin().setUser("user");
this.deploymentProperties.getAppAdmin().setPassword("password");
}
@SuppressWarnings("unchecked")
@Test
public void deploy() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariables(defaultEnvironmentVariables())
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.EMPTY_MAP));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@Test
void getLog() {
final String LOG_RESULTS = "log results";
when(this.operations.applications().get(any())).thenReturn(applicationDetailRunningApp());
ApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);
when(applicationLogAccessor.getLog(any(), any())).thenReturn(LOG_RESULTS);
CloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,
this.deploymentProperties,
this.operations, this.runtimeEnvironmentInfo, applicationLogAccessor);
assertThat(deployer.getLog("test-application-id")).isEqualTo(LOG_RESULTS);
}
@Test
void getLogWithNoResult() {
when(this.operations.applications().get(any())).thenThrow(new NoSuchElementException());
ApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);
CloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,
this.deploymentProperties,
this.operations, this.runtimeEnvironmentInfo, applicationLogAccessor);
assertThatThrownBy( () -> deployer.getLog("test-application-id"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unable to find cf-guid");
}
@Test
public void deployMavenArtifactShouldDeleteByDefault() throws IOException, URISyntaxException {
MavenResource resource = mock(MavenResource.class);
Path mavenPath = folder.resolve("maven");
Path artifactPath = mavenPath.resolve("artifact.jar");
File mavenArtifact = artifactPath.toFile();
given(resource.getFile()).willReturn(mavenArtifact);
given(resource.getURI()).willReturn(new URI("maven://test:demo:0.0.1"));
assertThat(this.deploymentProperties.isAutoDeleteMavenArtifacts()).isTrue();
deployResource(this.deployer, resource);
assertThat(mavenArtifact.getParentFile().exists()).isFalse();
}
@Test
public void deployMavenArtifactShouldNotDeleteIfConfigured() throws IOException, URISyntaxException {
MavenResource resource = mock(MavenResource.class);
Path mavenPath = folder.resolve("maven");
Path artifactPath = mavenPath.resolve("artifact.jar");
File mavenArtifact = artifactPath.toFile();
Files.createDirectories(artifactPath.getParent());
given(resource.getFile()).willReturn(mavenArtifact);
given(resource.getURI()).willReturn(new URI("maven://test:demo:0.0.1"));
CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();
deploymentProperties.setAutoDeleteMavenArtifacts(false);
CloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,
deploymentProperties,
this.operations, this.runtimeEnvironmentInfo, this.applicationLogAccessor);
deployResource(deployer, resource);
assertThat(mavenArtifact.getParentFile().exists()).isTrue();
}
@Test
public void deployHttpArtifactShouldDelete() throws IOException, URISyntaxException {
UrlResource resource = mock(UrlResource.class);
Path downloadPath = folder.resolve("download");
Path artifactPath = downloadPath.resolve("artifact.jar");
File downloadedArtifact = artifactPath.toFile();
given(resource.getFile()).willReturn(downloadedArtifact);
given(resource.getURI()).willReturn(new URI("http://somehost/artifact.jar"));
assertThat(this.deploymentProperties.isAutoDeleteMavenArtifacts()).isTrue();
deployResource(this.deployer, resource);
assertThat(downloadedArtifact.exists()).isFalse();
}
@SuppressWarnings("unchecked")
private void deployResource(CloudFoundryAppDeployer deployer, Resource resource) throws IOException {
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.instances(1)
.memory(1024)
.name("test-application-id")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.EMPTY_MAP));
}
@SuppressWarnings("unchecked")
@Test
public void deployWithServiceParameters() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
given(this.services.bind(any(BindServiceInstanceRequest.class)))
.willReturn(Mono.empty());
given(this.applications.start(any(StartApplicationRequest.class)))
.willReturn(Mono.empty());
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariables(defaultEnvironmentVariables())
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.singletonMap(
CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "'test-service-3 foo:bar'")));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithServiceParametersAndBindingError() throws Exception {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
AtomicInteger count = new AtomicInteger();
CountDownLatch okLatch = new CountDownLatch(1);
ClientV2Exception e = new ClientV2Exception(500, 10001,
"The service broker could not perform this operation in parallel with other running operations",
"CF-ConcurrencyError");
// fail 2 times
given(this.services.bind(any(BindServiceInstanceRequest.class)))
.will(x -> {
if (count.getAndIncrement() < 2) {
return Mono.error(e);
}
okLatch.countDown();
return Mono.empty();
});
given(this.applications.start(any(StartApplicationRequest.class)))
.willReturn(Mono.empty());
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariables(defaultEnvironmentVariables())
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.singletonMap(
CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "'test-service-3 foo:bar'")));
assertThat(deploymentId).isEqualTo("test-application-id");
// deploy is actually subscribe and forget so need to wait and
// check that we got retries.
assertThat(okLatch.await(30, TimeUnit.SECONDS)).isTrue();
assertThat(count.get()).isEqualTo(3);
}
@SuppressWarnings("unchecked")
@Test
public void deployWithAdditionalProperties() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
Map environmentVariables = new HashMap<>();
environmentVariables.put("test-key-1", "test-value-1");
addGuidAndIndex(environmentVariables);
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariables(environmentVariables)
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(new AppDeploymentRequest(
new AppDefinition("test-application", Collections.singletonMap("test-key-1", "test-value-1")), resource,
FluentMap.builder().entry("test-key-2", "test-value-2")
.entry(CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY, String.valueOf(false))
.build()));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithAdditionalPropertiesInSpringApplicationJson() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
Map environmentVariables = new HashMap<>();
environmentVariables.put("SPRING_APPLICATION_JSON", "{\"test-key-1\":\"test-value-1\"}");
addGuidAndIndex(environmentVariables);
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariables(environmentVariables)
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(new AppDeploymentRequest(
new AppDefinition("test-application", Collections.singletonMap("test-key-1", "test-value-1")), resource,
Collections.singletonMap("test-key-2", "test-value-2")));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@Test
public void deployWithDeployerEnvironmentVariables() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), applicationDetailRunningApp());
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack("test-buildpack")
.disk(0)
.environmentVariables(defaultEnvironmentVariables())
.healthCheckType(ApplicationHealthCheck.NONE)
.instances(0)
.memory(0)
.name("test-application-id")
.noRoute(false)
.host("test-host")
.domain("test-domain")
.service("test-service-2")
.service("test-service-1")
.build())
.build(), Mono.empty());
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition("test-application",
Collections.emptyMap()), resource,
FluentMap.builder().entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, "test-buildpack")
.entry(AppDeployer.DISK_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, "test-domain")
.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, "none")
.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, "test-host")
.entry(AppDeployer.COUNT_PROPERTY_KEY, "0")
.entry(AppDeployer.MEMORY_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, "false")
.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, "/test-route-path")
.entry(CloudFoundryDeploymentProperties.ENV_KEY + ".JBP_CONFIG_SPRING_AUTO_RECONFIGURATION", CfEnvConfigurer.ENABLED_FALSE)
.entry(CloudFoundryDeploymentProperties.ENV_KEY + ".SPRING_PROFILES_ACTIVE", "cloud,foo")
.build());
String deploymentId = deployer.deploy(appDeploymentRequest);
Map merged = this.deployer.mergeEnvironmentVariables("test-application-id", appDeploymentRequest);
assertThat(merged).containsEntry("JBP_CONFIG_SPRING_AUTO_RECONFIGURATION", CfEnvConfigurer.ENABLED_FALSE);
assertThat(merged).containsEntry("SPRING_PROFILES_ACTIVE", "cloud,foo");
assertThat(merged).containsEntry("SOME_GLOBAL_PROPERTY", "someGlobalValue");
assertThat(merged).containsKey("SPRING_APPLICATION_JSON");
assertThat(merged.get("SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-USER")).isEqualTo("user");
assertThat(merged.get("SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-PASSWORD")).isEqualTo("password");
assertThat(deploymentId).isEqualTo("test-application-id");
}
private Mono applicationDetailRunningApp() {
return Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build());
}
@Test
public void automaticallyConfigureForCfEnv() throws JsonProcessingException {
Resource resource = new FileSystemResource("src/test/resources/log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar");
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition("test-application",
Collections.emptyMap()), resource, Collections.emptyMap());
Map env = deployer.mergeEnvironmentVariables("test-application-id", appDeploymentRequest);
assertThat(env).containsEntry("SOME_GLOBAL_PROPERTY", "someGlobalValue");
assertThat(env).containsEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE);
assertThat(env).containsKey("SPRING_APPLICATION_JSON");
ObjectMapper objectMapper = new ObjectMapper();
Map saj = objectMapper.readValue(env.get("SPRING_APPLICATION_JSON"), HashMap.class);
assertThat(saj).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME);
resource = new FileSystemResource("src/test/resources/log-sink-rabbit-2.1.5.RELEASE.jar");
appDeploymentRequest = new AppDeploymentRequest(new AppDefinition("test-application",
Collections.emptyMap()), resource, Collections.emptyMap());
env = deployer.mergeEnvironmentVariables("test-application-id", appDeploymentRequest);
assertThat(env).doesNotContainKey(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION);
}
@SuppressWarnings("unchecked")
@Test
public void deployWithApplicationDeploymentProperties() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack("test-buildpack")
.disk(0)
.environmentVariables(defaultEnvironmentVariables())
.healthCheckType(ApplicationHealthCheck.NONE)
.instances(0)
.memory(0)
.name("test-application-id")
.noRoute(false)
.host("test-host")
.domain("test-domain")
.routePath("/test-route-path")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
FluentMap.builder().entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, "test-buildpack")
.entry(AppDeployer.DISK_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, "test-domain")
.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, "none")
.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, "test-host")
.entry(AppDeployer.COUNT_PROPERTY_KEY, "0")
.entry(AppDeployer.MEMORY_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, "false")
.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, "/test-route-path")
.build()));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithInvalidRoutePathProperty() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id",
Mono.error(new IllegalArgumentException()),
Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack("test-buildpack")
.disk(0)
.environmentVariables(defaultEnvironmentVariables())
.healthCheckType(ApplicationHealthCheck.NONE)
.instances(0)
.memory(0)
.name("test-application-id")
.noRoute(false)
.host("test-host")
.domain("test-domain")
.routePath("/test-route-path")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
try {
assertThatThrownBy(() -> {
this.deployer.deploy(new AppDeploymentRequest(
new AppDefinition("test-application", Collections.emptyMap()),
resource,
FluentMap.builder()
.entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, "test-buildpack")
.entry(AppDeployer.DISK_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, "test-domain")
.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, "none")
.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, "test-host")
.entry(AppDeployer.COUNT_PROPERTY_KEY, "0")
.entry(AppDeployer.MEMORY_PROPERTY_KEY, "0")
.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, "false")
.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, "test-route-path")
.build()));
}).isInstanceOf(IllegalArgumentException.class).as("Illegal Argument exception is expected.");
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage()).isEqualTo("Cloud Foundry routes must start with \"/\". Route passed = [test-route-path].");
}
}
@SuppressWarnings("unchecked")
@Test
public void deployWithCustomDeploymentProperties() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
this.deploymentProperties.setBuildpack("test-buildpack");
this.deploymentProperties.setDisk("0");
this.deploymentProperties.setDomain("test-domain");
this.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE);
this.deploymentProperties.setHost("test-host");
this.deploymentProperties.setInstances(0);
this.deploymentProperties.setMemory("0");
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack("test-buildpack")
.disk(0)
.domain("test-domain")
.environmentVariables(defaultEnvironmentVariables())
.healthCheckType(ApplicationHealthCheck.NONE)
.host("test-host")
.instances(0)
.memory(0)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.emptyMap()));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithMultipleRoutes() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id",
Mono.error(new IllegalArgumentException()),
Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
this.deploymentProperties.setBuildpack("test-buildpack");
this.deploymentProperties.setDisk("0");
this.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE);
this.deploymentProperties.setRoutes(Sets.newHashSet("route1.test-domain", "route2.test-domain"));
this.deploymentProperties.setInstances(0);
this.deploymentProperties.setMemory("0");
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack("test-buildpack")
.disk(0)
.routes(Sets.newHashSet(
Route.builder().route("route1.test-domain").build(),
Route.builder().route("route2.test-domain").build()))
.environmentVariables(defaultEnvironmentVariables())
.healthCheckType(ApplicationHealthCheck.NONE)
.instances(0)
.memory(0)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(new AppDeploymentRequest(
new AppDefinition("test-application", Collections.emptyMap()),
resource,
Collections.emptyMap()));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithMultipleRoutesAndHostOrDomainMutuallyExclusive() {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
assertThatThrownBy(() -> {
givenRequestGetApplication("test-application-id",
Mono.error(new IllegalArgumentException()),
Mono.just(ApplicationDetail.builder()
.id("test-application-id")
.name("test-application")
.build()));
}).isInstanceOf(IllegalStateException.class)
.as("routes and hosts cannot be set, expect cf java client to throw an exception");
this.deploymentProperties.setHost("route0");
this.deploymentProperties.setDomain("test-domain");
this.deploymentProperties.setRoutes(Sets.newHashSet("route1.test-domain", "route2.test-domain"));
assertThatThrownBy(() -> {
this.deployer.deploy(new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()),
resource, Collections.emptyMap()));
}).isInstanceOf(IllegalStateException.class)
.as("routes and hosts cannot be set, expect cf java client to throw an exception");
}
@SuppressWarnings("unchecked")
@Test
public void deployWithGroup() throws IOException {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
given(this.applicationNameGenerator.generateAppName("test-group-test-application")).willReturn(
"test-group-test-application-id");
givenRequestGetApplication("test-group-test-application-id", Mono.error(new IllegalArgumentException()),
Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-group-test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-group-test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.disk(1024)
.environmentVariable("SPRING_CLOUD_APPLICATION_GROUP", "test-group")
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.environmentVariable("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}")
.environmentVariable("SPRING_CLOUD_APPLICATION_GUID",
"${vcap.application.name}:${vcap.application.instance_index}")
.instances(1)
.memory(1024)
.name("test-group-test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.singletonMap(AppDeployer.GROUP_PROPERTY_KEY, "test-group")));
assertThat(deploymentId).isEqualTo("test-group-test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void deployDockerResource() {
Resource resource = new DockerResource("somecorp/someimage:latest");
given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id");
givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(
ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(0)
.stack("test-stack")
.build()));
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.docker(Docker.builder().image("somecorp/someimage:latest").build())
.disk(1024)
.environmentVariables(defaultEnvironmentVariables())
.instances(1)
.memory(1024)
.name("test-application-id")
.service("test-service-2")
.service("test-service-1")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String deploymentId = this.deployer.deploy(
new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource,
Collections.emptyMap()));
assertThat(deploymentId).isEqualTo("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void statusOfCrashedApplicationIsFailed() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("CRASHED").index("1").build())
.build()));
AppStatus status = this.deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.failed);
}
@SuppressWarnings("unchecked")
@Test
public void statusOfDownApplicationIsDeploying() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("DOWN").index("1").build())
.build()));
AppStatus status = this.deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.deploying);
}
@SuppressWarnings("unchecked")
@Test
public void statusOfFlappingApplicationIsDeployed() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("FLAPPING").index("1").build())
.build()));
AppStatus status = deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.deployed);
}
@SuppressWarnings("unchecked")
@Test
public void statusOfRunningApplicationIsDeployed() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build())
.build()));
AppStatus status = this.deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.deployed);
assertThat(status.getInstances().get("test-application-0").toString())
.isEqualTo("CloudFoundryAppInstanceStatus[test-application-0 : deployed]");
assertThat(status.getInstances().get("test-application-0").getAttributes())
.containsOnly(
MapEntry.entry(CloudFoundryAppInstanceStatus.GUID, "test-application:0"),
MapEntry.entry(CloudFoundryAppInstanceStatus.INDEX, "0"),
MapEntry.entry(CloudFoundryAppInstanceStatus.CF_GUID, "test-application-id"));
}
@SuppressWarnings("unchecked")
@Test
public void statusOfStartingApplicationIsDeploying() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("STARTING").index("1").build())
.build()));
AppStatus status = this.deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.deploying);
}
@SuppressWarnings("unchecked")
@Test
public void statusOfUnknownApplicationIsUnknown() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build())
.build()));
AppStatus status = this.deployer.status("test-application-id");
assertThat(status.getState()).isEqualTo(DeploymentState.unknown);
}
@SuppressWarnings("unchecked")
@Test
public void statusWithAbnormalInstanceStateThrowsException() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("ABNORMAL").index("1").build())
.build()));
assertThatThrownBy(() -> {
this.deployer.status("test-application-id").getState();
}).isInstanceOf(IllegalStateException.class).hasMessageContaining("Unsupported CF state");
}
@SuppressWarnings("unchecked")
@Test
public void statusWithFailingCAPICallRetries() {
AtomicInteger i = new AtomicInteger();
Mono m = Mono.create(s -> {
if (i.incrementAndGet() == 2) {
s.success(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build())
.build());
}
else {
s.error(new RuntimeException("Simulated Server Side error"));
}
});
givenRequestGetApplication("test-application-id", m);
DeploymentState state = this.deployer.status("test-application-id").getState();
assertThat(state).isEqualTo(DeploymentState.unknown);
}
@SuppressWarnings("unchecked")
@Test
public void statusWithFailingCAPICallRetriesEventualError() {
AtomicInteger i = new AtomicInteger();
Mono m = Mono.create(s -> {
if (i.incrementAndGet() == 12) { // 12 is more than the number of retries
s.success(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build())
.build());
}
else {
s.error(new RuntimeException("Simulated Server Side error"));
}
});
givenRequestGetApplication("test-application-id", m);
this.deployer.deploymentProperties.setStatusTimeout(200); // Will cause wait of 20ms then 40ms,80ms
DeploymentState state = this.deployer.status("test-application-id").getState();
assertThat(state).isEqualTo(DeploymentState.error);
}
@SuppressWarnings("unchecked")
@Test
public void statusWithErrorThrownOnBlocking() {
AtomicInteger i = new AtomicInteger();
Mono m = Mono.delay(Duration.ofSeconds(30)).then(Mono.create(s -> {
i.incrementAndGet();
s.success(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build())
.build());
}));
givenRequestGetApplication("test-application-id", m);
this.deployer.deploymentProperties.setStatusTimeout(1);// Is less than the delay() above
DeploymentState state = this.deployer.status("test-application-id").getState();
assertThat(state).isEqualTo(DeploymentState.error);
assertThat(i.get()).isEqualTo(0);
}
@SuppressWarnings("unchecked")
@Test
public void undeploy() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build())
.build()));
givenRequestDeleteApplication("test-application-id", Mono.empty());
this.deployer.undeploy("test-application-id");
}
@SuppressWarnings("unchecked")
@Test
public void scale() {
givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(2)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(2)
.stack("test-stack")
.instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build())
.build()));
givenRequestScaleApplication("test-application-id", 2, 1024, 1024, Mono.empty());
this.deployer.scale(new AppScaleRequest("test-application-id", 2));
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppNameGeneratorTest.java
================================================
/*
* Copyright 2016-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Soby Chacko
* @author Mark Pollack
*/
public class CloudFoundryAppNameGeneratorTest {
@Test
public void testDeploymentIdWithAppNamePrefixAndRandomAppNamePrefixFalse() throws Exception {
CloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();
properties.setEnableRandomAppNamePrefix(false);
properties.setAppNamePrefix("dataflow");
CloudFoundryAppNameGenerator deploymentCustomizer =
new CloudFoundryAppNameGenerator(properties);
deploymentCustomizer.afterPropertiesSet();
assertThat(deploymentCustomizer.generateAppName("foo")).isEqualTo("dataflow-foo");
}
@Test
public void testDeploymentIdWithAppNamePrefixAndRandomAppNamePrefixTrue() throws Exception {
CloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();
properties.setEnableRandomAppNamePrefix(true);
properties.setAppNamePrefix("dataflow-longername");
CloudFoundryAppNameGenerator deploymentCustomizer =
new CloudFoundryAppNameGenerator(properties);
deploymentCustomizer.afterPropertiesSet();
String deploymentIdWithUniquePrefix = deploymentCustomizer.generateAppName("foo");
assertThat(deploymentIdWithUniquePrefix).matches("dataflow-longername-\\w+-foo");
String deploymentIdWithUniquePrefixAgain = deploymentCustomizer.generateAppName("foo");
assertThat(deploymentIdWithUniquePrefix).isEqualTo(deploymentIdWithUniquePrefixAgain);
}
@Test
public void testDeploymentIdWithoutAppNamePrefixAndRandomAppNamePrefixTrue() throws Exception {
CloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();
properties.setEnableRandomAppNamePrefix(true);
properties.setAppNamePrefix("");
CloudFoundryAppNameGenerator deploymentCustomizer =
new CloudFoundryAppNameGenerator(properties);
deploymentCustomizer.afterPropertiesSet();
String deploymentIdWithUniquePrefix = deploymentCustomizer.generateAppName("foo");
assertThat(deploymentIdWithUniquePrefix).matches("\\w+-foo");
}
@Test
public void testDeploymentIdWithoutAppNamePrefixAndRandomAppNamePrefixFalse() throws Exception {
CloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();
properties.setEnableRandomAppNamePrefix(false);
properties.setAppNamePrefix("");
CloudFoundryAppNameGenerator deploymentCustomizer =
new CloudFoundryAppNameGenerator(properties);
deploymentCustomizer.afterPropertiesSet();
assertThat(deploymentCustomizer.generateAppName("foo")).isEqualTo("foo");
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryConnectionPropertiesTests.java
================================================
/*
* Copyright 2019-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
public class CloudFoundryConnectionPropertiesTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
public void setAllProperties() {
this.contextRunner
.withInitializer(context -> {
Map map = new HashMap<>();
map.put("spring.cloud.deployer.cloudfoundry.org", "org");
map.put("spring.cloud.deployer.cloudfoundry.space", "space");
map.put("spring.cloud.deployer.cloudfoundry.url", "http://example.com");
map.put("spring.cloud.deployer.cloudfoundry.username", "username");
map.put("spring.cloud.deployer.cloudfoundry.password", "password");
map.put("spring.cloud.deployer.cloudfoundry.client-id", "id");
map.put("spring.cloud.deployer.cloudfoundry.client-secret", "secret");
map.put("spring.cloud.deployer.cloudfoundry.login-hint", "hint");
map.put("spring.cloud.deployer.cloudfoundry.skip-ssl-validation", "true");
context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));
})
.withUserConfiguration(Config1.class)
.run((context) -> {
CloudFoundryConnectionProperties properties = context.getBean(CloudFoundryConnectionProperties.class);
assertThat(properties.getOrg()).isEqualTo("org");
assertThat(properties.getSpace()).isEqualTo("space");
assertThat(properties.getUrl().toString()).isEqualTo("http://example.com");
assertThat(properties.getUsername()).isEqualTo("username");
assertThat(properties.getPassword()).isEqualTo("password");
assertThat(properties.getClientId()).isEqualTo("id");
assertThat(properties.getClientSecret()).isEqualTo("secret");
assertThat(properties.getLoginHint()).isEqualTo("hint");
assertThat(properties.isSkipSslValidation()).isTrue();
});
}
@EnableConfigurationProperties
private static class Config1 {
@Bean
@ConfigurationProperties(prefix = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES)
public TestCloudFoundryConnectionProperties testCloudFoundryConnectionProperties() {
return new TestCloudFoundryConnectionProperties();
}
}
private static class TestCloudFoundryConnectionProperties extends CloudFoundryConnectionProperties {
// not to get Configuration Processor @Bean Duplicate Prefix Definition error
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryDeployerTests.java
================================================
/*
* Copyright 2020 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
public class CloudFoundryDeployerTests {
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private RuntimeEnvironmentInfo runtimeEnvironmentInfo;
private Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
private AppDefinition definition = new AppDefinition("test-application", Collections.emptyMap());
@Test
public void testBuildpacksDefault() {
CloudFoundryDeploymentProperties props = new CloudFoundryDeploymentProperties();
TestCloudFoundryDeployer deployer = new TestCloudFoundryDeployer(props, runtimeEnvironmentInfo);
AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);
Set buildpacks = deployer.buildpacks(request);
assertThat(buildpacks).hasSize(1);
}
@Test
public void testBuildpacksSingleMultiLogic() {
CloudFoundryDeploymentProperties props = new CloudFoundryDeploymentProperties();
props.setBuildpack("buildpack1");
TestCloudFoundryDeployer deployer = new TestCloudFoundryDeployer(props, runtimeEnvironmentInfo);
Map deploymentProperties = new HashMap<>();
AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties, null);
Set buildpacks = deployer.buildpacks(request);
assertThat(buildpacks).hasSize(1);
assertThat(buildpacks).contains("buildpack1");
props.setBuildpacks(new HashSet<>(Arrays.asList("buildpack2", "buildpack3")));
buildpacks = deployer.buildpacks(request);
assertThat(buildpacks).hasSize(2);
assertThat(buildpacks).contains("buildpack2", "buildpack3");
deploymentProperties.put(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, "buildpack4");
buildpacks = deployer.buildpacks(request);
assertThat(buildpacks).hasSize(1);
assertThat(buildpacks).contains("buildpack4");
deploymentProperties.put(CloudFoundryDeploymentProperties.BUILDPACKS_PROPERTY_KEY, "buildpack5,buildpack6");
buildpacks = deployer.buildpacks(request);
assertThat(buildpacks).hasSize(2);
assertThat(buildpacks).contains("buildpack5", "buildpack6");
}
private static class TestCloudFoundryDeployer extends AbstractCloudFoundryDeployer {
TestCloudFoundryDeployer(CloudFoundryDeploymentProperties deploymentProperties,
RuntimeEnvironmentInfo runtimeEnvironmentInfo) {
super(deploymentProperties, runtimeEnvironmentInfo);
}
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherCachingTests.java
================================================
/*
* Copyright 2020-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.Metadata;
import org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;
import org.cloudfoundry.client.v2.organizations.OrganizationResource;
import org.cloudfoundry.client.v2.organizations.Organizations;
import org.cloudfoundry.client.v2.spaces.ListSpacesResponse;
import org.cloudfoundry.client.v2.spaces.SpaceResource;
import org.cloudfoundry.client.v2.spaces.Spaces;
import org.cloudfoundry.client.v3.Pagination;
import org.cloudfoundry.client.v3.tasks.ListTasksResponse;
import org.cloudfoundry.client.v3.tasks.TaskResource;
import org.cloudfoundry.client.v3.tasks.TaskState;
import org.cloudfoundry.client.v3.tasks.Tasks;
import org.cloudfoundry.logcache.v1.LogCacheClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
public class CloudFoundryTaskLauncherCachingTests {
@Test
public void testOrgSpaceCachingRetries() {
CloudFoundryClient client = mock(CloudFoundryClient.class);
AtomicBoolean spaceError = new AtomicBoolean(true);
AtomicBoolean orgError = new AtomicBoolean(true);
Spaces spaces = mock(Spaces.class);
given(client.spaces()).willReturn(spaces);
given(spaces.list(any())).willReturn(listSpacesResponse(spaceError));
Organizations organizations = mock(Organizations.class);
given(client.organizations()).willReturn(organizations);
given(organizations.list(any())).willReturn(listOrganizationsResponse(orgError));
Tasks tasks = mock(Tasks.class);
given(client.tasks()).willReturn(tasks);
given(tasks.list(any())).willReturn(runningTasksResponse());
LogCacheClient logCacheClient = mock(LogCacheClient.class);
CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();
CloudFoundryOperations operations = mock(CloudFoundryOperations.class);
RuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);
Map orgAndSpace = new HashMap<>();
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, "this-org");
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, "this-space");
given(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);
CloudFoundryTaskLauncher launcher = new CloudFoundryTaskLauncher(client, deploymentProperties, operations,
runtimeEnvironmentInfo, new ApplicationLogAccessor(logCacheClient));
Throwable thrown1 = catchThrowable(() -> {
launcher.getRunningTaskExecutionCount();
});
assertThat(thrown1).isInstanceOf(RuntimeException.class).hasNoCause();
// space should still error
orgError.set(false);
Throwable thrown2 = catchThrowable(() -> {
launcher.getRunningTaskExecutionCount();
});
assertThat(thrown2).isInstanceOf(RuntimeException.class).hasNoCause();
// cache should now be getting cleared as space doesn't error
spaceError.set(false);
Throwable thrown3 = catchThrowable(() -> {
launcher.getRunningTaskExecutionCount();
});
assertThat(thrown3).doesNotThrowAnyException();
assertThat(launcher.getRunningTaskExecutionCount()).isEqualTo(1);
}
private Mono listOrganizationsResponse(AtomicBoolean error) {
// defer so that we can conditionally throw within mono
return Mono.defer(() -> {
if (error.get()) {
throw new RuntimeException();
}
ListOrganizationsResponse response = ListOrganizationsResponse.builder()
.addAllResources(Collections.singletonList(
OrganizationResource.builder()
.metadata(Metadata.builder().id("123").build()).build())
)
.build();
return Mono.just(response);
});
}
private Mono listSpacesResponse(AtomicBoolean error) {
// defer so that we can conditionally throw within mono
return Mono.defer(() -> {
if (error.get()) {
throw new RuntimeException();
}
ListSpacesResponse response = ListSpacesResponse.builder()
.addAllResources(Collections.singletonList(
SpaceResource.builder()
.metadata(Metadata.builder().id("123").build()).build())
)
.build();
return Mono.just(response);
});
}
private Mono runningTasksResponse() {
List taskResources = new ArrayList<>();
for (int i = 0; i < 1; i++) {
taskResources.add(TaskResource.builder()
.name("task-" + i)
.dropletId(UUID.randomUUID().toString())
.id(UUID.randomUUID().toString())
.diskInMb(2048)
.sequenceId(i)
.state(TaskState.RUNNING)
.memoryInMb(2048)
.createdAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.build());
}
ListTasksResponse listTasksResponse = ListTasksResponse.builder().resources(taskResources)
.pagination(Pagination.builder().totalResults(taskResources.size()).build())
.build();
return Mono.just(listTasksResponse);
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherIntegrationIT.java
================================================
/*
* Copyright 2016-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import com.github.zafarkhaja.semver.Version;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
import org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;
import org.springframework.cloud.deployer.spi.test.Timeout;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
/**
* Runs integration tests for {@link CloudFoundryTaskLauncher}, using the production configuration,
* that may be configured via {@link CloudFoundryConnectionProperties}.
*
* Tests are only run if a successful connection can be made at startup.
*
* @author Eric Bottard
* @author Greg Turnquist
* @author Michael Minella
* @author Ben Hale
*/
@ContextConfiguration(classes=CloudFoundryTaskLauncherIntegrationIT.Config.class)
public class CloudFoundryTaskLauncherIntegrationIT extends AbstractTaskLauncherIntegrationJUnit5Tests {
@Autowired
private TaskLauncher taskLauncher;
@Autowired
private Version cloudControllerAPIVersion;
/**
* Execution environments may override this default value to have tests wait longer for a deployment, for example if
* running in an environment that is known to be slow.
*/
protected double timeoutMultiplier = 1.0D;
protected int maxRetries = 60;
@BeforeEach
public void init() {
Assumptions.assumeTrue(cloudControllerAPIVersion.greaterThanOrEqualTo(Version.forIntegers(2, 65, 0)),
"Skipping TaskLauncher ITs on PCF<1.9 (2.65.0). Actual API version is " + cloudControllerAPIVersion);
String multiplier = System.getenv("CF_DEPLOYER_TIMEOUT_MULTIPLIER");
if (multiplier != null) {
timeoutMultiplier = Double.parseDouble(multiplier);
}
}
@Override
protected TaskLauncher provideTaskLauncher() {
return taskLauncher;
}
/*
* Allow for a small pause so that each each TL.destroy() at the end of tests actually completes,
* as this is asynchronous.
*/
@AfterEach
public void pause() throws InterruptedException {
Thread.sleep(500);
}
@Test
@Override
@Disabled("CF Deployer incorrectly reports status as failed instead of canceled")
public void testSimpleCancel() throws InterruptedException {
super.testSimpleCancel();
}
@Override
protected Timeout deploymentTimeout() {
return new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));
}
@Override
protected Timeout undeploymentTimeout() {
return new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));
}
/**
* This triggers the use of {@link CloudFoundryDeployerAutoConfiguration}.
*
* @author Eric Bottard
*/
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public static class Config {
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherTests.java
================================================
/*
* Copyright 2016-2023 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.Metadata;
import org.cloudfoundry.client.v2.applications.ApplicationsV2;
import org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;
import org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;
import org.cloudfoundry.client.v2.organizations.OrganizationResource;
import org.cloudfoundry.client.v2.organizations.Organizations;
import org.cloudfoundry.client.v2.spaces.ListSpacesResponse;
import org.cloudfoundry.client.v2.spaces.SpaceResource;
import org.cloudfoundry.client.v2.spaces.Spaces;
import org.cloudfoundry.client.v3.Pagination;
import org.cloudfoundry.client.v3.Relationship;
import org.cloudfoundry.client.v3.ToOneRelationship;
import org.cloudfoundry.client.v3.tasks.CancelTaskRequest;
import org.cloudfoundry.client.v3.tasks.CancelTaskResponse;
import org.cloudfoundry.client.v3.tasks.CreateTaskRequest;
import org.cloudfoundry.client.v3.tasks.CreateTaskResponse;
import org.cloudfoundry.client.v3.tasks.GetTaskRequest;
import org.cloudfoundry.client.v3.tasks.GetTaskResponse;
import org.cloudfoundry.client.v3.tasks.ListTasksResponse;
import org.cloudfoundry.client.v3.tasks.TaskRelationships;
import org.cloudfoundry.client.v3.tasks.TaskResource;
import org.cloudfoundry.client.v3.tasks.TaskState;
import org.cloudfoundry.client.v3.tasks.Tasks;
import org.cloudfoundry.doppler.Envelope;
import org.cloudfoundry.doppler.EventType;
import org.cloudfoundry.doppler.LogMessage;
import org.cloudfoundry.doppler.MessageType;
import org.cloudfoundry.logcache.v1.EnvelopeBatch;
import org.cloudfoundry.logcache.v1.LogCacheClient;
import org.cloudfoundry.logcache.v1.ReadResponse;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.StopApplicationRequest;
import org.cloudfoundry.operations.services.Services;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.cloud.deployer.spi.task.LaunchState;
import org.springframework.cloud.deployer.spi.task.TaskStatus;
import org.springframework.cloud.deployer.spi.util.ByteSizeUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Michael Minella
* @author Ben Hale
* @author Glenn Renfro
* @author David Turanski
* @author David Bernard
*/
public class CloudFoundryTaskLauncherTests {
private final static int TASK_EXECUTION_COUNT = 10;
private final static String LOG_RESPONSE = "Test Log Response";
private final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Applications applications;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private ApplicationsV2 applicationsV2;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private CloudFoundryClient client;
private CloudFoundryTaskLauncher launcher;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private CloudFoundryOperations operations;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Services services;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Spaces spaces;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Organizations organizations;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Tasks tasks;
private Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
given(this.tasks.list(any())).willReturn(this.runningTasksResponse());
given(this.client.applicationsV2()).willReturn(this.applicationsV2);
given(this.client.tasks()).willReturn(this.tasks);
given(this.operations.applications()).willReturn(this.applications);
given(this.operations.services()).willReturn(this.services);
given(this.client.spaces()).willReturn(this.spaces);
given(this.client.organizations()).willReturn(this.organizations);
Mono getTaskResponse = getDefaultGetTaskResponse();
given(this.tasks.get(any())).willReturn(getTaskResponse);
RuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);
Map orgAndSpace = new HashMap<>();
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, "this-org");
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, "this-space");
given(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);
given(this.organizations.list(any())).willReturn(listOrganizationsResponse());
given(this.spaces.list(any())).willReturn(listSpacesResponse());
ApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);
given(applicationLogAccessor.getLog(any(), any())).willReturn(LOG_RESPONSE);
this.launcher = getCloudFoundryTaskLauncher(applicationLogAccessor);
}
private CloudFoundryTaskLauncher getCloudFoundryTaskLauncher(ApplicationLogAccessor applicationLogAccessor) {
RuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);
Map orgAndSpace = new HashMap<>();
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, "this-org");
orgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, "this-space");
given(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);
given(this.organizations.list(any())).willReturn(listOrganizationsResponse());
given(this.spaces.list(any())).willReturn(listSpacesResponse());
this.deploymentProperties.setApiTimeout(1);
this.deploymentProperties.setStatusTimeout(1_250L);
return new CloudFoundryTaskLauncher(this.client,
this.deploymentProperties,
this.operations,
runtimeEnvironmentInfo,
applicationLogAccessor);
}
@Test
void cancel() {
givenRequestCancelTask("test-task-id", Mono.just(CancelTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.CANCELING)
.build()));
this.launcher.cancel("test-task-id");
}
@Test
void currentExecutionCount() {
assertThat(this.launcher.getRunningTaskExecutionCount()).isEqualTo(this.TASK_EXECUTION_COUNT);
}
@Test
void launchTaskApplicationExists() throws IOException{
setupExistingAppSuccessful();
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
String taskId = this.launcher.launch(defaultRequest());
assertThat(taskId).isEqualTo("test-task-id");
}
@Test
void stageTaskApplicationExists() throws IOException{
setupExistingAppSuccessful();
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
SummaryApplicationResponse response = this.launcher.stage(defaultRequest());
assertThat(response.getId()).isEqualTo("test-application-id");
assertThat(response.getDetectedStartCommand()).isEqualTo("test-command");
}
@Test
void launchTaskWithNonExistentApplication() throws IOException {
setupTaskWithNonExistentApplication(this.resource);
String taskId = this.launcher.launch(defaultRequest());
assertThat(taskId).isEqualTo("test-task-id");
}
@Test
void launchExistingTaskApplicationWithPushDisabled() {
setupExistingAppSuccessful();
deploymentProperties.setPushTaskAppsEnabled(false);
String taskId = this.launcher.launch(defaultRequest());
assertThat(taskId).isEqualTo("test-task-id");
}
@Test
void launchNonExistingTaskApplicationWithPushDisabled() throws IOException {
setupTaskWithNonExistentApplication(this.resource);
deploymentProperties.setPushTaskAppsEnabled(false);
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(IllegalStateException.class);
}
@Test
void stageTaskWithNonExistentApplication() throws IOException {
setupTaskWithNonExistentApplication(this.resource);
SummaryApplicationResponse response = this.launcher.stage(defaultRequest());
assertThat(response.getId()).isEqualTo("test-application-id");
assertThat(response.getDetectedStartCommand()).isEqualTo("test-command");
}
@Test
void automaticallyConfigureForCfEnv() throws JsonProcessingException {
Resource resource = new FileSystemResource("src/test/resources/log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar");
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition("test-application",
Collections.emptyMap()), resource, Collections.emptyMap());
Map env = launcher.mergeEnvironmentVariables("test-application-id", appDeploymentRequest);
// MatcherAssert.assertThat(env, hasEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE));
// MatcherAssert.assertThat(env, hasKey(CoreMatchers.equalTo("SPRING_APPLICATION_JSON")));
// ObjectMapper objectMapper = new ObjectMapper();
// Map saj = objectMapper.readValue(env.get("SPRING_APPLICATION_JSON"),HashMap.class);
// MatcherAssert.assertThat(saj, hasEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME));
assertThat(env).containsEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE);
assertThat(env).containsKeys("SPRING_APPLICATION_JSON");
ObjectMapper objectMapper = new ObjectMapper();
Map saj = objectMapper.readValue(env.get("SPRING_APPLICATION_JSON"), HashMap.class);
assertThat(saj).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME);
}
@Test
void launchTaskWithNonExistentApplicationAndApplicationListingFails() {
givenRequestListApplications(Flux.error(new UnsupportedOperationException()));
given(this.operations.applications().pushManifest(any())).willThrow(new UnsupportedOperationException());
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void stageTaskWithNonExistentApplicationAndApplicationListingFails() {
givenRequestListApplications(Flux.error(new UnsupportedOperationException()));
given(this.operations.applications().pushManifest(any())).willThrow(new UnsupportedOperationException());
assertThatThrownBy(() -> {
this.launcher.stage(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void launchTaskWithNonExistentApplicationAndPushFails() throws IOException {
setupFailedPush(this.resource);
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void stageTaskWithNonExistentApplicationAndPushFails() throws IOException {
setupFailedPush(this.resource);
assertThatThrownBy(() -> {
this.launcher.stage(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void launchTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {
setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void stageTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {
setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.stage(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void launchTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {
setupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void stageTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {
setupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.stage(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void launchTaskWithNonExistentApplicationAndTaskCreationFails() throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(this.resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplicationSummary("test-application-id",
Mono.just(SummaryApplicationResponse.builder()
.id("test-application-id")
.detectedStartCommand("test-command")
.build()));
givenRequestCreateTask("test-application-id",
"test-command",
this.deploymentProperties.getMemory(),
this.deploymentProperties.getDisk(),
"test-application",
Mono.error(new UnsupportedOperationException()));
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void launchTaskWithNonExistentApplicationBindingOneService() throws IOException {
setupTaskWithNonExistentApplicationBindingOneService(this.resource);
AppDeploymentRequest request = deploymentRequest(this.resource,
Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "test-service-instance-2"));
String taskId = this.launcher.launch(request);
assertThat(taskId).isEqualTo("test-task-id");
}
@Test
void stageTaskWithNonExistentApplicationBindingOneService() throws IOException {
setupTaskWithNonExistentApplicationBindingOneService(this.resource);
AppDeploymentRequest request = deploymentRequest(this.resource,
Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "test-service-instance-2"));
SummaryApplicationResponse response = this.launcher.stage(request);
assertThat(response.getId()).isEqualTo("test-application-id");
assertThat(response.getDetectedStartCommand()).isEqualTo("test-command");
}
@Test
void launchTaskWithNonExistentApplicationBindingThreeServices() throws IOException {
setupTaskWithNonExistentApplicationBindingThreeServices(this.resource);
AppDeploymentRequest request = deploymentRequest(this.resource,
Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,
"test-service-instance-1,test-service-instance-2,test-service-instance-3"));
String taskId = this.launcher.launch(request);
assertThat(taskId).isEqualTo("test-task-id");
}
@Test
void stageTaskWithNonExistentApplicationBindingThreeServices() throws IOException {
setupTaskWithNonExistentApplicationBindingThreeServices(this.resource);
AppDeploymentRequest request = deploymentRequest(this.resource,
Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,
"test-service-instance-1,test-service-instance-2,test-service-instance-3"));
SummaryApplicationResponse response = this.launcher.stage(request);
assertThat(response.getId()).isEqualTo("test-application-id");
assertThat(response.getDetectedStartCommand()).isEqualTo("test-command");
}
@Test
void launchTaskWithNonExistentApplicationRetrievalFails() throws IOException {
setupTaskWithNonExistentApplicationRetrievalFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.launch(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void stageTaskWithNonExistentApplicationRetrievalFails() throws IOException {
setupTaskWithNonExistentApplicationRetrievalFails(this.resource);
assertThatThrownBy(() -> {
this.launcher.stage(defaultRequest());
}).isInstanceOf(UnsupportedOperationException.class);
}
@Test
void status() {
ToOneRelationship toOneRelationship = ToOneRelationship.builder()
.data(Relationship.builder().id("task-app-guid").build()).build();
TaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();
givenRequestGetTask("test-task-id", getDefaultGetTaskResponse());
TaskStatus status = this.launcher.status("test-task-id");
assertThat(status.getState()).isEqualTo(LaunchState.complete);
}
private Mono getDefaultGetTaskResponse() {
ToOneRelationship toOneRelationship = ToOneRelationship.builder()
.data(Relationship.builder().id("task-app-guid").build()).build();
TaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();
return Mono.just(GetTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.taskRelationships(taskRelationships)
.name("test-task-id")
.state(TaskState.SUCCEEDED)
.build());
}
@Test
void statusTimeout() {
// Delay twice as much as 40% of statusTimeout, which is what the deployer uses
long delay = (long) (this.deploymentProperties.getStatusTimeout() * .4f * 2);
givenRequestGetTask("test-task-id", Mono
.delay(Duration.ofMillis(delay))
.then(Mono.just(GetTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.SUCCEEDED)
.build())));
assertThat(this.launcher.status("test-task-id").getState()).isEqualTo(LaunchState.error);
}
@Test
void destroyApp() {
givenRequestDeleteApplication("test-application");
this.launcher.destroy("test-application");
}
@Test
void commandProperlyConfigured() {
AppDeploymentRequest request = new AppDeploymentRequest(new AppDefinition(
"test-app-1", null),
this.resource,
Collections.singletonMap("test-key-1", "test-val-1"),
Collections.singletonList("test-command-arg-1"));
String command = this.launcher.getCommand(SummaryApplicationResponse
.builder()
.detectedStartCommand("command-val")
.build(),
request);
assertThat(command).isEqualTo("command-val test-command-arg-1");
List args = new ArrayList<>();
args.add("test-command-arg-1");
args.add("a=b");
args.add("run.id=1");
args.add("run.id(long)=1");
args.add("run.id(long=1");
args.add("run.id)=1");
request = new AppDeploymentRequest(new AppDefinition(
"test-app-1", null),
this.resource,
Collections.singletonMap("test-key-1", "test-val-1"),
args);
command = this.launcher.getCommand(SummaryApplicationResponse
.builder()
.detectedStartCommand("command-val")
.build(),
request);
assertThat(command).isEqualTo("command-val test-command-arg-1 a=b run.id=1 run.id\\\\\\(long\\\\\\)=1 run.id\\\\\\(long=1 run.id\\\\\\)=1");
}
@Test
void getLog() {
assertThat(this.launcher.getLog("agcd")).isEqualTo(LOG_RESPONSE);
}
@Test
void getLogForUnknownId() {
LogCacheClient logCacheClient = mock(LogCacheClient.class);
ApplicationLogAccessor applicationLogAccessor = new ApplicationLogAccessor(logCacheClient);
Envelope.builder().logMessage(LogMessage.builder().message("").messageType(MessageType.OUT).timestamp(0l)
.build()).eventType(EventType.LOG_MESSAGE).origin("foo").build();
EnvelopeBatch envelopeBatch = EnvelopeBatch.builder().batch().build();
ReadResponse response = ReadResponse.builder().envelopes(envelopeBatch).build();
given(logCacheClient.read(any())).willReturn(Mono.just(response));
this.launcher = getCloudFoundryTaskLauncher(applicationLogAccessor);
assertThat(this.launcher.getLog("agcd")).isEmpty();
}
@Test
void getLogWithUnknownTaskAppGuid() {
ToOneRelationship toOneRelationship = ToOneRelationship.builder()
.data(Relationship.builder().id("task-app-guid").build()).build();
TaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();
Mono getTaskResponse = Mono.just(GetTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.state(TaskState.FAILED)
.name("TaskAppNotPresent")
.build());
given(this.tasks.get(any())).willReturn(getTaskResponse);
assertThatThrownBy(() -> {
assertThat(this.launcher.getLog("agcd")).isEqualTo(LOG_RESPONSE);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("could not find a GUID app id for the task guid id agcd");
}
private void givenRequestCancelTask(String taskId, Mono response) {
given(this.client.tasks()
.cancel(CancelTaskRequest.builder()
.taskId(taskId)
.build()))
.willReturn(response);
}
private void givenRequestCreateTask(String applicationId,
String command,
String memory,
String disk,
String name,
Mono response) {
given(this.client.tasks()
.create(CreateTaskRequest.builder()
.applicationId(applicationId)
.command(command)
.memoryInMb((int) ByteSizeUtils.parseToMebibytes(memory))
.diskInMb((int) ByteSizeUtils.parseToMebibytes(disk))
.name(name)
.build()))
.willReturn(response);
}
private void givenRequestDeleteApplication(String appName) {
given(this.operations.applications()
.delete(DeleteApplicationRequest.builder()
.name(appName)
.deleteRoutes(true)
.build()))
.willReturn(Mono.empty());
}
private void givenRequestGetApplication(String name, Mono response) {
given(this.operations.applications()
.get(GetApplicationRequest.builder()
.name(name)
.build()))
.willReturn(response);
}
private void givenRequestGetApplicationSummary(String applicationId, Mono response) {
given(this.client.applicationsV2()
.summary(org.cloudfoundry.client.v2.applications.SummaryApplicationRequest.builder()
.applicationId(applicationId)
.build()))
.willReturn(response);
}
private void givenRequestGetTask(String taskId, Mono response) {
given(this.client.tasks()
.get(GetTaskRequest.builder()
.taskId(taskId)
.build()))
.willReturn(response);
}
private void givenRequestListApplications(Flux response) {
given(this.operations.applications()
.list())
.willReturn(response);
}
private void givenRequestPushApplication(PushApplicationManifestRequest request, Mono response) {
given(this.operations.applications()
.pushManifest(any(PushApplicationManifestRequest.class)))
.willReturn(response);
}
private void givenRequestStopApplication(String name, Mono response) {
given(this.operations.applications()
.stop(StopApplicationRequest.builder()
.name(name)
.build()))
.willReturn(response);
}
private void setupExistingAppSuccessful() {
givenRequestListApplications(Flux.just(ApplicationSummary.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.build()));
givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
.id("test-application-id")
.detectedStartCommand("test-command")
.build()));
givenRequestCreateTask("test-application-id",
"test-command",
this.deploymentProperties.getMemory(),
this.deploymentProperties.getDisk(),
"test-application",
Mono.just(CreateTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.FAILED)
.build()));
}
private void setupTaskWithNonExistentApplication(Resource resource) throws IOException{
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
.id("test-application-id")
.detectedStartCommand("test-command")
.build()));
givenRequestCreateTask("test-application-id",
"test-command",
this.deploymentProperties.getMemory(),
this.deploymentProperties.getDisk(),
"test-application",
Mono.just(CreateTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.SUCCEEDED)
.build()));
}
private void setupFailedPush(Resource resource) throws IOException{
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.error(new UnsupportedOperationException()));
}
private void setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(Resource resource) throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplicationSummary("test-application-id", Mono.error(new UnsupportedOperationException()));
}
private void setupTaskWithNonExistentApplicationAndStoppingApplicationFails(Resource resource) throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.error(new UnsupportedOperationException()));
}
private void setupTaskWithNonExistentApplicationBindingOneService(Resource resource) throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.service("test-service-instance-2")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
.id("test-application-id")
.detectedStartCommand("test-command")
.build()));
givenRequestCreateTask("test-application-id",
"test-command",
this.deploymentProperties.getMemory(),
this.deploymentProperties.getDisk(),
"test-application",
Mono.just(CreateTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.SUCCEEDED)
.build()));
}
private void setupTaskWithNonExistentApplicationBindingThreeServices(Resource resource) throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.service("test-service-instance-1")
.service("test-service-instance-2")
.service("test-service-instance-3")
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
.diskQuota(0)
.id("test-application-id")
.instances(1)
.memoryLimit(0)
.name("test-application")
.requestedState("RUNNING")
.runningInstances(1)
.stack("test-stack")
.build()));
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
.id("test-application-id")
.detectedStartCommand("test-command")
.build()));
givenRequestCreateTask("test-application-id",
"test-command",
this.deploymentProperties.getMemory(),
this.deploymentProperties.getDisk(),
"test-application",
Mono.just(CreateTaskResponse.builder()
.id("test-task-id")
.memoryInMb(1024)
.diskInMb(1024)
.dropletId("1")
.createdAt(new Date().toString())
.updatedAt(new Date().toString())
.sequenceId(1)
.name("test-task-id")
.state(TaskState.SUCCEEDED)
.build()));
}
public void setupTaskWithNonExistentApplicationRetrievalFails(Resource resource) throws IOException {
givenRequestListApplications(Flux.empty());
givenRequestPushApplication(PushApplicationManifestRequest.builder()
.manifest(ApplicationManifest.builder()
.path(resource.getFile().toPath())
.buildpack(deploymentProperties.getBuildpack())
.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
.environmentVariable("SPRING_APPLICATION_JSON", "{}")
.healthCheckType(ApplicationHealthCheck.NONE)
.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
.name("test-application")
.noRoute(true)
.services(Collections.emptySet())
.build())
.stagingTimeout(this.deploymentProperties.getStagingTimeout())
.startupTimeout(this.deploymentProperties.getStartupTimeout())
.build(), Mono.empty());
givenRequestStopApplication("test-application", Mono.empty());
givenRequestGetApplication("test-application", Mono.error(new UnsupportedOperationException()));
}
private AppDeploymentRequest defaultRequest() {
return deploymentRequest(this.resource, Collections.emptyMap());
}
private AppDeploymentRequest deploymentRequest(Resource resource, Map deploymentProperties) {
AppDefinition definition = new AppDefinition("test-application", null);
return new AppDeploymentRequest(definition, resource, deploymentProperties);
}
private Mono runningTasksResponse() {
List taskResources = new ArrayList<>();
for (int i=0; i< TASK_EXECUTION_COUNT; i++) {
taskResources.add(TaskResource.builder()
.name("task-" + i)
.dropletId(UUID.randomUUID().toString())
.id(UUID.randomUUID().toString())
.diskInMb(2048)
.sequenceId(i)
.state(TaskState.RUNNING)
.memoryInMb(2048)
.createdAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.build());
}
ListTasksResponse listTasksResponse = ListTasksResponse.builder().resources(taskResources)
.pagination(Pagination.builder().totalResults(taskResources.size()).build())
.build();
return Mono.just(listTasksResponse);
}
private Mono listOrganizationsResponse() {
ListOrganizationsResponse response = ListOrganizationsResponse.builder()
.addAllResources(Collections.singletonList(
OrganizationResource.builder()
.metadata(Metadata.builder().id("123").build()).build())
).build();
return Mono.just(response);
}
private Mono listSpacesResponse() {
ListSpacesResponse response = ListSpacesResponse.builder()
.addAllResources(Collections.singletonList(
SpaceResource.builder()
.metadata(Metadata.builder().id("123").build()).build())
).build();
return Mono.just(response);
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/ServiceParserTests.java
================================================
/*
* Copyright 2019-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.cloudfoundry;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
/** @author David Turanski */
public class ServiceParserTests {
@Test
public void plainService() {
assertThat(ServiceParser.getServiceParameters("test-service").isPresent()).isFalse();
assertThat(ServiceParser.getServiceInstanceName("test-service")).isEqualTo("test-service");
}
@Test
public void plainServiceWithSpecialCharacters() {
assertThat(ServiceParser.getServiceParameters("test.service.$$").isPresent()).isFalse();
}
@Test
public void serviceWithParameters() {
String serviceSpec = "test-service foo:bar";
assertThat(ServiceParser.getServiceParameters(serviceSpec).get())
.isEqualTo(Collections.singletonMap("foo", "bar"));
}
@Test
public void getServiceInstanceName() {
String serviceSpec = "test-service foo:bar";
assertThat(ServiceParser.getServiceInstanceName(serviceSpec)).isEqualTo("test-service");
}
@Test
public void serviceWithSpacesParameters() {
assertThat(ServiceParser.getServiceParameters("test-service foo : bar").get())
.isEqualTo(Collections.singletonMap("foo", "bar"));
}
@Test
public void serviceWithEqualsInParameters() {
assertThat(ServiceParser.getServiceParameters("test-service foo=bar").get())
.isEqualTo(Collections.singletonMap("foo", "bar"));
}
@Test
public void realWorldExample() {
Map params = ServiceParser.getServiceParameters(
"nfs share:1.2.3.4/export, uid:65534, gid:65534, mount:/var/scdf")
.get();
assertThat(params).containsOnly(
entry("share","1.2.3.4/export"),
entry("uid","65534"),
entry("gid","65534"),
entry("mount","/var/scdf")
);
}
@Test
public void anotherRealWorldExample() {
Map params = ServiceParser.getServiceParameters(
"nfs share=1.2.3.4/export, uid=65534, gid=65534, mount=/var/scdf")
.get();
assertThat(params).containsOnly(
entry("share","1.2.3.4/export"),
entry("uid","65534"),
entry("gid","65534"),
entry("mount","/var/scdf")
);
}
@Test
public void serviceWithInvalidParameters() {
assertThatThrownBy(() -> {
ServiceParser.getServiceParameters("test-service foo bar");
}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(
"invalid service specification: test-service foo bar");
}
@Test
public void splitServiceProperties() {
assertThat(ServiceParser.splitServiceProperties(
"'nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf',mysql,'foo bar:baz'"))
.containsExactlyInAnyOrder(
"nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf",
"mysql",
"foo bar:baz");
assertThat(ServiceParser.splitServiceProperties("mysql,rabbit,redis"))
.containsExactlyInAnyOrder(
"mysql",
"rabbit",
"redis");
assertThat(ServiceParser.splitServiceProperties(
"redis, 'nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf', mysql , 'foo bar:baz'"))
.containsExactlyInAnyOrder(
"redis",
"nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf",
"mysql",
"foo bar:baz");
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryAppSchedulerTests.java
================================================
/*
* Copyright 2018-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import io.pivotal.scheduler.SchedulerClient;
import io.pivotal.scheduler.v1.Pagination;
import io.pivotal.scheduler.v1.calls.Calls;
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.pivotal.scheduler.v1.schedules.ExpressionType;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v3.applications.ApplicationsV3;
import org.cloudfoundry.client.v3.tasks.Tasks;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.spaces.SpaceSummary;
import org.cloudfoundry.operations.spaces.Spaces;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
import org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;
import org.springframework.cloud.deployer.spi.scheduler.SchedulerException;
import org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Test the core features of the Spring Cloud Scheduler implementation.
*
* @author Glenn Renfro
* @author Ilayaperumal Gopinathan
*/
public class CloudFoundryAppSchedulerTests {
public static final String DEFAULT_CRON_EXPRESSION = "0/5 * ? * *";
public static final String CRON_EXPRESSION_FOR_SIX_MIN = "0/6 * ? * *";
public static final String BAD_CRON_EXPRESSION = "FOOBAD";
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Applications applications;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private CloudFoundryOperations operations;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Spaces spaces;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private ApplicationsV3 applicationsV3;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private CloudFoundryClient cloudFoundryClient;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private Tasks tasks;
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private CloudFoundryTaskLauncher taskLauncher;
private CloudFoundryAppScheduler deprecatedCloudFoundryAppScheduler;
private CloudFoundryAppScheduler deprecatedNoServiceCloudFoundryAppScheduler;
private CloudFoundryAppScheduler cloudFoundryAppScheduler;
private CloudFoundryAppScheduler noServiceCloudFoundryAppScheduler;
private SchedulerClient client;
private SchedulerClient noServiceClient;
private CloudFoundryConnectionProperties properties = new CloudFoundryConnectionProperties();
private CloudFoundrySchedulerProperties schedulerProperties = new CloudFoundrySchedulerProperties();
private CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
given(this.cloudFoundryClient.applicationsV3()).willReturn(this.applicationsV3);
given(this.cloudFoundryClient.tasks()).willReturn(this.tasks);
given(this.spaces.list()).willReturn(getTestSpaces());
this.properties.setSpace("test-space");
given(this.operations.applications()).willReturn(this.applications);
given(this.operations.spaces()).willReturn(this.spaces);
this.client = new TestSchedulerClient();
this.noServiceClient = new NoServiceTestSchedulerClient();
this.deprecatedCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.client, this.operations,
this.properties, taskLauncher, schedulerProperties);
this.deprecatedNoServiceCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.noServiceClient, this.operations,
this.properties, taskLauncher, schedulerProperties);
this.cloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.client, this.operations,
this.properties, taskLauncher, deploymentProperties);
this.noServiceCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.noServiceClient, this.operations,
this.properties, taskLauncher, deploymentProperties);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testEmptySchedulerProperties(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
AppDefinition definition = new AppDefinition("bar", null);
ScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, null, null, null, "testschedule", resource)
: new ScheduleRequest(definition, null, (List) null, "testschedule", resource);
assertThatThrownBy(() -> {
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
}).isInstanceOf(IllegalArgumentException.class);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testCreateNoCommandLineArgs(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
ScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, getDefaultScheduleProperties(),null, null, "test-schedule", resource)
: new ScheduleRequest(definition, getDefaultDeploymentProperties(), (List) null, "test-schedule", resource);
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getId()).isEqualTo("test-job-id-1");
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getApplicationId()).isEqualTo("test-application-id-1");
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getCommand()).isEmpty();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testInvalidCron(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
Map badCronMap = new HashMap<>();
badCronMap.put(SchedulerPropertyKeys.CRON_EXPRESSION, BAD_CRON_EXPRESSION);
ScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition,
Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, BAD_CRON_EXPRESSION), null, null, "test-schedule", resource)
: new ScheduleRequest(definition, Collections.singletonMap(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, BAD_CRON_EXPRESSION),
(List) null, "test-schedule", resource);
assertThatThrownBy(() -> {
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
}).isInstanceOf(CreateScheduleException.class).hasMessageContaining(
"Illegal characters for this position: 'FOO'");
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNameTooLong(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
Map cronMap = new HashMap<>();
cronMap.put(SchedulerPropertyKeys.CRON_EXPRESSION, DEFAULT_CRON_EXPRESSION);
ScheduleRequest request = new ScheduleRequest(definition, cronMap, (List) null,
"j1-scdf-itcouldbesaidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisis" +
"longtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-oopsitcouldbe" +
"saidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-" +
"oopsitcouldbesaidthatthisislongtoowaytoo-oops12", resource);
assertThatThrownBy(() -> {
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
}).isInstanceOf(CreateScheduleException.class).hasMessageContaining(
"Schedule can not be created because its name " +
"'j1-scdf-itcouldbesaidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisis" +
"longtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-oopsitcouldbe" +
"saidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-" +
"oopsitcouldbesaidthatthisislongtoowaytoo-oops12' has too many characters. " +
"Schedule name length must be 255 characters or less");
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testSuccessJobCreateFailedSchedule(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
ScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition,
Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, CRON_EXPRESSION_FOR_SIX_MIN), null, null, "test-schedule", resource) :
new ScheduleRequest(definition, Collections.singletonMap(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, CRON_EXPRESSION_FOR_SIX_MIN),
(List) null, "test-schedule", resource);
assertThatThrownBy(() -> {
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
}).isInstanceOf(CreateScheduleException.class);
assertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testCreateWithCommandLineArgs(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
ScheduleRequest request = isDeprecated ? new ScheduleRequest(definition,
getDefaultScheduleProperties(), null,
Collections.singletonList("TestArg"), "test-schedule", resource) :
new ScheduleRequest(definition,
getDefaultDeploymentProperties(),
Collections.singletonList("TestArg"), "test-schedule", resource);
getCloudFoundryAppScheduler(isDeprecated).schedule(request);
ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AppDeploymentRequest.class);
verify(this.taskLauncher).stage(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getCommandlineArguments().get(0)).isEqualTo("TestArg");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testList(boolean isDeprecated) {
setupMockResults();
List result = getCloudFoundryAppScheduler(isDeprecated).list();
assertThat(result.size()).isEqualTo(2);
verifyScheduleInfo(result.get(0), "test-application-1", "test-job-name-1", DEFAULT_CRON_EXPRESSION);
verifyScheduleInfo(result.get(1), "test-application-2", "test-job-name-2", DEFAULT_CRON_EXPRESSION);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testListWithJobsNoAssociatedSchedule(boolean isDeprecated) {
setupMockResultsNoScheduleForJobs();
List result = getCloudFoundryAppScheduler(isDeprecated).list();
assertThat(result.size()).isEqualTo(2);
verifyScheduleInfo(result.get(0), "test-application-1", "test-job-name-1", null);
verifyScheduleInfo(result.get(1), "test-application-2", "test-job-name-2", null);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testListWithNoSchedules(boolean isDeprecated) {
given(this.operations.applications()
.list())
.willReturn(Flux.empty());
List result = getCloudFoundryAppScheduler(isDeprecated).list();
assertThat(result.size()).isEqualTo(0);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testListSchedulesWithAppName(boolean isDeprecated) {
setupMockResults();
List result = getCloudFoundryAppScheduler(isDeprecated).list("test-application-2");
assertThat(result.size()).isEqualTo(1);
verifyScheduleInfo(result.get(0), "test-application-2", "test-job-name-2", DEFAULT_CRON_EXPRESSION);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testListSchedulesWithInvalidAppName(boolean isDeprecated) {
setupMockResults();
List result = getCloudFoundryAppScheduler(isDeprecated).list("not-here");
assertThat(result.size()).isEqualTo(0);
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testUnschedule(boolean isDeprecated) {
setupMockResults();
List result = getCloudFoundryAppScheduler(isDeprecated).list();
assertThat(result.size()).isEqualTo(2);
getCloudFoundryAppScheduler(isDeprecated).unschedule("test-job-name-1");
result = getCloudFoundryAppScheduler(isDeprecated).list();
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0).getScheduleName()).isEqualTo("test-job-name-2");
assertThat(result.get(0).getTaskDefinitionName()).isEqualTo("test-application-2");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testMissingScheduleDelete(boolean isDeprecated) {
boolean exceptionFired = false;
setupMockResults();
try {
getCloudFoundryAppScheduler(isDeprecated).unschedule("test-job-name-3");
}
catch (SchedulerException se) {
assertThat(se.getMessage()).isEqualTo("Failed to unschedule schedule test-job-name-3 does not exist.");
exceptionFired = true;
}
assertThat(exceptionFired).isTrue();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNoServiceList(boolean isDeprecated) {
assertThatThrownBy(() -> {
getNoServiceCloudFoundryAppScheduler(isDeprecated).list();
}).isInstanceOf(SchedulerException.class).hasMessageContaining(
"Scheduler Service returned a null response.");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNoServiceListSchedulesWithAppName(boolean isDeprecated) {
assertThatThrownBy(() -> {
getNoServiceCloudFoundryAppScheduler(isDeprecated).list("test-application-2");
}).isInstanceOf(SchedulerException.class).hasMessageContaining(
"Scheduler Service returned a null response.");
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNoServiceCreate(boolean isDeprecated) {
Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");
mockAppResultsInAppList();
AppDefinition definition = new AppDefinition("test-application-1", null);
ScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, getDefaultScheduleProperties(), null, null, "test-schedule", resource) :
new ScheduleRequest(definition, getDefaultDeploymentProperties(), (List) null, "test-schedule", resource);
assertThatThrownBy(() -> {
getNoServiceCloudFoundryAppScheduler(isDeprecated).schedule(request);
}).isInstanceOf(SchedulerException.class).hasMessageContaining(
"Scheduler Service returned a null response.");
}
private void givenRequestListApplications(Flux response) {
given(this.operations.applications()
.list())
.willReturn(response);
}
private void verifyScheduleInfo(ScheduleInfo scheduleInfo, String taskDefinitionName, String scheduleName, String expression) {
assertThat(scheduleInfo.getTaskDefinitionName()).isEqualTo(taskDefinitionName);
assertThat(scheduleInfo.getScheduleName()).isEqualTo(scheduleName);
if (expression != null) {
assertThat(scheduleInfo.getScheduleProperties().size()).isEqualTo(1);
assertThat(scheduleInfo.getScheduleProperties().get(SchedulerPropertyKeys.CRON_EXPRESSION)).isEqualTo(expression);
}
else {
assertThat(scheduleInfo.getScheduleProperties().size()).isEqualTo(0);
}
}
private static class TestSchedulerClient implements SchedulerClient {
private Jobs jobs;
public TestSchedulerClient() {
jobs = new TestJobs();
}
@Override
public Calls calls() {
return null;
}
@Override
public Jobs jobs() {
return jobs;
}
}
private static class NoServiceTestSchedulerClient implements SchedulerClient {
private Jobs jobs;
public NoServiceTestSchedulerClient() {
jobs = new NoServiceTestJobs();
}
@Override
public Calls calls() {
return null;
}
@Override
public Jobs jobs() {
return jobs;
}
}
private static class NoServiceTestJobs extends TestJobs {
@Override
public Mono list(ListJobsRequest request) {
return Mono.justOrEmpty(null);
}
@Override
public Mono create(CreateJobRequest request) {
return Mono.justOrEmpty(null);
}
}
private static class TestJobs implements Jobs {
private CreateJobResponse createJobResponse;
private List jobResources = new ArrayList<>();
private List jobScheduleResources = new ArrayList<>();
@Override
public Mono create(CreateJobRequest request) {
this.createJobResponse = CreateJobResponse.builder()
.applicationId(request.getApplicationId())
.name(request.getName())
.id("test-job-id-1")
.command(request.getCommand())
.build();
this.jobResources.add(Job.builder().applicationId(request.getApplicationId())
.command(request.getCommand())
.id("test-job-1")
.name(request.getName())
.build());
return Mono.just(createJobResponse);
}
@Override
public Mono delete(DeleteJobRequest request) {
for (int i = 0; i < this.jobResources.size(); i++) {
if (this.jobResources.get(i).getId().equals(request.getJobId())) {
jobResources.remove(i);
break;
}
}
return Mono.justOrEmpty(null);
}
@Override
public Mono deleteSchedule(DeleteJobScheduleRequest request) {
return null;
}
@Override
public Mono execute(ExecuteJobRequest request) {
return null;
}
@Override
public Mono get(GetJobRequest request) {
return null;
}
@Override
public Mono list(ListJobsRequest request) {
ListJobsResponse response = ListJobsResponse.builder()
.addAllResources(jobResources)
.pagination(Pagination.builder().totalPages(1).build())
.build();
return Mono.just(response);
}
@Override
public Mono listHistories(ListJobHistoriesRequest request) {
return null;
}
@Override
public Mono listScheduleHistories(ListJobScheduleHistoriesRequest request) {
return null;
}
@Override
public Mono listSchedules(ListJobSchedulesRequest request) {
ListJobSchedulesResponse response = ListJobSchedulesResponse.builder()
.addAllResources(jobScheduleResources.stream().filter(jobScheduleResource -> jobScheduleResource.getJobId().equals(request.getJobId())).collect(Collectors.toList()))
.build();
return Mono.just(response);
}
@Override
public Mono schedule(ScheduleJobRequest request) {
if(request.getExpression().equals(CRON_EXPRESSION_FOR_SIX_MIN)) {
throw new IllegalStateException();
}
return Mono.just(ScheduleJobResponse.builder().expression(request.getExpression())
.expressionType(request.getExpressionType())
.enabled(true)
.jobId(request.getJobId())
.id("schedule-1234")
.build());
}
public CreateJobResponse getCreateJobResponse() {
if(this.jobResources.size() == 0) {
this.createJobResponse = null;
}
return createJobResponse;
}
}
private Flux getTestSpaces() {
return Flux.just(SpaceSummary.builder().id("test-space-1")
.name("test-space")
.build());
}
private void setupMockResults() {
mockJobsInJobList();
mockAppResultsInAppList();
}
private void setupMockResultsNoScheduleForJobs() {
mockJobsInJobListNoSchedule();
mockAppResultsInAppList();
}
private void mockAppResultsInAppList() {
givenRequestListApplications(Flux.just(ApplicationSummary.builder()
.diskQuota(0)
.id("test-application-id-1")
.instances(1)
.memoryLimit(0)
.name("test-application-1")
.requestedState("RUNNING")
.runningInstances(1)
.build(),
ApplicationSummary.builder()
.diskQuota(0)
.id("test-application-id-2")
.instances(1)
.memoryLimit(0)
.name("test-application-2")
.requestedState("RUNNING")
.runningInstances(1)
.build()));
}
private void mockJobsInJobListNoSchedule() {
TestJobs localJobs = (TestJobs) client.jobs();
localJobs.jobResources.add(Job.builder().applicationId("test-application-id-1")
.command("test-command")
.id("test-job-1")
.name("test-job-name-1")
.build());
localJobs.jobResources.add(Job.builder().applicationId("test-application-id-2")
.command("test-command")
.id("test-job-2")
.name("test-job-name-2")
.build());
}
private void mockJobsInJobList() {
TestJobs localJobs = (TestJobs) client.jobs();
localJobs.jobResources.add(Job.builder().applicationId("test-application-id-1")
.command("test-command")
.id("test-job-1")
.name("test-job-name-1")
.jobSchedules(createJobScheduleList("test-job-1", "test-schedule-1"))
.build());
localJobs.jobResources.add(Job.builder().applicationId("test-application-id-2")
.command("test-command")
.id("test-job-2")
.name("test-job-name-2")
.jobSchedules(createJobScheduleList("test-job-2", "test-schedule-2"))
.build());
}
private List createJobScheduleList(String jobId, String scheduleId) {
List jobSchedules = new ArrayList<>();
jobSchedules.add(JobSchedule.builder()
.enabled(true)
.expression(DEFAULT_CRON_EXPRESSION)
.expressionType(ExpressionType.CRON)
.id(scheduleId)
.jobId(jobId)
.build());
return jobSchedules;
}
private Map getDefaultScheduleProperties() {
Map result = new HashMap<>();
result.put(SchedulerPropertyKeys.CRON_EXPRESSION, DEFAULT_CRON_EXPRESSION);
return result;
}
private Map getDefaultDeploymentProperties() {
Map result = new HashMap<>();
result.put(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, DEFAULT_CRON_EXPRESSION);
return result;
}
private CloudFoundryAppScheduler getCloudFoundryAppScheduler(boolean isDeprecated) {
return isDeprecated ? this.deprecatedCloudFoundryAppScheduler : this.cloudFoundryAppScheduler;
}
private CloudFoundryAppScheduler getNoServiceCloudFoundryAppScheduler(boolean isDeprecated) {
return isDeprecated ? this.deprecatedNoServiceCloudFoundryAppScheduler : this.noServiceCloudFoundryAppScheduler;
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryScheduleSSLExceptionTests.java
================================================
/*
* Copyright 2018-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Verify that {@linkCloudFoundryScheduleSSLException} has the expected behavior.
*
* @author Glenn Renfro
*/
public class CloudFoundryScheduleSSLExceptionTests {
@Test
public void testExceptionMessageOnly() {
try {
throw new CloudFoundryScheduleSSLException("oops");
}
catch (CloudFoundryScheduleSSLException cfe) {
assertThat(cfe.getMessage()).isEqualTo("oops");
}
}
@Test
public void testExceptionMessageWithException() {
RuntimeException rte = new RuntimeException("RTE");
try {
throw new CloudFoundryScheduleSSLException("oops", rte);
}
catch (CloudFoundryScheduleSSLException cfe) {
assertThat(cfe.getMessage()).isEqualTo("oops");
assertThat(cfe.getCause()).isEqualTo(rte);
}
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundrySchedulerPropertiesTest.java
================================================
/*
* Copyright 2018-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Validate the basic behavior of the {@link CloudFoundrySchedulerProperties}.
*
* @author Glenn Renfro
*/
public class CloudFoundrySchedulerPropertiesTest {
@Test
public void testProperties() {
CloudFoundrySchedulerProperties props = new CloudFoundrySchedulerProperties();
props.setSchedulerUrl("testProperty");
props.setScheduleSSLRetryCount(10);
props.setListTimeoutInSeconds(5);
props.setUnScheduleTimeoutInSeconds(10);
props.setScheduleTimeoutInSeconds(15);
assertThat(props.getSchedulerUrl()).isEqualTo("testProperty");
assertThat(props.getScheduleSSLRetryCount()).isEqualTo(10);
assertThat(props.getScheduleTimeoutInSeconds()).isEqualTo(15);
assertThat(props.getUnScheduleTimeoutInSeconds()).isEqualTo(10);
assertThat(props.getListTimeoutInSeconds()).isEqualTo(5);
}
@Test
public void testEmptyProperties() {
CloudFoundrySchedulerProperties props = new CloudFoundrySchedulerProperties();
assertThat(props.getSchedulerUrl()).isNull();
assertThat(props.getScheduleSSLRetryCount()).isEqualTo(5);
assertThat(props.getListTimeoutInSeconds()).isEqualTo(60);
assertThat(props.getScheduleTimeoutInSeconds()).isEqualTo(30);
assertThat(props.getUnScheduleTimeoutInSeconds()).isEqualTo(30);
}
}
================================================
FILE: spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/SpringCloudSchedulerIntegrationIT.java
================================================
/*
* Copyright 2018-2021 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
*
* 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.
*/
package org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.pivotal.reactor.scheduler.ReactorSchedulerClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.reactor.ConnectionContext;
import org.cloudfoundry.reactor.TokenProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties;
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
import org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;
import org.springframework.cloud.deployer.spi.scheduler.test.AbstractSchedulerIntegrationJUnit5Tests;
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* Integration tests for CloudFoundryAppScheduler.
*
* @author Glenn Renfro
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@ContextConfiguration(classes = {SpringCloudSchedulerIntegrationIT.Config.class})
public class SpringCloudSchedulerIntegrationIT extends AbstractSchedulerIntegrationJUnit5Tests {
@Autowired
protected MavenProperties mavenProperties;
@Autowired
private Scheduler scheduler;
@Value("${spring.cloud.deployer.cloudfoundry.services}")
private String deployerProps;
@Override
protected Scheduler provideScheduler() {
return this.scheduler;
}
@Autowired
private CloudFoundryOperations operations;
@Override
protected List getCommandLineArgs() {
return null;
}
@Override
protected Map