Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added auto book feature #2

Merged
merged 5 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,36 @@
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.function.Function;
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 +55,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, String> normalisedBookName = autoPages.keySet().stream()
.collect(Collectors.toMap(
PageManager::normaliseBookName,
Function.identity()
));
Nikita-Smirnov-Exactpro marked this conversation as resolved.
Show resolved Hide resolved

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(autoPages.get(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 +139,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 +150,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
Loading