<commit_before>/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.license.plugin.core;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.FutureUtils;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.EmptyTransportResponseHandler;
import org.elasticsearch.transport.TransportChannel;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportService;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Service responsible for managing {@link LicensesMetaData}
 * Interfaces through which this is exposed are:
 * - LicensesManagerService - responsible for managing signed and one-time-trial licenses
 * - LicensesClientService - responsible for listener registration of consumer plugin(s)
 * <p>
 * Registration Scheme:
 * <p>
 * A consumer plugin is registered with {@link LicenseeRegistry#register(Licensee)}
 * This method can be called at any time during the life-cycle of the consumer plugin.
 * If the listener can not be registered immediately, it is queued up and registered on the first clusterChanged event with
 * no {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK} block
 * Upon successful registration, the listeners are notified appropriately using the notification scheme
 * <p>
 * Notification Scheme:
 * <p>
 * All registered listeners are notified of the current license upon registration or when a new license is installed in the cluster state.
 * When a new license is notified as enabled to the registered listener, a notification is scheduled at the time of license expiry.
 * Registered listeners are notified using {@link #notifyAndSchedule(LicensesMetaData)}
 */
@Singleton
public class LicensesService extends AbstractLifecycleComponent<LicensesService> implements ClusterStateListener, LicensesManagerService,
        LicenseeRegistry {

    public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license";

    private final ClusterService clusterService;

    private final ThreadPool threadPool;

    private final TransportService transportService;

    /**
     * Currently active consumers to notify to
     */
    private final List<InternalLicensee> registeredLicensees = new CopyOnWriteArrayList<>();

    /**
     * Currently active expiry notifications
     */
    private final Queue<ScheduledFuture> expiryNotifications = new ConcurrentLinkedQueue<>();

    /**
     * Currently active event notifications for every registered listener
     */
    private final Queue<ScheduledFuture> eventNotifications = new ConcurrentLinkedQueue<>();

    /**
     * Currently active license
     */
    private final AtomicReference<License> currentLicense = new AtomicReference<>();

    /**
     * Callbacks to notify relative to license expiry
     */
    private List<ExpirationCallback> expirationCallbacks = new ArrayList<>();

    /**
     * Duration of generated trial license
     */
    private TimeValue trialLicenseDuration = TimeValue.timeValueHours(30 * 24);

    /**
     * Max number of nodes licensed by generated trial license
     */
    private int trialLicenseMaxNodes = 1000;

    /**
     * Duration of grace period after a license has expired
     */
    private TimeValue gracePeriodDuration = days(7);

    private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);

    private static final String ACKNOWLEDGEMENT_HEADER = "This license update requires acknowledgement. To acknowledge the license, " +
            "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:";

    @Inject
    public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
        super(settings);
        this.clusterService = clusterService;
        this.threadPool = threadPool;
        this.transportService = transportService;
        if (DiscoveryNode.isMasterNode(settings)) {
            transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, TransportRequest.Empty::new,
                    ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
        }
        populateExpirationCallbacks();
    }

    private void populateExpirationCallbacks() {
        expirationCallbacks.add(new ExpirationCallback.Pre(days(7), days(30), days(1)) {
                                    @Override
                                    public void on(License license) {
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# License will expire on [{}]. If you have a new license, please update it.\n" +
                                                "# Otherwise, please reach out to your support contact.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# Commercial plugins operate with reduced functionality on license " +
                                                    "expiration:");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.error("{}", builder);
                                        } else {
                                            logger.error("{}", general);
                                        }
                                    }
                                }
        );
        expirationCallbacks.add(new ExpirationCallback.Pre(days(0), days(7), TimeValue.timeValueMinutes(10)) {
                                    @Override
                                    public void on(License license) {
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# License will expire on [{}]. If you have a new license, please update it.\n" +
                                                "# Otherwise, please reach out to your support contact.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# Commercial plugins operate with reduced functionality on license " +
                                                    "expiration:");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.error("{}", builder.toString());
                                        } else {
                                            logger.error("{}", general);
                                        }
                                    }
                                }
        );
        expirationCallbacks.add(new ExpirationCallback.Post(days(0), null, TimeValue.timeValueMinutes(10)) {
                                    @Override
                                    public void on(License license) {
                                        // logged when grace period begins
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# LICENSE EXPIRED ON [{}]. IF YOU HAVE A NEW LICENSE, PLEASE\n" +
                                                "# UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# COMMERCIAL PLUGINS OPERATING WITH REDUCED FUNCTIONALITY");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.error("{}", builder.toString());
                                        } else {
                                            logger.error("{}", general);
                                        }
                                    }
                                }
        );
    }

    /**
     * Registers new license in the cluster
     * Master only operation. Installs a new license on the master provided it is VALID
     */
    public void registerLicense(final PutLicenseRequest request, final ActionListener<LicensesUpdateResponse> listener) {
        final License newLicense = request.license();
        final long now = System.currentTimeMillis();
        if (!verifyLicense(newLicense) || newLicense.issueDate() > now) {
            listener.onResponse(new LicensesUpdateResponse(true, LicensesStatus.INVALID));
        } else if (newLicense.expiryDate() < now) {<commit_msg>Fix license expiry log message  - change license expiration message from error to warn  - start logging expiry message from 25 days instead of 30<commit_after>/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.license.plugin.core;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.FutureUtils;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.EmptyTransportResponseHandler;
import org.elasticsearch.transport.TransportChannel;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportService;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Service responsible for managing {@link LicensesMetaData}
 * Interfaces through which this is exposed are:
 * - LicensesManagerService - responsible for managing signed and one-time-trial licenses
 * - LicensesClientService - responsible for listener registration of consumer plugin(s)
 * <p>
 * Registration Scheme:
 * <p>
 * A consumer plugin is registered with {@link LicenseeRegistry#register(Licensee)}
 * This method can be called at any time during the life-cycle of the consumer plugin.
 * If the listener can not be registered immediately, it is queued up and registered on the first clusterChanged event with
 * no {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK} block
 * Upon successful registration, the listeners are notified appropriately using the notification scheme
 * <p>
 * Notification Scheme:
 * <p>
 * All registered listeners are notified of the current license upon registration or when a new license is installed in the cluster state.
 * When a new license is notified as enabled to the registered listener, a notification is scheduled at the time of license expiry.
 * Registered listeners are notified using {@link #notifyAndSchedule(LicensesMetaData)}
 */
@Singleton
public class LicensesService extends AbstractLifecycleComponent<LicensesService> implements ClusterStateListener, LicensesManagerService,
        LicenseeRegistry {

    public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license";

    private final ClusterService clusterService;

    private final ThreadPool threadPool;

    private final TransportService transportService;

    /**
     * Currently active consumers to notify to
     */
    private final List<InternalLicensee> registeredLicensees = new CopyOnWriteArrayList<>();

    /**
     * Currently active expiry notifications
     */
    private final Queue<ScheduledFuture> expiryNotifications = new ConcurrentLinkedQueue<>();

    /**
     * Currently active event notifications for every registered listener
     */
    private final Queue<ScheduledFuture> eventNotifications = new ConcurrentLinkedQueue<>();

    /**
     * Currently active license
     */
    private final AtomicReference<License> currentLicense = new AtomicReference<>();

    /**
     * Callbacks to notify relative to license expiry
     */
    private List<ExpirationCallback> expirationCallbacks = new ArrayList<>();

    /**
     * Duration of generated trial license
     */
    private TimeValue trialLicenseDuration = TimeValue.timeValueHours(30 * 24);

    /**
     * Max number of nodes licensed by generated trial license
     */
    private int trialLicenseMaxNodes = 1000;

    /**
     * Duration of grace period after a license has expired
     */
    private TimeValue gracePeriodDuration = days(7);

    private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);

    private static final String ACKNOWLEDGEMENT_HEADER = "This license update requires acknowledgement. To acknowledge the license, " +
            "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:";

    @Inject
    public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
        super(settings);
        this.clusterService = clusterService;
        this.threadPool = threadPool;
        this.transportService = transportService;
        if (DiscoveryNode.isMasterNode(settings)) {
            transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, TransportRequest.Empty::new,
                    ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
        }
        populateExpirationCallbacks();
    }

    private void populateExpirationCallbacks() {
        expirationCallbacks.add(new ExpirationCallback.Pre(days(7), days(25), days(1)) {
                                    @Override
                                    public void on(License license) {
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# License will expire on [{}]. If you have a new license, please update it.\n" +
                                                "# Otherwise, please reach out to your support contact.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# Commercial plugins operate with reduced functionality on license " +
                                                    "expiration:");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.warn("{}", builder);
                                        } else {
                                            logger.warn("{}", general);
                                        }
                                    }
                                }
        );
        expirationCallbacks.add(new ExpirationCallback.Pre(days(0), days(7), TimeValue.timeValueMinutes(10)) {
                                    @Override
                                    public void on(License license) {
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# License will expire on [{}]. If you have a new license, please update it.\n" +
                                                "# Otherwise, please reach out to your support contact.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# Commercial plugins operate with reduced functionality on license " +
                                                    "expiration:");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.warn("{}", builder.toString());
                                        } else {
                                            logger.warn("{}", general);
                                        }
                                    }
                                }
        );
        expirationCallbacks.add(new ExpirationCallback.Post(days(0), null, TimeValue.timeValueMinutes(10)) {
                                    @Override
                                    public void on(License license) {
                                        // logged when grace period begins
                                        String general = LoggerMessageFormat.format(null, "\n" +
                                                "#\n" +
                                                "# LICENSE EXPIRED ON [{}]. IF YOU HAVE A NEW LICENSE, PLEASE\n" +
                                                "# UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
                                                "# ", DATE_FORMATTER.printer().print(license.expiryDate()));
                                        if (!registeredLicensees.isEmpty()) {
                                            StringBuilder builder = new StringBuilder(general);
                                            builder.append(System.lineSeparator());
                                            builder.append("# COMMERCIAL PLUGINS OPERATING WITH REDUCED FUNCTIONALITY");
                                            for (InternalLicensee licensee : registeredLicensees) {
                                                if (licensee.expirationMessages().length > 0) {
                                                    builder.append(System.lineSeparator());
                                                    builder.append("# - ");
                                                    builder.append(licensee.id());
                                                    for (String message : licensee.expirationMessages()) {
                                                        builder.append(System.lineSeparator());
                                                        builder.append("#  - ");
                                                        builder.append(message);
                                                    }
                                                }
                                            }
                                            logger.warn("{}", builder.toString());
                                        } else {
                                            logger.warn("{}", general);
                                        }
                                    }
                                }
        );
    }

    /**
     * Registers new license in the cluster
     * Master only operation. Installs a new license on the master provided it is VALID
     */
    public void registerLicense(final PutLicenseRequest request, final ActionListener<LicensesUpdateResponse> listener) {
        final License newLicense = request.license();
        final long now = System.currentTimeMillis();
        if (!verifyLicense(newLicense) || newLicense.issueDate() > now) {
            listener.onResponse(new LicensesUpdateResponse(true, LicensesStatus.INVALID));
        } else if (newLicense.expiryDate() < now) {
{'fraction_non_alphanumeric': 0.07506775067750678, 'fraction_numerical': 0.0016937669376693768, 'mean_word_length': 1.5147797938495613, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 318, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '265456', 'n_tokens_mistral': 5590, 'n_tokens_neox': 5500, 'n_words': 1516}