Migrates existing schedules to SCDF 2.3.0
resolves #121 This app pulls in data from the PCFScheduler, AppSummary, AppManifest, the SCDF Task Definition Schema to construct the new schedule for the migration. You will note that for the App Summary, the code was pulled directly from the CF-Deployer, the reason this was done was to obtain additional schedule information required for the migration, without having to make another call to retrieve the app summary (i.e. obtain this detail while its still there). As discussed in the README, you will see that not all properties could be obtained from the available resources and thus have to be populated by a property that user can establish at runtime. However this also means this affects all schedules to be migrated. Updated to allow user to setup maven repository Updated instructions to include how to execute migration Updated docs Updated to support deployer format for tasks Also updated deployers to current release
This commit is contained in:
committed by
Michael Minella
parent
f6a75bac23
commit
c3f5c9e65c
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MigrateScheduleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MigrateScheduleApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.batch;
|
||||
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateProperties;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import io.spring.migrateschedule.service.ScheduleProcessedException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
|
||||
/**
|
||||
* Enriches the {@link ConvertScheduleInfo} with information obtained from the platform.
|
||||
* The new name for the schedule is established and the properties as well as commandline args
|
||||
* so that the SchedulerTaskLauncher can process the entries.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class SchedulerProcessor<T, C extends ScheduleInfo> implements ItemProcessor<ConvertScheduleInfo, ConvertScheduleInfo>{
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SchedulerProcessor.class);
|
||||
|
||||
private MigrateScheduleService migrateScheduleService;
|
||||
|
||||
private MigrateProperties migrateProperties;
|
||||
|
||||
public SchedulerProcessor(MigrateScheduleService migrateScheduleService, MigrateProperties migrateProperties) {
|
||||
this.migrateScheduleService = migrateScheduleService;
|
||||
this.migrateProperties = migrateProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConvertScheduleInfo process(ConvertScheduleInfo scheduleInfo){
|
||||
if(scheduleInfo.getScheduleName().contains(migrateProperties.getSchedulerToken())) {
|
||||
throw new ScheduleProcessedException(scheduleInfo.getScheduleName());
|
||||
}
|
||||
logger.info(String.format("Processing Schedule %s", scheduleInfo.getScheduleName()));
|
||||
return this.migrateScheduleService.enrichScheduleMetadata(scheduleInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.batch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Retrieves all of the available schedules.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class SchedulerReader<C extends ScheduleInfo> extends AbstractItemCountingItemStreamItemReader<ConvertScheduleInfo> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SchedulerReader.class);
|
||||
|
||||
private List<ConvertScheduleInfo> scheduleInfoList;
|
||||
|
||||
private MigrateScheduleService migrateScheduleService;
|
||||
|
||||
public SchedulerReader(MigrateScheduleService migrateScheduleService) {
|
||||
Assert.notNull(migrateScheduleService, "convertScheduleService must not be null");
|
||||
logger.info("Retrieving schedules from PCF Scheduler");
|
||||
this.migrateScheduleService = migrateScheduleService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConvertScheduleInfo doRead(){
|
||||
return this.scheduleInfoList.get(this.getCurrentItemCount()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOpen() {
|
||||
this.scheduleInfoList = migrateScheduleService.scheduleInfoList();
|
||||
this.setMaxItemCount(this.scheduleInfoList.size());
|
||||
this.setName("scheduler-reader");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.batch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
|
||||
/**
|
||||
* Migrates the original schedule to the new scheduler format required for SCDF
|
||||
* and stages the SchedulerTaskLauncher.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class SchedulerWriter<C extends ScheduleInfo> implements ItemWriter<ConvertScheduleInfo> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SchedulerWriter.class);
|
||||
|
||||
private Scheduler scheduler;
|
||||
|
||||
private MigrateScheduleService scheduleService;
|
||||
|
||||
public SchedulerWriter (MigrateScheduleService scheduleService, Scheduler scheduler) {
|
||||
this.scheduleService = scheduleService;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(List<? extends ConvertScheduleInfo> list) {
|
||||
for(ConvertScheduleInfo scheduleInfo : list) {
|
||||
logger.info(String.format("Migrating Schedule %s ", scheduleInfo.getScheduleName()));
|
||||
this.scheduleService.migrateSchedule(this.scheduler, scheduleInfo);
|
||||
logger.info(String.format("Migrated Schedule %s ", scheduleInfo.getScheduleName()));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.configuration;
|
||||
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import io.spring.migrateschedule.service.MigrateProperties;
|
||||
import io.spring.migrateschedule.batch.SchedulerProcessor;
|
||||
import io.spring.migrateschedule.batch.SchedulerReader;
|
||||
import io.spring.migrateschedule.batch.SchedulerWriter;
|
||||
import io.spring.migrateschedule.service.ScheduleProcessedException;
|
||||
import io.spring.migrateschedule.service.SchedulerSkipPolicy;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
import org.springframework.cloud.task.configuration.EnableTask;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Configuration
|
||||
@EnableBatchProcessing
|
||||
@EnableTask
|
||||
public class BatchConfiguration {
|
||||
|
||||
|
||||
@Autowired
|
||||
public JobBuilderFactory jobBuilderFactory;
|
||||
|
||||
@Autowired
|
||||
public StepBuilderFactory stepBuilderFactory;
|
||||
|
||||
@Bean
|
||||
public Job importUserJob(Step migrationStep) {
|
||||
return this.jobBuilderFactory.get("migrationJob")
|
||||
.incrementer(new RunIdIncrementer())
|
||||
.start(migrationStep)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Step migrationStep(SchedulerReader itemReader,
|
||||
SchedulerProcessor schedulerProcessor, SchedulerWriter writer) {
|
||||
return this.stepBuilderFactory.get("migrationStep")
|
||||
.<ScheduleInfo, ScheduleInfo> chunk(1)
|
||||
.reader(itemReader)
|
||||
.processor(schedulerProcessor)
|
||||
.writer(writer)
|
||||
.faultTolerant()
|
||||
.skip(ScheduleProcessedException.class)
|
||||
.skipPolicy(new SchedulerSkipPolicy())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SchedulerReader<ConvertScheduleInfo> itemReader(MigrateScheduleService scheduler) {
|
||||
return new SchedulerReader(scheduler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SchedulerWriter<ConvertScheduleInfo> itemWriter(Scheduler scheduler, MigrateScheduleService scheduleService) {
|
||||
return new SchedulerWriter(scheduleService, scheduler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SchedulerProcessor<ConvertScheduleInfo, ConvertScheduleInfo> itemProcessor(MigrateScheduleService migrateScheduleService, MigrateProperties migrateProperties) {
|
||||
return new SchedulerProcessor(migrateScheduleService, migrateProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties
|
||||
public MigrateProperties converterProperties() {
|
||||
return new MigrateProperties();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.configuration;
|
||||
|
||||
import io.pivotal.reactor.scheduler.ReactorSchedulerClient;
|
||||
import io.pivotal.scheduler.SchedulerClient;
|
||||
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
|
||||
import io.spring.migrateschedule.service.MigrateProperties;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import io.spring.migrateschedule.service.TaskDefinitionRepository;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.reactor.ConnectionContext;
|
||||
import org.cloudfoundry.reactor.TokenProvider;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.CloudFoundryAppScheduler;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.CloudFoundrySchedulerProperties;
|
||||
import org.springframework.cloud.deployer.spi.task.TaskLauncher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
/**
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Configuration
|
||||
@EntityScan({
|
||||
"org.springframework.cloud.dataflow.core"
|
||||
})
|
||||
public class CFMigrateScheduleConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactorSchedulerClient reactorSchedulerClient(ConnectionContext context,
|
||||
TokenProvider passwordGrantTokenProvider,
|
||||
CloudFoundrySchedulerProperties properties) {
|
||||
return ReactorSchedulerClient.builder()
|
||||
.connectionContext(context)
|
||||
.tokenProvider(passwordGrantTokenProvider)
|
||||
.root(Mono.just(properties.getSchedulerUrl()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloudFoundrySchedulerProperties cloudFoundrySchedulerProperties() {
|
||||
return new CloudFoundrySchedulerProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CFMigrateSchedulerService scheduleService(CloudFoundryOperations cloudFoundryOperations,
|
||||
SchedulerClient schedulerClient,
|
||||
CloudFoundryConnectionProperties properties, MigrateProperties migrateProperties,
|
||||
TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
|
||||
return new CFMigrateSchedulerService(cloudFoundryOperations,
|
||||
schedulerClient, properties, migrateProperties, taskDefinitionRepository, mavenProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloudFoundryAppScheduler scheduler(SchedulerClient client, CloudFoundryOperations operations,
|
||||
CloudFoundryConnectionProperties properties, TaskLauncher taskLauncher,
|
||||
CloudFoundrySchedulerProperties schedulerProperties) {
|
||||
return new CloudFoundryAppScheduler(client, operations, properties, (CloudFoundryTaskLauncher) taskLauncher, schedulerProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties
|
||||
public MavenProperties mavenProperties() {
|
||||
return new MavenProperties();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.cloud.dataflow.core.TaskDefinition;
|
||||
import org.springframework.cloud.dataflow.registry.support.AppResourceCommon;
|
||||
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract class containing methods that will be required for both Cloud Foundry
|
||||
* and the Kubernetes Impls.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public abstract class AbstractMigrateService implements MigrateScheduleService {
|
||||
|
||||
private final static String DATA_FLOW_URI_KEY = "spring.cloud.dataflow.client.serverUri";
|
||||
|
||||
private final static String COMMAND_ARGUMENT_PREFIX = "cmdarg.";
|
||||
|
||||
protected final static String APP_PREFIX = "app.";
|
||||
|
||||
protected final static String DEPLOYER_PREFIX = "deployer.";
|
||||
|
||||
protected MigrateProperties migrateProperties;
|
||||
|
||||
private TaskDefinitionRepository taskDefinitionRepository;
|
||||
|
||||
private MavenProperties mavenProperties;
|
||||
|
||||
public AbstractMigrateService(MigrateProperties migrateProperties, TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
|
||||
this.migrateProperties = migrateProperties;
|
||||
this.taskDefinitionRepository = taskDefinitionRepository;
|
||||
this.mavenProperties = mavenProperties;
|
||||
}
|
||||
|
||||
public TaskDefinition findTaskDefinitionByName(String taskDefinitionName) {
|
||||
return this.taskDefinitionRepository.findByTaskName(taskDefinitionName);
|
||||
}
|
||||
|
||||
protected String getSchedulePrefixDefinitionName(String taskDefinitionName) {
|
||||
return this.migrateProperties.getSchedulerToken() + taskDefinitionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retain only properties that are meant for the <em>scheduler</em> of a given task(those
|
||||
* that start with {@code scheduler.}and qualify all
|
||||
* property values with the {@code spring.cloud.scheduler.} prefix.
|
||||
*
|
||||
* @param input the scheduler properties
|
||||
* @return scheduler properties for the task
|
||||
*/
|
||||
protected static Map<String, String> extractAndQualifySchedulerProperties(Map<String, String> input) {
|
||||
final String prefix = "spring.cloud.scheduler.";
|
||||
|
||||
Map<String, String> result = new TreeMap<>(input).entrySet().stream()
|
||||
.filter(kv -> kv.getKey().startsWith(prefix))
|
||||
.collect(Collectors.toMap(kv -> kv.getKey(), kv -> kv.getValue(),
|
||||
(fromWildcard, fromApp) -> fromApp));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource for the SchedulerTaskLauncher and verify the URI.
|
||||
* @return {@link Resource} for the SchedulerTaskLauncher.
|
||||
*/
|
||||
protected Resource getTaskLauncherResource() {
|
||||
final URI url;
|
||||
try {
|
||||
new URI(this.migrateProperties.getSchedulerTaskLauncherUrl()); //verify url
|
||||
}
|
||||
catch (URISyntaxException uriSyntaxException) {
|
||||
throw new IllegalArgumentException(uriSyntaxException);
|
||||
}
|
||||
AppResourceCommon appResourceCommon = new AppResourceCommon(this.mavenProperties, new DefaultResourceLoader());
|
||||
return appResourceCommon.getResource(this.migrateProperties.getSchedulerTaskLauncherUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the appropriate tags to the command line args so that the SchedulerTaskLauncher can
|
||||
* extract them.
|
||||
* @param args the command line args to be tagged.
|
||||
* @return the tagged command line args.
|
||||
*/
|
||||
protected List<String> tagCommandLineArgs(List<String> args) {
|
||||
List<String> taggedArgs = new ArrayList<>();
|
||||
|
||||
for(String arg : args) {
|
||||
if(arg.contains("spring.cloud.task.name")) {
|
||||
continue;
|
||||
}
|
||||
String updatedArg = arg;
|
||||
if (!arg.startsWith(DATA_FLOW_URI_KEY) && !"--".concat(arg).startsWith(DATA_FLOW_URI_KEY)) {
|
||||
updatedArg = COMMAND_ARGUMENT_PREFIX +
|
||||
this.migrateProperties.getTaskLauncherPrefix() + "." + arg;
|
||||
}
|
||||
taggedArgs.add(updatedArg);
|
||||
}
|
||||
return taggedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the appropriate tags to the command line args so that the SchedulerTaskLauncher can
|
||||
* extract them.
|
||||
* @param appName the name of the application to be associated with the property
|
||||
* @param appProperties the properties to be tagged
|
||||
* @param prefix the prefix to mark the property as to be used by the SchedulerTaskLauncher.
|
||||
* @return the tagged command line args.
|
||||
*/
|
||||
protected Map<String, String> tagProperties(String appName, Map<String, String> appProperties, String prefix) {
|
||||
Map<String, String> taggedAppProperties = new HashMap<>(appProperties.size());
|
||||
|
||||
for(String key : appProperties.keySet()) {
|
||||
if(key.contains("spring.cloud.task.name")) {
|
||||
continue;
|
||||
}
|
||||
String updatedKey = key;
|
||||
if (!key.startsWith(DATA_FLOW_URI_KEY)) {
|
||||
if (StringUtils.hasText(appName)) {
|
||||
updatedKey = this.migrateProperties.getTaskLauncherPrefix() + "." +
|
||||
prefix + appName + "." + key;
|
||||
}
|
||||
else {
|
||||
updatedKey = this.migrateProperties.getTaskLauncherPrefix() + "." +
|
||||
prefix + key;
|
||||
}
|
||||
}
|
||||
taggedAppProperties.put(updatedKey, appProperties.get(key));
|
||||
}
|
||||
return taggedAppProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the required SchedulerTaskLauncher properties.
|
||||
* @param properties the map of properties in which to add the SchedulerTaskLauncher properties.
|
||||
* @return updated properties.
|
||||
*/
|
||||
protected Map<String, String> addSchedulerAppProps(Map<String, String> properties) {
|
||||
Map<String, String> appProperties = new HashMap<>(properties);
|
||||
appProperties.put("spring.cloud.dataflow.client.serverUri", this.migrateProperties.getDataflowServerUri());
|
||||
return appProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.pivotal.scheduler.SchedulerClient;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobsRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobsResponse;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
|
||||
import org.cloudfoundry.operations.applications.ApplicationManifest;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
|
||||
import org.cloudfoundry.operations.applications.Route;
|
||||
import org.cloudfoundry.operations.spaces.SpaceSummary;
|
||||
import org.codehaus.plexus.util.cli.CommandLineUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.dataflow.core.TaskDefinition;
|
||||
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
|
||||
import org.springframework.cloud.deployer.spi.core.AppDefinition;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.SchedulerException;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Services required to migrate schedules to the 2.3.0 format in Cloud Foundry
|
||||
* and stage the SchedulerTaskLauncher.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class CFMigrateSchedulerService extends AbstractMigrateService {
|
||||
|
||||
public static final String JAR_LAUNCHER = "org.springframework.boot.loader.JarLauncher";
|
||||
|
||||
private static final int JAR_LAUNCHER_LENGTH = JAR_LAUNCHER.length();
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CFMigrateSchedulerService.class);
|
||||
|
||||
private static final String CLOUD_FOUNDRY_PREFIX = "cloudfoundry";
|
||||
|
||||
private final static int PCF_PAGE_START_NUM = 1; //First PageNum for PCFScheduler starts at 1.
|
||||
|
||||
private final static String SCHEDULER_SERVICE_ERROR_MESSAGE = "Scheduler Service returned a null response.";
|
||||
|
||||
private CloudFoundryOperations cloudFoundryOperations;
|
||||
|
||||
private SchedulerClient schedulerClient;
|
||||
|
||||
private CloudFoundryConnectionProperties properties;
|
||||
|
||||
|
||||
public CFMigrateSchedulerService(CloudFoundryOperations cloudFoundryOperations,
|
||||
SchedulerClient schedulerClient,
|
||||
CloudFoundryConnectionProperties properties, MigrateProperties migrateProperties,
|
||||
TaskDefinitionRepository taskDefinitionRepository, MavenProperties mavenProperties) {
|
||||
super(migrateProperties, taskDefinitionRepository, mavenProperties);
|
||||
this.cloudFoundryOperations = cloudFoundryOperations;
|
||||
this.schedulerClient = schedulerClient;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConvertScheduleInfo> scheduleInfoList() {
|
||||
List<ConvertScheduleInfo> result = new ArrayList<>();
|
||||
int pageCount = getJobPageCount();
|
||||
for (int i = PCF_PAGE_START_NUM; i <= pageCount; i++) {
|
||||
logger.info(String.format("Reading Schedules Page %s of %s ", i, pageCount ));
|
||||
List<ConvertScheduleInfo> scheduleInfoPage = getSchedules(i);
|
||||
if(scheduleInfoPage == null) {
|
||||
throw new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);
|
||||
}
|
||||
result.addAll(scheduleInfoPage);
|
||||
}
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ConvertScheduleInfo> getSchedules(int page) {
|
||||
Flux<ApplicationSummary> applicationSummaries = cacheAppSummaries();
|
||||
return this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {
|
||||
return this.schedulerClient.jobs().list(ListJobsRequest.builder()
|
||||
.spaceId(requestSummary.getId())
|
||||
.page(page)
|
||||
.detailed(true).build());
|
||||
})
|
||||
.flatMapIterable(jobs -> jobs.getResources())// iterate over the resources returned.
|
||||
.flatMap(job -> {
|
||||
return getApplication(applicationSummaries,
|
||||
job.getApplicationId()) // get the application name for each job.
|
||||
.map(optionalApp -> {
|
||||
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setScheduleName(job.getName());
|
||||
scheduleInfo.setTaskDefinitionName(optionalApp.getName());
|
||||
String jobCommandLine = job.getCommand();
|
||||
String commandArgs = "";
|
||||
if (jobCommandLine != null && jobCommandLine.length() > JAR_LAUNCHER_LENGTH) {
|
||||
int locationOfArgs = job.getCommand().indexOf(JAR_LAUNCHER) + JAR_LAUNCHER_LENGTH;
|
||||
commandArgs = job.getCommand().substring(locationOfArgs);
|
||||
}
|
||||
else {
|
||||
logger.warn(String.format("Job %s does not have commandArgs associated with it.", job.getName()));
|
||||
}
|
||||
if (StringUtils.hasText(commandArgs)) {
|
||||
try {
|
||||
scheduleInfo.setCommandLineArgs(Arrays.asList(CommandLineUtils.translateCommandline(commandArgs)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
if (job.getJobSchedules() != null) {
|
||||
scheduleInfo.getScheduleProperties().put(SchedulerPropertyKeys.CRON_EXPRESSION,
|
||||
job.getJobSchedules().get(0).getExpression());
|
||||
}
|
||||
else {
|
||||
logger.warn(String.format("Job %s does not have an associated schedule", job.getName()));
|
||||
}
|
||||
return scheduleInfo;
|
||||
});
|
||||
})
|
||||
.filter(job -> isScheduleMigratable(job.getScheduleName()))
|
||||
.collectList().block();
|
||||
}
|
||||
|
||||
private boolean isScheduleMigratable(String scheduleName) {
|
||||
boolean result;
|
||||
if(migrateProperties.getScheduleNamesToMigrate().size() > 0) {
|
||||
result = migrateProperties.getScheduleNamesToMigrate().contains(scheduleName);
|
||||
}
|
||||
else {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConvertScheduleInfo enrichScheduleMetadata(ConvertScheduleInfo scheduleInfo) {
|
||||
logger.info(String.format("Retrieving Properties from application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
ApplicationEnvironments environment = this.cloudFoundryOperations.applications().
|
||||
getEnvironments(GetApplicationEnvironmentsRequest.builder().
|
||||
name(scheduleInfo.getTaskDefinitionName()).
|
||||
build()).
|
||||
block();
|
||||
|
||||
logger.info(String.format("Retrieving ApplicationManifest for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
ApplicationManifest applicationManifest = getApplicationManifest(scheduleInfo.getTaskDefinitionName());
|
||||
if(applicationManifest != null) {
|
||||
addApplicationManifestPropsToConvertScheduleInfo(scheduleInfo, applicationManifest);
|
||||
}
|
||||
if (environment != null) {
|
||||
for (Map.Entry<String, Object> var : environment.getUserProvided().entrySet()) {
|
||||
scheduleInfo.getScheduleProperties().put(var.getKey(), (String) var.getValue());
|
||||
}
|
||||
}
|
||||
logger.info(String.format("Tagging command line args for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
List<String> revisedCommandLineArgs = tagCommandLineArgs(scheduleInfo.getCommandLineArgs());
|
||||
revisedCommandLineArgs.add("--spring.cloud.scheduler.task.launcher.taskName=" + scheduleInfo.getTaskDefinitionName());
|
||||
scheduleInfo.setCommandLineArgs(revisedCommandLineArgs);
|
||||
Map<String, String> appProperties = null;
|
||||
try {
|
||||
logger.info(String.format("Extracting Spring App Properties for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
appProperties = getSpringAppProperties(scheduleInfo.getScheduleProperties());
|
||||
if(appProperties.size() > 0) {
|
||||
scheduleInfo.setUseSpringApplicationJson(true);
|
||||
}
|
||||
}
|
||||
catch (Exception exception) {
|
||||
throw new IllegalArgumentException("Unable to parse SPRING_APPLICATION_JSON from USER VARIABLES", exception);
|
||||
}
|
||||
logger.info(String.format("Retrieving Task Definition for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
TaskDefinition taskDefinition = findTaskDefinitionByName(appProperties.get("spring.cloud.task.name"));
|
||||
if (appProperties.size() > 0 && taskDefinition == null) {
|
||||
throw new IllegalStateException(String.format("The schedule %s contains " +
|
||||
"properties but the task definition %s does not exist and thus can't be migrated",
|
||||
scheduleInfo.getScheduleName(), scheduleInfo.getTaskDefinitionName()));
|
||||
}
|
||||
logger.info(String.format("Tagging app properties for application %s for schedule %s", scheduleInfo.getTaskDefinitionName(), scheduleInfo.getScheduleName()));
|
||||
appProperties = tagProperties(taskDefinition.getRegisteredAppName(), appProperties, APP_PREFIX);
|
||||
Map<String, String> deployerProperties = tagProperties(taskDefinition.getRegisteredAppName(),
|
||||
getDeployerProperties(scheduleInfo), DEPLOYER_PREFIX);
|
||||
appProperties = addSchedulerAppProps(appProperties);
|
||||
appProperties.putAll(deployerProperties);
|
||||
scheduleInfo.setAppProperties(appProperties);
|
||||
return scheduleInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateSchedule(Scheduler scheduler, ConvertScheduleInfo scheduleInfo) {
|
||||
String scheduleName = scheduleInfo.getScheduleName() + "-" + getSchedulePrefixDefinitionName(scheduleInfo.getTaskDefinitionName());
|
||||
AppDefinition appDefinition = new AppDefinition(scheduleName, scheduleInfo.getAppProperties());
|
||||
logger.info(String.format("Extracting schedule specific properties for schedule %s", scheduleInfo.getScheduleName()));
|
||||
Map<String, String> schedulerProperties = extractAndQualifySchedulerProperties(scheduleInfo.getScheduleProperties());
|
||||
ScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties, new HashMap<>(), scheduleInfo.getCommandLineArgs(), scheduleName, getTaskLauncherResource());
|
||||
logger.info(String.format("Staging ScheduleTaskLauncher and scheduling %s", scheduleInfo.getScheduleName()));
|
||||
scheduler.schedule(scheduleRequest);
|
||||
logger.info(String.format("Unscheduling original %s", scheduleInfo.getScheduleName()));
|
||||
scheduler.unschedule(scheduleInfo.getScheduleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of pages that can be returned when retrieving a list of jobs.
|
||||
* @return an int containing the number of available pages.
|
||||
*/
|
||||
private int getJobPageCount() {
|
||||
ListJobsResponse response = this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {
|
||||
return this.schedulerClient.jobs().list(ListJobsRequest.builder()
|
||||
.spaceId(requestSummary.getId())
|
||||
.detailed(false).build());
|
||||
}).block();
|
||||
if(response == null) {
|
||||
throw new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);
|
||||
}
|
||||
return response.getPagination().getTotalPages();
|
||||
}
|
||||
|
||||
private Map<String, String> getSpringAppProperties(Map<String, String> properties) throws Exception {
|
||||
Map<String, String> result;
|
||||
if(properties.containsKey("SPRING_APPLICATION_JSON")) {
|
||||
result = new ObjectMapper()
|
||||
.readValue(properties.get("SPRING_APPLICATION_JSON"), Map.class);
|
||||
}
|
||||
else {
|
||||
result = new HashMap<>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Mono} containing a {@link SpaceSummary} for the specified name.
|
||||
*
|
||||
* @param spaceName the name of space to search.
|
||||
* @return the {@link SpaceSummary} associated with the spaceName.
|
||||
*/
|
||||
private Mono<SpaceSummary> getSpace(String spaceName) {
|
||||
return requestSpaces()
|
||||
.cache() //cache results from first call.
|
||||
.filter(space -> spaceName.equals(space.getName()))
|
||||
.singleOrEmpty()
|
||||
.cast(SpaceSummary.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Flux} containing the available {@link SpaceSummary}s.
|
||||
*
|
||||
* @return {@link Flux} of {@link SpaceSummary}s.
|
||||
*/
|
||||
private Flux<SpaceSummary> requestSpaces() {
|
||||
return this.cloudFoundryOperations.spaces()
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached {@link Flux} of {@link ApplicationSummary}s.
|
||||
*/
|
||||
private Flux<ApplicationSummary> cacheAppSummaries() {
|
||||
return requestListApplications()
|
||||
.cache(); //cache results from first call. No need to re-retrieve each time.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Flux} of {@link ApplicationSummary}s.
|
||||
*/
|
||||
private Flux<ApplicationSummary> requestListApplications() {
|
||||
return this.cloudFoundryOperations.applications()
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Mono} containing the {@link ApplicationSummary} associated with the appId.
|
||||
*
|
||||
* @param applicationSummaries {@link Flux} of {@link ApplicationSummary}s to filter.
|
||||
* @param appId the id of the {@link ApplicationSummary} to search.
|
||||
*/
|
||||
private Mono<ApplicationSummary> getApplication(Flux<ApplicationSummary> applicationSummaries,
|
||||
String appId) {
|
||||
return applicationSummaries
|
||||
.filter(application -> appId.equals(application.getId()))
|
||||
.singleOrEmpty();
|
||||
}
|
||||
private ApplicationManifest getApplicationManifest(String appName) {
|
||||
return this.cloudFoundryOperations.applications()
|
||||
.getApplicationManifest(GetApplicationManifestRequest
|
||||
.builder().name(appName).build())
|
||||
.block();
|
||||
}
|
||||
|
||||
private Map<String, String> getDeployerProperties(ConvertScheduleInfo scheduleInfo) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
if (scheduleInfo.getJavaBuildPack() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".buildpack", scheduleInfo.getJavaBuildPack());
|
||||
}
|
||||
if (scheduleInfo.getMemoryInMB() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".memory", scheduleInfo.getMemoryInMB() + "m");
|
||||
}
|
||||
if (scheduleInfo.getDiskInMB() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".disk", scheduleInfo.getDiskInMB() + "m");
|
||||
}
|
||||
if (scheduleInfo.getApplicationHealthCheck() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check", scheduleInfo.getApplicationHealthCheck().getValue());
|
||||
}
|
||||
if (scheduleInfo.getHealthCheckEndPoint() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check-http-endpoint", scheduleInfo.getHealthCheckEndPoint());
|
||||
}
|
||||
if (scheduleInfo.getServices() != null && scheduleInfo.getServices().size() > 0) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".services", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getServices().toArray()));
|
||||
}
|
||||
if (scheduleInfo.getDomains() != null && scheduleInfo.getDomains().size() > 0) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".domain", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getDomains().toArray()));
|
||||
}
|
||||
if (scheduleInfo.getRoutes() != null && scheduleInfo.getRoutes().size() > 0) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".route-path", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getRoutes().toArray()));
|
||||
}
|
||||
if (scheduleInfo.getHosts() != null && scheduleInfo.getHosts().size() > 0) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".host", StringUtils.arrayToCommaDelimitedString(scheduleInfo.getHosts().toArray()));
|
||||
}
|
||||
|
||||
// Global deployer properties;
|
||||
if (this.migrateProperties.getHealthCheckTimeout() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".health-check-timeout", this.migrateProperties.getHealthCheckTimeout());
|
||||
}
|
||||
if (this.migrateProperties.getJavaOptions() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".javaOpts", this.migrateProperties.getJavaOptions());
|
||||
}
|
||||
if (this.migrateProperties.getApiTimeout() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".api-timeout", String.valueOf(this.migrateProperties.getApiTimeout()));
|
||||
}
|
||||
if (this.migrateProperties.getStatusTimeout() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".status-timeout", String.valueOf(this.migrateProperties.getStatusTimeout()));
|
||||
}
|
||||
if (this.migrateProperties.getStagingTimeout() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".staging-timeout", String.valueOf(this.migrateProperties.getStagingTimeout()));
|
||||
}
|
||||
if (this.migrateProperties.getStartupTimeout() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".startup-timeout", String.valueOf(this.migrateProperties.getStartupTimeout()));
|
||||
}
|
||||
if (this.migrateProperties.getMaximumConcurrentTasks() != null) {
|
||||
result.put(CLOUD_FOUNDRY_PREFIX + ".maximum-concurrent-tasks", String.valueOf(this.migrateProperties.getMaximumConcurrentTasks()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ConvertScheduleInfo addApplicationManifestPropsToConvertScheduleInfo(ConvertScheduleInfo scheduleInfo, ApplicationManifest applicationManifest) {
|
||||
scheduleInfo.setDiskInMB(applicationManifest.getDisk());
|
||||
scheduleInfo.setMemoryInMB(applicationManifest.getMemory());
|
||||
scheduleInfo.setApplicationHealthCheck(applicationManifest.getHealthCheckType());
|
||||
scheduleInfo.setJavaBuildPack(applicationManifest.getBuildpack());
|
||||
scheduleInfo.setHealthCheckEndPoint(applicationManifest.getHealthCheckHttpEndpoint());
|
||||
if(applicationManifest.getServices() != null && applicationManifest.getServices().size() > 0) {
|
||||
scheduleInfo.setServices(applicationManifest.getServices());
|
||||
}
|
||||
if(applicationManifest.getDomains() != null && applicationManifest.getDomains().size() > 0) {
|
||||
scheduleInfo.setDomains(applicationManifest.getDomains());
|
||||
}
|
||||
if(applicationManifest.getRoutes() != null && applicationManifest.getRoutes().size() > 0) {
|
||||
List<String> routes = new ArrayList<>();
|
||||
for (Route route : applicationManifest.getRoutes()) {
|
||||
routes.add(route.getRoute());
|
||||
}
|
||||
scheduleInfo.setRoutes(routes);
|
||||
}
|
||||
if (applicationManifest.getHosts() != null && applicationManifest.getHosts().size() > 0) {
|
||||
List<String> hosts = new ArrayList<>();
|
||||
for (String host : applicationManifest.getHosts()) {
|
||||
hosts.add(host);
|
||||
}
|
||||
scheduleInfo.setHosts(hosts);
|
||||
}
|
||||
|
||||
return scheduleInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
|
||||
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
|
||||
/**
|
||||
* A child implementation of {@link ScheduleInfo} that adds additional attributes
|
||||
* required to migrate to the new schedule format.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class ConvertScheduleInfo extends ScheduleInfo implements Comparable{
|
||||
|
||||
private List<String> commandLineArgs = new ArrayList<>();
|
||||
|
||||
private String registeredAppName;
|
||||
|
||||
private Map<String, String> appProperties = new HashMap<>();
|
||||
|
||||
private Integer diskInMB;
|
||||
|
||||
private Integer memoryInMB;
|
||||
|
||||
private String javaBuildPack;
|
||||
|
||||
private ApplicationHealthCheck applicationHealthCheck;
|
||||
|
||||
private String healthCheckEndPoint;
|
||||
|
||||
private Integer maximumConcurrentTasks = 20;
|
||||
|
||||
private boolean useSpringApplicationJson;
|
||||
|
||||
private List<String> services;
|
||||
|
||||
private List<String> domains;
|
||||
|
||||
private List<String> routes;
|
||||
|
||||
private List<String> hosts;
|
||||
|
||||
public List<String> getCommandLineArgs() {
|
||||
return commandLineArgs;
|
||||
}
|
||||
|
||||
public void setCommandLineArgs(List<String> commandLineArgs) {
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
}
|
||||
|
||||
public String getRegisteredAppName() {
|
||||
return registeredAppName;
|
||||
}
|
||||
|
||||
public void setRegisteredAppName(String registeredAppName) {
|
||||
this.registeredAppName = registeredAppName;
|
||||
}
|
||||
|
||||
public Map<String, String> getAppProperties() {
|
||||
return appProperties;
|
||||
}
|
||||
|
||||
public void setAppProperties(Map<String, String> appProperties) {
|
||||
this.appProperties = appProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if(o instanceof ConvertScheduleInfo) {
|
||||
return this.getScheduleName().compareTo(((ConvertScheduleInfo) o).getScheduleName());
|
||||
}
|
||||
throw new IllegalArgumentException("Can only compare Objects of type ConvertScheduleInfo");
|
||||
}
|
||||
|
||||
public Integer getDiskInMB() {
|
||||
return diskInMB;
|
||||
}
|
||||
|
||||
public void setDiskInMB(Integer diskInMB) {
|
||||
this.diskInMB = diskInMB;
|
||||
}
|
||||
|
||||
public Integer getMemoryInMB() {
|
||||
return memoryInMB;
|
||||
}
|
||||
|
||||
public void setMemoryInMB(Integer memoryInMB) {
|
||||
this.memoryInMB = memoryInMB;
|
||||
}
|
||||
|
||||
public String getJavaBuildPack() {
|
||||
return javaBuildPack;
|
||||
}
|
||||
|
||||
public void setJavaBuildPack(String javaBuildPack) {
|
||||
this.javaBuildPack = javaBuildPack;
|
||||
}
|
||||
|
||||
public ApplicationHealthCheck getApplicationHealthCheck() {
|
||||
return applicationHealthCheck;
|
||||
}
|
||||
|
||||
public void setApplicationHealthCheck(ApplicationHealthCheck applicationHealthCheck) {
|
||||
this.applicationHealthCheck = applicationHealthCheck;
|
||||
}
|
||||
|
||||
public boolean isUseSpringApplicationJson() {
|
||||
return useSpringApplicationJson;
|
||||
}
|
||||
|
||||
public void setUseSpringApplicationJson(boolean useSpringApplicationJson) {
|
||||
this.useSpringApplicationJson = useSpringApplicationJson;
|
||||
}
|
||||
|
||||
public String getHealthCheckEndPoint() {
|
||||
return healthCheckEndPoint;
|
||||
}
|
||||
|
||||
public void setHealthCheckEndPoint(String healthCheckEndPoint) {
|
||||
this.healthCheckEndPoint = healthCheckEndPoint;
|
||||
}
|
||||
|
||||
public Integer getMaximumConcurrentTasks() {
|
||||
return maximumConcurrentTasks;
|
||||
}
|
||||
|
||||
public void setMaximumConcurrentTasks(Integer maximumConcurrentTasks) {
|
||||
this.maximumConcurrentTasks = maximumConcurrentTasks;
|
||||
}
|
||||
|
||||
public List<String> getServices() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public void setServices(List<String> services) {
|
||||
this.services = services;
|
||||
}
|
||||
|
||||
public List<String> getDomains() {
|
||||
return domains;
|
||||
}
|
||||
|
||||
public void setDomains(List<String> domains) {
|
||||
this.domains = domains;
|
||||
}
|
||||
|
||||
public List<String> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(List<String> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
public List<String> getHosts() {
|
||||
return hosts;
|
||||
}
|
||||
|
||||
public void setHosts(List<String> hosts) {
|
||||
this.hosts = hosts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Allows user to configure the migration application.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class MigrateProperties {
|
||||
private String schedulerTaskLauncherUrl = "maven://org.springframework.cloud:spring-cloud-dataflow-scheduler-task-launcher:2.3.0.BUILD-SNAPSHOT";
|
||||
|
||||
/**
|
||||
* The token for the updated schedules.
|
||||
*/
|
||||
private String schedulerToken = "scdf-";
|
||||
|
||||
/**
|
||||
* The prefix to attach to the application properties to be sent to the SchedulerTaskLauncher.
|
||||
*/
|
||||
private String taskLauncherPrefix = "tasklauncher";
|
||||
|
||||
private String dataflowServerUri = "http://localhost:9393";
|
||||
|
||||
/**
|
||||
* The global Java Options required for the applications to be launched by the schedulerTaskLauncher.
|
||||
*/
|
||||
private String javaOptions;
|
||||
|
||||
/**
|
||||
* The global timeout to be assigned to applications to be launched by the schedulerTaskLauncher.
|
||||
*/
|
||||
private String healthCheckTimeout;
|
||||
|
||||
/**
|
||||
* The global api timeout to be assigned to applications to be launched by scheduleTaskLauncher.
|
||||
*/
|
||||
private Long apiTimeout;
|
||||
|
||||
/**
|
||||
* Timeout for status API operations in milliseconds to be assigned to applications to be launched by scheduleTaskLauncher
|
||||
*/
|
||||
private Long statusTimeout;
|
||||
|
||||
/**
|
||||
* If set, the global override the timeout allocated for staging an app launched by the schedulefTaskLauncher.
|
||||
*/
|
||||
private Long stagingTimeout;
|
||||
|
||||
/**
|
||||
* If set, the global override the timeout allocated for starting an app launched by scheduleTaskLauncher.
|
||||
*/
|
||||
private Long startupTimeout;
|
||||
|
||||
/**
|
||||
* If set, the global override for the maximum number of concurrently running tasks.
|
||||
*/
|
||||
private Integer maximumConcurrentTasks;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait for a schedule to complete.
|
||||
* This excludes the time it takes to stage the application on Cloud Foundry.
|
||||
*/
|
||||
private int scheduleTimeoutInSeconds = 30;
|
||||
|
||||
/**
|
||||
* Comma delimited list of schedules to migrate. If empty then all schedules will be migrated.
|
||||
*/
|
||||
private List<String> scheduleNamesToMigrate = new ArrayList<>();
|
||||
|
||||
public String getSchedulerTaskLauncherUrl() {
|
||||
return schedulerTaskLauncherUrl;
|
||||
}
|
||||
|
||||
public void setSchedulerTaskLauncherUrl(String schedulerTaskLauncherUrl) {
|
||||
this.schedulerTaskLauncherUrl = schedulerTaskLauncherUrl;
|
||||
}
|
||||
|
||||
public String getSchedulerToken() {
|
||||
return schedulerToken;
|
||||
}
|
||||
|
||||
public void setSchedulerToken(String schedulerToken) {
|
||||
this.schedulerToken = schedulerToken;
|
||||
}
|
||||
|
||||
public String getTaskLauncherPrefix() {
|
||||
return taskLauncherPrefix;
|
||||
}
|
||||
|
||||
public void setTaskLauncherPrefix(String taskLauncherPrefix) {
|
||||
this.taskLauncherPrefix = taskLauncherPrefix;
|
||||
}
|
||||
|
||||
public String getDataflowServerUri() {
|
||||
return dataflowServerUri;
|
||||
}
|
||||
|
||||
public void setDataflowServerUri(String dataflowServerUri) {
|
||||
this.dataflowServerUri = dataflowServerUri;
|
||||
}
|
||||
|
||||
public int getScheduleTimeoutInSeconds() {
|
||||
return scheduleTimeoutInSeconds;
|
||||
}
|
||||
|
||||
public void setScheduleTimeoutInSeconds(int scheduleTimeoutInSeconds) {
|
||||
this.scheduleTimeoutInSeconds = scheduleTimeoutInSeconds;
|
||||
}
|
||||
|
||||
public String getJavaOptions() {
|
||||
return javaOptions;
|
||||
}
|
||||
|
||||
public void setJavaOptions(String javaOptions) {
|
||||
this.javaOptions = javaOptions;
|
||||
}
|
||||
|
||||
public String getHealthCheckTimeout() {
|
||||
return healthCheckTimeout;
|
||||
}
|
||||
|
||||
public void setHealthCheckTimeout(String healthCheckTimeout) {
|
||||
this.healthCheckTimeout = healthCheckTimeout;
|
||||
}
|
||||
|
||||
public Long getApiTimeout() {
|
||||
return apiTimeout;
|
||||
}
|
||||
|
||||
public void setApiTimeout(Long apiTimeout) {
|
||||
this.apiTimeout = apiTimeout;
|
||||
}
|
||||
|
||||
public Long getStatusTimeout() {
|
||||
return statusTimeout;
|
||||
}
|
||||
|
||||
public void setStatusTimeout(Long statusTimeout) {
|
||||
this.statusTimeout = statusTimeout;
|
||||
}
|
||||
|
||||
public Long getStagingTimeout() {
|
||||
return stagingTimeout;
|
||||
}
|
||||
|
||||
public void setStagingTimeout(Long stagingTimeout) {
|
||||
this.stagingTimeout = stagingTimeout;
|
||||
}
|
||||
|
||||
public Long getStartupTimeout() {
|
||||
return startupTimeout;
|
||||
}
|
||||
|
||||
public void setStartupTimeout(Long startupTimeout) {
|
||||
this.startupTimeout = startupTimeout;
|
||||
}
|
||||
|
||||
public Integer getMaximumConcurrentTasks() {
|
||||
return maximumConcurrentTasks;
|
||||
}
|
||||
|
||||
public void setMaximumConcurrentTasks(Integer maximumConcurrentTasks) {
|
||||
this.maximumConcurrentTasks = maximumConcurrentTasks;
|
||||
}
|
||||
|
||||
public List<String> getScheduleNamesToMigrate() {
|
||||
return scheduleNamesToMigrate;
|
||||
}
|
||||
|
||||
public void setScheduleNamesToMigrate(List<String> scheduleNamesToMigrate) {
|
||||
this.scheduleNamesToMigrate = scheduleNamesToMigrate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.dataflow.core.TaskDefinition;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
|
||||
/**
|
||||
* Interface that establishes the method signatures required to migrate
|
||||
* schedules to the 2.3.0 format as well as stage the application.
|
||||
*/
|
||||
public interface MigrateScheduleService {
|
||||
|
||||
/**
|
||||
* Retrieve all available {@link ScheduleInfo}s.
|
||||
* @return list of available ScheduleInfos
|
||||
*/
|
||||
List<ConvertScheduleInfo> scheduleInfoList();
|
||||
|
||||
/**
|
||||
* Add properties and commandLine args to the {@link ScheduleInfo}
|
||||
* @return enriched {@link ConvertScheduleInfo}
|
||||
*/
|
||||
ConvertScheduleInfo enrichScheduleMetadata(ConvertScheduleInfo scheduleInfo);
|
||||
|
||||
/**
|
||||
* Migrates existing schedule to new SCDF schedule.
|
||||
* @param scheduler the deployer scheduler to build the new schedule.
|
||||
* @param scheduleInfo the schedule info containing the existing schedule.
|
||||
*/
|
||||
void migrateSchedule(Scheduler scheduler, ConvertScheduleInfo scheduleInfo);
|
||||
|
||||
/**
|
||||
* Retrieve {@link TaskDefinition} for the name provided
|
||||
* @param taskDefinitionName the name of the {@link TaskDefinition}.
|
||||
* @return a TaskDefinition
|
||||
*/
|
||||
TaskDefinition findTaskDefinitionByName(String taskDefinitionName);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
/**
|
||||
* Thrown if a schedule has already been processed.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class ScheduleProcessedException extends RuntimeException {
|
||||
public ScheduleProcessedException(String scheduleName) {
|
||||
super(String.format("Schedule %s has already been migrated", scheduleName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
|
||||
import org.springframework.batch.core.step.skip.SkipPolicy;
|
||||
|
||||
/**
|
||||
* Establish that there is no max maximum skip count if {@link ScheduleProcessedException} is thrown.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*
|
||||
*/
|
||||
public class SchedulerSkipPolicy implements SkipPolicy {
|
||||
@Override
|
||||
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
|
||||
boolean result = false;
|
||||
if(t instanceof ScheduleProcessedException) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule.service;
|
||||
|
||||
import org.springframework.cloud.dataflow.core.TaskDefinition;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Repository to access {@link org.springframework.cloud.dataflow.core.TaskDefinition}s.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @author Gunnar Hillert
|
||||
*/
|
||||
@Transactional
|
||||
public interface TaskDefinitionRepository extends PagingAndSortingRepository<org.springframework.cloud.dataflow.core.TaskDefinition, String> {
|
||||
|
||||
Page<org.springframework.cloud.dataflow.core.TaskDefinition> findByTaskNameContains(String taskName, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Performs a findByName query and throws an exception if the name is not found.
|
||||
* @param name the name of the task definition
|
||||
* @return The task definition instance or NoSuchTaskDefinitionException if not found.
|
||||
*/
|
||||
TaskDefinition findByTaskName(String name);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
logging.level.org.springframework.cloud.task=debug
|
||||
6
dataflow-migrate-schedules/src/main/resources/banner.txt
Normal file
6
dataflow-migrate-schedules/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
____ ____ ____ _____ ____ _ _ _ __ __ _ _
|
||||
/ ___| / ___| | _ \ | ___| / ___| ___ | |__ ___ __| | _ _ | | ___ ___ | \/ | (_) __ _ _ __ __ _ | |_ ___ _ __
|
||||
\___ \ | | | | | | | |_ \___ \ / __| | '_ \ / _ \ / _` | | | | | | | / _ \ / __| | |\/| | | | / _` | | '__| / _` | | __| / _ \ | '__|
|
||||
___) | | |___ | |_| | | _| ___) | | (__ | | | | | __/ | (_| | | |_| | | | | __/ \__ \ | | | | | | | (_| | | | | (_| | | |_ | (_) | | |
|
||||
|____/ \____| |____/ |_| |____/ \___| |_| |_| \___| \__,_| \__,_| |_| \___| |___/ |_| |_| |_| \__, | |_| \__,_| \__| \___/ |_|
|
||||
|___/
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule;
|
||||
|
||||
import org.cloudfoundry.doppler.LogMessage;
|
||||
import org.cloudfoundry.operations.applications.ApplicationEvent;
|
||||
import org.cloudfoundry.operations.applications.ApplicationManifest;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSshEnabledRequest;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.Applications;
|
||||
import org.cloudfoundry.operations.applications.CopySourceApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.DisableApplicationSshRequest;
|
||||
import org.cloudfoundry.operations.applications.EnableApplicationSshRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationEventsRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
|
||||
import org.cloudfoundry.operations.applications.ListApplicationTasksRequest;
|
||||
import org.cloudfoundry.operations.applications.LogsRequest;
|
||||
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
|
||||
import org.cloudfoundry.operations.applications.PushApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.RenameApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.RestageApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.RestartApplicationInstanceRequest;
|
||||
import org.cloudfoundry.operations.applications.RestartApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.RunApplicationTaskRequest;
|
||||
import org.cloudfoundry.operations.applications.ScaleApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.SetApplicationHealthCheckRequest;
|
||||
import org.cloudfoundry.operations.applications.SetEnvironmentVariableApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.StartApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.StopApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.Task;
|
||||
import org.cloudfoundry.operations.applications.TerminateApplicationTaskRequest;
|
||||
import org.cloudfoundry.operations.applications.UnsetEnvironmentVariableApplicationRequest;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public abstract class AbstractApplications implements Applications {
|
||||
@Override
|
||||
public Mono<Void> copySource(CopySourceApplicationRequest copySourceApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(DeleteApplicationRequest deleteApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> disableSsh(DisableApplicationSshRequest disableApplicationSshRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> enableSsh(EnableApplicationSshRequest enableApplicationSshRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ApplicationEvent> getEvents(GetApplicationEventsRequest getApplicationEventsRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ApplicationSummary> list() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Task> listTasks(ListApplicationTasksRequest listApplicationTasksRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<LogMessage> logs(LogsRequest logsRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> push(PushApplicationRequest pushApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> pushManifest(PushApplicationManifestRequest pushApplicationManifestRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> rename(RenameApplicationRequest renameApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> restage(RestageApplicationRequest restageApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> restart(RestartApplicationRequest restartApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> restartInstance(RestartApplicationInstanceRequest restartApplicationInstanceRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Task> runTask(RunApplicationTaskRequest runApplicationTaskRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> terminateTask(TerminateApplicationTaskRequest terminateApplicationTaskRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> scale(ScaleApplicationRequest scaleApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> setEnvironmentVariable(SetEnvironmentVariableApplicationRequest setEnvironmentVariableApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> setHealthCheck(SetApplicationHealthCheckRequest setApplicationHealthCheckRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> sshEnabled(ApplicationSshEnabledRequest applicationSshEnabledRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> start(StartApplicationRequest startApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> stop(StopApplicationRequest stopApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> unsetEnvironmentVariable(UnsetEnvironmentVariableApplicationRequest unsetEnvironmentVariableApplicationRequest) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.configuration.BatchConfiguration;
|
||||
import io.spring.migrateschedule.service.MigrateScheduleService;
|
||||
import io.spring.migrateschedule.service.TaskDefinitionRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.test.JobLauncherTestUtils;
|
||||
import org.springframework.batch.test.context.SpringBatchTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBatchTest
|
||||
@SpringBootTest
|
||||
@EnableAutoConfiguration(exclude = {CloudFoundryDeployerAutoConfiguration.class})
|
||||
@ContextConfiguration(classes = { BatchIntegrationTests.BatchTestConfiguration.class, BatchConfiguration.class})
|
||||
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class})
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
|
||||
public class BatchIntegrationTests {
|
||||
|
||||
public static final String DEFAULT_SCHEDULE_NAME = "defaultScheduleName";
|
||||
public static final String DEFAULT_TASK_DEFINITION_NAME = "defaultTaskDefinitionName";
|
||||
public static final String DEFAULT_APP_NAME = "defaultAppName";
|
||||
|
||||
@Autowired
|
||||
private JobLauncherTestUtils jobLauncherTestUtils;
|
||||
|
||||
@Autowired
|
||||
private MigrateScheduleService migrateScheduleService;
|
||||
|
||||
@Autowired
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
@MockBean
|
||||
private TaskDefinitionRepository taskDefinitionRepository;
|
||||
|
||||
@MockBean
|
||||
private Scheduler scheduler;
|
||||
|
||||
@Test
|
||||
public void baseTest() throws Exception{
|
||||
final ArgumentCaptor<Scheduler> schedulerArgumentCaptor = ArgumentCaptor.forClass(Scheduler.class);
|
||||
final ArgumentCaptor<ConvertScheduleInfo> scheduleInfoArgumentCaptor = ArgumentCaptor.forClass(ConvertScheduleInfo.class);
|
||||
verify(this.migrateScheduleService, times(2)).enrichScheduleMetadata(scheduleInfoArgumentCaptor.capture());
|
||||
verify(this.migrateScheduleService, times(2)).migrateSchedule(schedulerArgumentCaptor.capture(), scheduleInfoArgumentCaptor.capture());
|
||||
assertThat(this.jobExplorer.getJobInstanceCount("migrationJob")).isEqualTo(1);
|
||||
JobInstance jobInstance = this.jobExplorer.getJobInstances("migrationJob",0, 1).get(0);
|
||||
List<JobExecution> jobExecutions = this.jobExplorer.getJobExecutions(jobInstance);
|
||||
assertThat(jobExecutions.size()).isEqualTo(1);
|
||||
assertThat(jobExecutions.get(0).getExitStatus().getExitCode()).isEqualTo("COMPLETED");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BatchTestConfiguration {
|
||||
@Bean
|
||||
public MigrateScheduleService convertScheduleService() {
|
||||
MigrateScheduleService migrateScheduleService = mock(MigrateScheduleService.class);
|
||||
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
|
||||
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
|
||||
List<ConvertScheduleInfo> schedules = new ArrayList<>();
|
||||
schedules.add(scheduleInfo);
|
||||
scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME + 1);
|
||||
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME + 1);
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME + 1);
|
||||
schedules.add(scheduleInfo);
|
||||
when(migrateScheduleService.scheduleInfoList()).thenReturn(schedules);
|
||||
when(migrateScheduleService.enrichScheduleMetadata(any())).thenReturn(scheduleInfo);
|
||||
return migrateScheduleService;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.pivotal.scheduler.SchedulerClient;
|
||||
import io.pivotal.scheduler.v1.jobs.CreateJobRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.CreateJobResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.DeleteJobRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.DeleteJobScheduleRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ExecuteJobRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ExecuteJobResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.GetJobRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.GetJobResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.Job;
|
||||
import io.pivotal.scheduler.v1.jobs.JobSchedule;
|
||||
import io.pivotal.scheduler.v1.jobs.Jobs;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobHistoriesRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobHistoriesResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobSchedulesRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobSchedulesResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobsRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ListJobsResponse;
|
||||
import io.pivotal.scheduler.v1.jobs.ScheduleJobRequest;
|
||||
import io.pivotal.scheduler.v1.jobs.ScheduleJobResponse;
|
||||
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateProperties;
|
||||
import io.spring.migrateschedule.service.TaskDefinitionRepository;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
|
||||
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
|
||||
import org.cloudfoundry.operations.applications.ApplicationManifest;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationHealthCheckRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.operations.spaces.AllowSpaceSshRequest;
|
||||
import org.cloudfoundry.operations.spaces.CreateSpaceRequest;
|
||||
import org.cloudfoundry.operations.spaces.DeleteSpaceRequest;
|
||||
import org.cloudfoundry.operations.spaces.DisallowSpaceSshRequest;
|
||||
import org.cloudfoundry.operations.spaces.GetSpaceRequest;
|
||||
import org.cloudfoundry.operations.spaces.RenameSpaceRequest;
|
||||
import org.cloudfoundry.operations.spaces.SpaceDetail;
|
||||
import org.cloudfoundry.operations.spaces.SpaceSshAllowedRequest;
|
||||
import org.cloudfoundry.operations.spaces.SpaceSummary;
|
||||
import org.cloudfoundry.operations.spaces.Spaces;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class CFGetSchedulesTests {
|
||||
|
||||
private static final String DEFAULT_SPACE = "TESTSPACE";
|
||||
private static final String DEFAULT_APPLICATION_ID = "TEST_APPLICATION_ID";
|
||||
private static final String DEFAULT_COMMAND_ARG_PROP = "foo=bar";
|
||||
private static final String DEFAULT_SCHEDULE_EXPRESSION = "*/1 * ? * *";
|
||||
private static final String DEFAULT_COMMAND_ARG = CFMigrateSchedulerService.JAR_LAUNCHER + " " + DEFAULT_COMMAND_ARG_PROP;
|
||||
|
||||
private CFMigrateSchedulerService cfConvertSchedulerService;
|
||||
private CloudFoundryOperations cloudFoundryOperations;
|
||||
private CloudFoundryConnectionProperties cloudFoundryConnectionProperties;
|
||||
private MigrateProperties migrateProperties;
|
||||
private TaskDefinitionRepository taskDefinitionRepository;
|
||||
private SchedulerClient schedulerClient;
|
||||
private Scheduler scheduler;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.cloudFoundryOperations = Mockito.mock(CloudFoundryOperations.class);
|
||||
this.schedulerClient = Mockito.mock(SchedulerClient.class);
|
||||
this.cloudFoundryConnectionProperties = new CloudFoundryConnectionProperties();
|
||||
this.cloudFoundryConnectionProperties.setSpace(DEFAULT_SPACE);
|
||||
this.migrateProperties = new MigrateProperties();
|
||||
this.taskDefinitionRepository = Mockito.mock(TaskDefinitionRepository.class);
|
||||
this.scheduler = Mockito.mock(Scheduler.class);
|
||||
this.cfConvertSchedulerService = new CFMigrateSchedulerService(this.cloudFoundryOperations,
|
||||
this.schedulerClient,
|
||||
this.cloudFoundryConnectionProperties, this.migrateProperties,
|
||||
this.taskDefinitionRepository, new MavenProperties()) ;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetSchedules() {
|
||||
Mockito.when(this.cloudFoundryOperations.applications()).thenReturn(new TestApplications());
|
||||
Mockito.when(this.cloudFoundryOperations.spaces()).thenReturn(new TestSpaces());
|
||||
Mockito.when(this.schedulerClient.jobs()).thenReturn(new TestJobs());
|
||||
List<ConvertScheduleInfo> convertScheduleInfos = this.cfConvertSchedulerService.getSchedules(1);
|
||||
assertThat(convertScheduleInfos.size()).isEqualTo(2);
|
||||
|
||||
ConvertScheduleInfo convertScheduleInfo = convertScheduleInfos.get(0);
|
||||
baseTests(convertScheduleInfo,"JOB1", DEFAULT_APPLICATION_ID);
|
||||
assertThat(convertScheduleInfo.getCommandLineArgs().size()).isEqualTo(1);
|
||||
assertThat(convertScheduleInfo.getCommandLineArgs().get(0)).isEqualTo(DEFAULT_COMMAND_ARG_PROP);
|
||||
|
||||
convertScheduleInfo = convertScheduleInfos.get(1);
|
||||
assertThat(convertScheduleInfo.getCommandLineArgs().size()).isEqualTo(0);
|
||||
baseTests(convertScheduleInfo,"JOB2", DEFAULT_APPLICATION_ID);
|
||||
}
|
||||
|
||||
private void baseTests(ConvertScheduleInfo convertScheduleInfo, String scheduleName, String taskDefinitionName) {
|
||||
assertThat(convertScheduleInfo.getScheduleProperties().get("spring.cloud.scheduler.cron.expression")).isEqualTo("*/1 * ? * *");
|
||||
assertThat(convertScheduleInfo.getScheduleName()).isEqualTo(scheduleName);
|
||||
assertThat(convertScheduleInfo.getTaskDefinitionName()).isEqualTo(taskDefinitionName);
|
||||
}
|
||||
|
||||
private static class TestApplications extends AbstractApplications {
|
||||
@Override
|
||||
public Mono<ApplicationDetail> get(GetApplicationRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ApplicationSummary> list() {
|
||||
ApplicationSummary applicationSummary = ApplicationSummary.builder().
|
||||
id(DEFAULT_APPLICATION_ID).
|
||||
diskQuota(1024).
|
||||
instances(1).
|
||||
memoryLimit(1024).
|
||||
name(DEFAULT_APPLICATION_ID).
|
||||
requestedState("GOOD").
|
||||
runningInstances(1).
|
||||
build();
|
||||
Flux<ApplicationSummary> applicationSummaries = Flux.just(applicationSummary);
|
||||
return applicationSummaries;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestSpaces implements Spaces {
|
||||
|
||||
@Override
|
||||
public Mono<Void> allowSsh(AllowSpaceSshRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> create(CreateSpaceRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(DeleteSpaceRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> disallowSsh(DisallowSpaceSshRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SpaceDetail> get(GetSpaceRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<SpaceSummary> list() {
|
||||
SpaceSummary spaceSummary = SpaceSummary.builder().id(DEFAULT_SPACE).name(DEFAULT_SPACE).build();
|
||||
Flux<SpaceSummary> spaceSummaries = Flux.just(spaceSummary);
|
||||
return spaceSummaries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> rename(RenameSpaceRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> sshAllowed(SpaceSshAllowedRequest request) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestJobs implements Jobs {
|
||||
|
||||
@Override
|
||||
public Mono<CreateJobResponse> create(CreateJobRequest createJobRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(DeleteJobRequest deleteJobRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteSchedule(DeleteJobScheduleRequest deleteJobScheduleRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExecuteJobResponse> execute(ExecuteJobRequest executeJobRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetJobResponse> get(GetJobRequest getJobRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListJobsResponse> list(ListJobsRequest listJobsRequest) {
|
||||
Job job = Job.builder().id("JOB1").name("JOB1").
|
||||
command(DEFAULT_COMMAND_ARG).
|
||||
applicationId(DEFAULT_APPLICATION_ID).
|
||||
jobSchedule(JobSchedule.builder().
|
||||
expression("*/1 * ? * *").
|
||||
build()).
|
||||
build();
|
||||
List<Job> jobList = new ArrayList<>();
|
||||
jobList.add(job);
|
||||
|
||||
job = Job.builder().id("JOB2").name("JOB2").
|
||||
applicationId(DEFAULT_APPLICATION_ID).
|
||||
jobSchedule(JobSchedule.builder().
|
||||
expression(DEFAULT_SCHEDULE_EXPRESSION).
|
||||
build()).
|
||||
build();
|
||||
jobList.add(job);
|
||||
ListJobsResponse listJobsResponse = ListJobsResponse.builder().addAllResources(jobList).build();
|
||||
return Mono.just(listJobsResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListJobHistoriesResponse> listHistories(ListJobHistoriesRequest listJobHistoriesRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListJobScheduleHistoriesResponse> listScheduleHistories(ListJobScheduleHistoriesRequest listJobScheduleHistoriesRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListJobSchedulesResponse> listSchedules(ListJobSchedulesRequest listJobSchedulesRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ScheduleJobResponse> schedule(ScheduleJobRequest scheduleJobRequest) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.migrateschedule;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.pivotal.scheduler.SchedulerClient;
|
||||
import io.spring.migrateschedule.service.ConvertScheduleInfo;
|
||||
import io.spring.migrateschedule.service.MigrateProperties;
|
||||
import io.spring.migrateschedule.service.CFMigrateSchedulerService;
|
||||
import io.spring.migrateschedule.service.TaskDefinitionRepository;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
|
||||
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
|
||||
import org.cloudfoundry.operations.applications.ApplicationManifest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationHealthCheckRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.dataflow.core.TaskDefinition;
|
||||
import org.springframework.cloud.deployer.resource.maven.MavenProperties;
|
||||
import org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;
|
||||
import org.springframework.cloud.deployer.spi.scheduler.Scheduler;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class CFMigrateScheduleConfigurationTests {
|
||||
|
||||
public static final String DEFAULT_SCHEDULE_NAME = "defaultScheduleName";
|
||||
public static final String DEFAULT_TASK_DEFINITION_NAME = "defaultTaskDefinitionName";
|
||||
public static final String DEFAULT_APP_NAME = "defaultAppName";
|
||||
public static final String DEFAULT_CMD_ARG = "defaultCmd=WOW";
|
||||
public static final String DEFAULT_BUILD_PACK = "defaultBuildPack";
|
||||
|
||||
private CFMigrateSchedulerService cfConvertSchedulerService;
|
||||
private CloudFoundryOperations cloudFoundryOperations;
|
||||
private CloudFoundryConnectionProperties cloudFoundryConnectionProperties;
|
||||
private MigrateProperties migrateProperties;
|
||||
private TaskDefinitionRepository taskDefinitionRepository;
|
||||
private SchedulerClient schedulerClient;
|
||||
private Scheduler scheduler;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.cloudFoundryOperations = Mockito.mock(CloudFoundryOperations.class);
|
||||
this.schedulerClient = Mockito.mock(SchedulerClient.class);
|
||||
this.cloudFoundryConnectionProperties = new CloudFoundryConnectionProperties();
|
||||
this.migrateProperties = new MigrateProperties();
|
||||
this.taskDefinitionRepository = Mockito.mock(TaskDefinitionRepository.class);
|
||||
this.scheduler = Mockito.mock(Scheduler.class);
|
||||
this.cfConvertSchedulerService = new CFMigrateSchedulerService(this.cloudFoundryOperations,
|
||||
this.schedulerClient,
|
||||
this.cloudFoundryConnectionProperties, this.migrateProperties,
|
||||
this.taskDefinitionRepository, new MavenProperties()) ;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnrichment() {
|
||||
ConvertScheduleInfo scheduleInfo = createFoundationConvertScheduleInfo();
|
||||
assertThat(scheduleInfo.getAppProperties().keySet().size()).isEqualTo(5);
|
||||
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.app.defaultAppName.foo")).isEqualTo("bar");
|
||||
assertThat(scheduleInfo.getAppProperties().get("spring.cloud.dataflow.client.serverUri")).isEqualTo("http://localhost:9393");
|
||||
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.memory")).isEqualTo("1024m");
|
||||
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.health-check")).isEqualTo("port");
|
||||
assertThat(scheduleInfo.getAppProperties().get("tasklauncher.deployer.defaultAppName.cloudfoundry.disk")).isEqualTo("1024m");
|
||||
|
||||
assertThat(scheduleInfo.getCommandLineArgs().size()).isEqualTo(2);
|
||||
assertThat(scheduleInfo.getCommandLineArgs().get(0)).isEqualTo("cmdarg.tasklauncher.defaultCmd=WOW");
|
||||
assertThat(scheduleInfo.getCommandLineArgs().get(1)).isEqualTo("--spring.cloud.scheduler.task.launcher.taskName=defaultTaskDefinitionName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnrichmentNoProps() {
|
||||
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
|
||||
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
|
||||
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new NoPropertyApplication());
|
||||
TaskDefinition taskDefinition = TaskDefinition.TaskDefinitionBuilder
|
||||
.from(new TaskDefinition("fooTask", "foo"))
|
||||
.setTaskName(DEFAULT_TASK_DEFINITION_NAME)
|
||||
.setRegisteredAppName(DEFAULT_APP_NAME)
|
||||
.build();
|
||||
Mockito.when(this.taskDefinitionRepository.findByTaskName(Mockito.any())).thenReturn(taskDefinition);
|
||||
scheduleInfo = this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo);
|
||||
assertThat(scheduleInfo.getAppProperties().keySet().size()).isEqualTo(1);
|
||||
assertThat(scheduleInfo.getAppProperties().get("spring.cloud.dataflow.client.serverUri")).isEqualTo("http://localhost:9393");
|
||||
assertThat(scheduleInfo.getCommandLineArgs().size()).isEqualTo(1);
|
||||
assertThat(scheduleInfo.getCommandLineArgs().get(0)).isEqualTo("--spring.cloud.scheduler.task.launcher.taskName=defaultTaskDefinitionName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoTaskDefinition() {
|
||||
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
|
||||
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
|
||||
scheduleInfo.getCommandLineArgs().add(DEFAULT_CMD_ARG);
|
||||
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new SinglePropertyApplication());
|
||||
assertThrows(IllegalStateException.class, () -> this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMigrate() {
|
||||
ConvertScheduleInfo scheduleInfo = createFoundationConvertScheduleInfo();
|
||||
this.cfConvertSchedulerService.migrateSchedule(this.scheduler, scheduleInfo);
|
||||
final ArgumentCaptor<ScheduleRequest> scheduleRequestArgument = ArgumentCaptor.forClass(ScheduleRequest.class);
|
||||
final ArgumentCaptor<String> scheduleNameArg = ArgumentCaptor.forClass(String.class);
|
||||
verify(this.scheduler, times(1)).schedule(scheduleRequestArgument.capture());
|
||||
verify(this.scheduler, times(1)).unschedule(scheduleNameArg.capture());
|
||||
assertThat(scheduleRequestArgument.getValue().getScheduleName()).isEqualTo("defaultScheduleName-scdf-defaultTaskDefinitionName");
|
||||
assertThat(scheduleNameArg.getValue()).isEqualTo(DEFAULT_SCHEDULE_NAME);
|
||||
}
|
||||
|
||||
private ConvertScheduleInfo createFoundationConvertScheduleInfo() {
|
||||
ConvertScheduleInfo scheduleInfo = new ConvertScheduleInfo();
|
||||
scheduleInfo.setScheduleName(DEFAULT_SCHEDULE_NAME);
|
||||
scheduleInfo.setTaskDefinitionName(DEFAULT_TASK_DEFINITION_NAME);
|
||||
scheduleInfo.setScheduleProperties(new HashMap<>());
|
||||
scheduleInfo.setRegisteredAppName(DEFAULT_APP_NAME);
|
||||
scheduleInfo.getCommandLineArgs().add(DEFAULT_CMD_ARG);
|
||||
Mockito.when(cloudFoundryOperations.applications()).thenReturn(new SinglePropertyApplication());
|
||||
TaskDefinition taskDefinition = TaskDefinition.TaskDefinitionBuilder
|
||||
.from(new TaskDefinition("fooTask", "foo"))
|
||||
.setTaskName(DEFAULT_TASK_DEFINITION_NAME)
|
||||
.setRegisteredAppName(DEFAULT_APP_NAME)
|
||||
.build();
|
||||
Mockito.when(this.taskDefinitionRepository.findByTaskName(Mockito.any())).thenReturn(taskDefinition);
|
||||
return this.cfConvertSchedulerService.enrichScheduleMetadata(scheduleInfo);
|
||||
}
|
||||
|
||||
public static class SinglePropertyApplication extends AbstractApplications {
|
||||
@Override
|
||||
public Mono<ApplicationDetail> get(GetApplicationRequest getApplicationRequest) {
|
||||
ApplicationDetail applicationDetail = ApplicationDetail.builder()
|
||||
.buildpack(DEFAULT_BUILD_PACK)
|
||||
.stack("defaultstack")
|
||||
.diskQuota(1024)
|
||||
.id("defaultappidone")
|
||||
.memoryLimit(1024)
|
||||
.instances(1)
|
||||
.name(DEFAULT_TASK_DEFINITION_NAME)
|
||||
.requestedState("requestedState")
|
||||
.runningInstances(1)
|
||||
.build();
|
||||
return Mono.just(applicationDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest getApplicationEnvironmentsRequest) {
|
||||
return Mono.just(ApplicationEnvironments.builder().userProvided("SPRING_APPLICATION_JSON", "{\"foo\":\"bar\"}").build());
|
||||
}
|
||||
@Override
|
||||
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest getApplicationHealthCheckRequest) {
|
||||
return Mono.just(ApplicationHealthCheck.PORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest getApplicationManifestRequest) {
|
||||
return Mono.just(ApplicationManifest.builder().name(DEFAULT_TASK_DEFINITION_NAME)
|
||||
.disk(1024)
|
||||
.memory(1024)
|
||||
.healthCheckType(ApplicationHealthCheck.PORT)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
public static class NoPropertyApplication extends AbstractApplications {
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationDetail> get(GetApplicationRequest getApplicationRequest) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationEnvironments> getEnvironments(GetApplicationEnvironmentsRequest getApplicationEnvironmentsRequest) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationHealthCheck> getHealthCheck(GetApplicationHealthCheckRequest getApplicationHealthCheckRequest) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ApplicationManifest> getApplicationManifest(GetApplicationManifestRequest getApplicationManifestRequest) {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user