Skip to content

Commit

Permalink
Added auto book feature (#2)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleg Smirnov <[email protected]>
  • Loading branch information
Nikita-Smirnov-Exactpro and OptimumCode authored Oct 3, 2023
1 parent 8a4fcf7 commit 54781d6
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 38 deletions.
13 changes: 11 additions & 2 deletions cradle-admin-tool-http/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# cradle-admin-tool-http (1.7.2)
# cradle-admin-tool-http (1.8.0)
Service which allows user to manage books/pages via RestAPI requests.
- The first page in a book can be created only if start time is more than current time.
- After the first page all new pages must have start time more than current time + `bookRefreshIntervalMillis` * 2
Expand All @@ -12,7 +12,10 @@ Service which allows user to manage books/pages via RestAPI requests.
- **ip** - host where http cradle admin instance will be instanciated. Default value: `0.0.0.0`
- **port** - port on which http server will listen user requests. Default value: `8080`
- **page-recheck-interval** - interval in seconds which `PageManager` service checks if new page is required to create or not based on duration values presented in `auto-pages`. Default value: 60 seconds
- **auto-pages** - defines rule for automatic pages creation for multiple books. If empty no pages will be created automatically. Default value: `empty_map`.
- **auto-book** - if `true` than cradle-admin-tool creates books with first page for each value from `auto-pages` option when target book doesn't exist in cradle.
Creation book time is calculate by the `current time - 1 day` formula to cover events and messages published a bit earlier than cradle-admin-tool started. Default value: `true`.
Please note you can create your own book via REST API later.
- **auto-pages** - defines rule for automatic pages creation for multiple books. If empty no pages will be created automatically.
- **page-duration** - defines duration of the page for the book. Value uses the Java Duration format. You can read more about it [here](https://docs.oracle.com/javase/8/docsT/api/java/time/Duration.html#parse-java.lang.CharSequence-).
- **page-start-time** - baseline date and time for every new page created by `PageManager` for this book.

Expand All @@ -29,6 +32,7 @@ spec:
custom-config:
ip: 198.168.0.2
port: 8080
auto-book: true
auto-pages:
book1:
page-duration: PT60S
Expand All @@ -44,6 +48,11 @@ spec:
## Release notes
### 1.8.0
+ Feature:
+ Added auto-book functionality
### 1.7.2
+ Bug fix:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static void main(String[] args) {
resources.add(
new PageManager(
storage,
config.isAutoBook(),
config.getAutoPages(),
config.getPageRecheckInterval(),
settings.calculatePageActionRejectionThreshold() * 2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*******************************************************************************
/*
* Copyright 2023 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -12,10 +12,11 @@
* 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 com.exactpro.th2.cradle.adm.http;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.time.Duration;
import java.time.Instant;

Expand All @@ -34,4 +35,12 @@ public Duration getPageDuration() {
public Instant getPageStartTime() {
return pageStartTime;
}

public void setPageDuration(Duration pageDuration) {
this.pageDuration = pageDuration;
}

public void setPageStartTime(Instant pageStartTime) {
this.pageStartTime = pageStartTime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.exactpro.th2.cradle.adm.http;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Collections;
import java.util.Map;

@SuppressWarnings("FieldMayBeFinal")
Expand All @@ -35,8 +37,11 @@ public class Configuration {
@JsonProperty("page-recheck-interval")
private int pageRecheckInterval = DEFAULT_PAGE_RECHECK_INTERVAL_SEC;

@JsonProperty("auto-book")
private boolean autoBook = true;

@JsonProperty("auto-pages")
private Map<String, AutoPageConfiguration> autoPages;
private Map<String, AutoPageConfiguration> autoPages = Collections.emptyMap();

public String getIp() {
return ip;
Expand All @@ -46,6 +51,10 @@ public int getPort() {
return port;
}

public boolean isAutoBook() {
return autoBook;
}

public Map<String, AutoPageConfiguration> getAutoPages() {
return autoPages;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,35 @@
package com.exactpro.th2.cradle.adm.http;

import com.exactpro.cradle.BookInfo;
import com.exactpro.cradle.BookListEntry;
import com.exactpro.cradle.BookToAdd;
import com.exactpro.cradle.CradleStorage;
import com.exactpro.cradle.PageInfo;
import com.exactpro.cradle.utils.CradleStorageException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.StringUtils.lowerCase;
import static org.apache.commons.lang3.StringUtils.trim;

public class PageManager implements AutoCloseable, Runnable{
private static final Logger logger = LoggerFactory.getLogger(Application.class);
private static final String AUTO_PAGE_COMMENT = "auto-page";
private static final Logger LOGGER = LoggerFactory.getLogger(PageManager.class);
static final String AUTO_PAGE_COMMENT = "auto-page";
static final String AUTO_BOOK_DESCRIPTION = "auto-book";

private final CradleStorage storage;
private final long pageActionRejectionThreshold;
Expand All @@ -44,56 +54,78 @@ public class PageManager implements AutoCloseable, Runnable{

public PageManager(
CradleStorage storage,
boolean autoBooks,
Map<String, AutoPageConfiguration> autoPages,
int pageRecheckInterval,
long pageActionRejectionThreshold
) throws CradleStorageException {
) {
if (autoPages == null || autoPages.isEmpty()) {
logger.info("auto-page configuration is not provided, pages will not be generated automatically");
LOGGER.info("auto-page configuration is not provided, pages will not be generated automatically");
this.storage = null;
this.pageActionRejectionThreshold = 0;
this.executorService = null;
this.books = Collections.emptyMap();
return;
}

this.storage = storage;
this.pageActionRejectionThreshold = pageActionRejectionThreshold;
Map<String, AutoPageConfiguration> normalisedBookName = autoPages.entrySet().stream()
.collect(Collectors.toMap(
entry -> normaliseBookName(entry.getKey()),
Map.Entry::getValue
));

books = new HashMap<>();
for (String bookName : autoPages.keySet()) {
books.put(bookName, new AutoPageInfo(autoPages.get(bookName), storage.refreshBook(bookName)));
if (normalisedBookName.size() != autoPages.size()) {
throw new IllegalArgumentException("Some of books have the same name after normalization" +
", origin: " + autoPages.keySet() +
", normalized: " + normalisedBookName.keySet());
}

logger.info("Managing pages for books {} every {} sec", books.keySet().toArray(), pageRecheckInterval);
books = normalisedBookName.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
entry -> new AutoPageInfo(entry.getValue(), getOrCreateBook(storage, entry.getKey(), autoBooks))
));

this.storage = storage;
this.pageActionRejectionThreshold = pageActionRejectionThreshold;

LOGGER.info("Managing pages for books {} every {} sec", books.keySet().toArray(), pageRecheckInterval);
executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(this, 0, pageRecheckInterval, TimeUnit.SECONDS);
}

@NotNull
private static String normaliseBookName(String origin) {
String bookName = lowerCase(trim(origin));
if (bookName == null || bookName.isEmpty()) {
throw new IllegalArgumentException("One of book is null or empty");
}
return bookName;
}


private BookInfo checkBook(BookInfo book, AutoPageConfiguration autoPageConfiguration) throws Exception {
Instant pageStartBase = autoPageConfiguration.getPageStartTime();
Duration pageDuration = autoPageConfiguration.getPageDuration();
Instant nowPlusThreshold = Instant.now().plusMillis(pageActionRejectionThreshold);
long nowMillis = nowPlusThreshold.toEpochMilli();

PageInfo pageInfo = book.getLastPage();

if (pageInfo == null) {
return storage.addPage(book.getId(), "auto-page-" + nowMillis, nowPlusThreshold, AUTO_PAGE_COMMENT);
return createAutoPage(storage, book, nowPlusThreshold);
}

Instant lastPageStart = pageInfo.getStarted();
if (lastPageStart.isBefore(nowPlusThreshold)) {
int comparison = nowPlusThreshold.compareTo(pageStartBase);
if (comparison < 0) {
return storage.addPage(book.getId(), "auto-page-" + nowMillis, pageStartBase, AUTO_PAGE_COMMENT);
return createAutoPage(storage, book, pageStartBase);
} else if (comparison > 0) {
Duration diff = Duration.between(pageStartBase, nowPlusThreshold);
Instant nextMark = pageStartBase.plus(pageDuration.multipliedBy(diff.dividedBy(pageDuration) + 1));
return storage.addPage(book.getId(), "auto-page-" + nowMillis, nextMark, AUTO_PAGE_COMMENT);
return createAutoPage(storage, book, nextMark);
} else {
return storage.addPage(book.getId(), "auto-page-" + nowMillis, pageStartBase.plus(pageDuration), AUTO_PAGE_COMMENT);
return createAutoPage(storage, book, pageStartBase.plus(pageDuration));
}
}

Expand All @@ -106,7 +138,7 @@ public void close() throws Exception {
executorService.shutdown();
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
List<Runnable> tasks = executorService.shutdownNow();
logger.warn("Executor can't stop during 5 seconds, " + tasks + " tasks that never commenced execution");
LOGGER.warn("Executor can't stop during 5 seconds, " + tasks + " tasks that never commenced execution");
}
}
}
Expand All @@ -117,8 +149,51 @@ public void run() {
try {
autoPageInfo.setBookInfo(checkBook(autoPageInfo.getBookInfo(), autoPageInfo.getAutoPageConfiguration()));
} catch (Exception e) {
logger.error("Exception processing book {}", bookName, e);
LOGGER.error("Exception processing book {}", bookName, e);
}
});
}

private static BookInfo getOrCreateBook(@NotNull CradleStorage storage, String bookName, boolean autoBook) {
try {
BookListEntry bookListEntry = storage.listBooks().stream()
.filter(entry -> Objects.equals(entry.getName(), bookName))
.findFirst()
.orElse(null);

if (bookListEntry != null) {
return storage.refreshBook(bookName);
}

if (!autoBook) {
throw new IllegalStateException("Storage doesn't contain the '" + bookName + "' book. Create book manually or enable auto-book functionality in configuration");
}

BookToAdd bookToAdd = new BookToAdd(
bookName,
Instant.now().minus(1, ChronoUnit.DAYS)
);
bookToAdd.setFullName(bookName);
bookToAdd.setDesc(AUTO_BOOK_DESCRIPTION);

BookInfo bookInfo = storage.addBook(bookToAdd);

LOGGER.info("Created '{}' book, time: {}, full name: {}, description: {}",
bookName,
bookToAdd.getCreated(),
bookToAdd.getFullName(),
bookToAdd.getDesc());

createAutoPage(storage, bookInfo, bookInfo.getCreated());
LOGGER.info("Added first page, book: {}, time: {}", bookInfo.getId().getName(), bookInfo.getCreated());
return bookInfo;
} catch (Exception e) {
throw new RuntimeException("Book with name '" + bookName + "' can't be created", e);
}
}

private static BookInfo createAutoPage(CradleStorage storage, BookInfo book, Instant nowPlusThreshold) throws CradleStorageException, IOException {
long nowMillis = nowPlusThreshold.toEpochMilli();
return storage.addPage(book.getId(), "auto-page-" + nowMillis, nowPlusThreshold, AUTO_PAGE_COMMENT);
}
}
Loading

0 comments on commit 54781d6

Please sign in to comment.