/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.network; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import fr.gael.dhus.service.NetworkUsageService; import fr.gael.dhus.database.object.User; import fr.gael.dhus.spring.context.ApplicationContextProvider; class ChannelQueue extends AbstractChannel implements Iterable<Channel> { /** * The collection children channels. The collection may be empty but * never null. */ private final Collection<Channel> subChannels; /** * Channel classifier */ private ChannelClassifier classifier = null; /** * Build a default channel queue. */ ChannelQueue(final String name) throws IllegalArgumentException { super(name); this.subChannels = Collections.synchronizedList (new LinkedList<Channel> ()); new Thread(new RoundRobin(), "" + name + " - Round-Robin Thread").start(); } @Override public Iterator<Channel> iterator() { return this.subChannels.iterator(); } /** * Add a channel to the queue. * * @param channel is the channel to be added. This parameter shall not * be null. * @throws IllegalArgumentException if the channel parameter is null. */ public void addChannel(Channel channel) throws IllegalArgumentException { // Check parameter if (channel == null) { throw new IllegalArgumentException("Cannot add null channel."); } // Add channel to the sub-channels this.subChannels.add(channel); // Register this channel as parent of the added one channel.setParent(this); } // End addChannel(Channel) public boolean removeChannel(Channel channel) { return this.subChannels.remove(channel); } ChannelClassifier getClassifier() { return classifier; } void setClassifier(ChannelClassifier classifier) { this.classifier = classifier; // TODO Shall reject flows that no longer comply with the classifier } @Override public synchronized Channel getChannel(ConnectionParameters parameters) throws IllegalArgumentException, RegulationException { // Check parameter if (parameters == null) { throw new IllegalArgumentException( "Cannot build a channel flow from a null " + "set of parameters."); } // Reject request if the parameters do not comply with the classifier if ((this.classifier != null) && (!this.classifier.complyWith(parameters))) { return null; } // Performs quotas control checkQuotas (parameters, this.getUserQuotas()); // Try to get the flow from sub-channels for (Channel sub_channel : this) { Channel channel = sub_channel.getChannel(parameters); if (channel != null) { return channel; } } // Compute flow name String flow_name = "--anonymous--"; if ((parameters.getUser() != null) && (parameters.getUser().getUsername() != null)) { flow_name = parameters.getUser().getUsername(); } flow_name += "@" + this.getName() + "{" + parameters.getDirection() .getLabel() + "}"; // Otherwise creates the channel Channel channel = new ChannelFlow(flow_name, parameters); this.addChannel(channel); return channel; } private void checkQuotas (ConnectionParameters parameters, UserQuotas quotas) throws RegulationException { // Raise an exception if connection count exceeded // Connection count is computed from this channel and includes all // sub-channels if ((quotas != null) && (quotas.getMaxConcurrent() != null)) { int max_concurrent = quotas.getMaxConcurrent(); int connection_count = this.countUserChannels(parameters.getUser()); if (connection_count >= max_concurrent) { // Get user name String user_name = "--anonymous--"; if (parameters.getUser() != null) { user_name = parameters.getUser().getUsername(); } // Throw regulation exception throw new RegulationException("Maximum number of " + max_concurrent + " concurrent flows achieved by the user \"" + user_name + "\""); } } NetworkUsageService network_service = ApplicationContextProvider. getBean (NetworkUsageService.class); // Raise an exception if maxCount reached if ((quotas !=null) && (quotas.getMaxCount () != null) && (parameters.getUser() != null)) { User user = parameters.getUser(); long period = quotas.getMaxCountPeriod (); int total_counted = network_service.countDownloadsByUserSince (user, period); // Checks the retrieved count if (total_counted>=quotas.getMaxCount ()) { // Throw regulation exception throw new RegulationException("Maximum number of download (" + total_counted + ") exceeded by the user \"" + user.getUsername () + "\""); } } // Raise an exception if maxCumulativeSize reached if ((quotas !=null) && (quotas.getMaxCumulativeSize () != null) && (parameters.getUser() != null)) { User user = parameters.getUser(); long period = quotas.getMaxCountPeriod (); long expected_size = parameters.getStreamSize (); long total_sized = network_service.getDownloadedSizeByUserSince (user, period) + expected_size; // Checks the retrieved count if (total_sized>=quotas.getMaxCumulativeSize ()) { // Throw regulation exception throw new RegulationException("Maximum size of download (" + total_sized + ") exceeded by the user \"" + user.getUsername () + "\""); } } } @Override public String toString() { return toString(0); } public String toString(final int indent_level) { String indent = " "; String left_margin = ""; for (int iindent = 0; iindent < indent_level; iindent++) { left_margin += indent; } String message = left_margin + "Channel Queue (" + ((this.getName() != null) ? this.getName() : "--anonymous--") + " x " + this.getWeight() + ") - Classifier: " + ((this.classifier != null) ? this.classifier : "None"); UserQuotas user_quotas = this.getUserQuotas(); message += "\n" + left_margin + indent + (user_quotas != null ? user_quotas.toString() : "User Quotas: none"); for (Channel sub_channel : this) { if (sub_channel instanceof ChannelQueue) { message += "\n" + ((ChannelQueue) sub_channel).toString(indent_level + 1); } else { message += "\n" + left_margin + indent + sub_channel.toString(); } } return message; } @Override public int countUserChannels(User user) { // Prepare counter int counter = 0; // Loop among sub-channels for (Channel channel : this) { counter += channel.countUserChannels(user); } // Return counter return counter; } private class RoundRobin implements Runnable { @Override public void run() { List<Channel> non_empty_queues = new ArrayList<Channel>(); // Infinite Round-Robin loop while (true) { int weights_sum = 0; int quantum = 0; int permits = 0; // Infinite loop broken when a sub-channel has something to send while (true) { non_empty_queues.clear(); weights_sum = 0; quantum = 0; permits = 0; for (Object channel_object : subChannels.toArray()) { Channel channel = (Channel)channel_object; if (channel == null) continue; int awaiting_permits = channel.getAwaitingPermits(); if (awaiting_permits > 0) { non_empty_queues.add(channel); weights_sum += channel.getWeight(); quantum = Math.max(awaiting_permits, quantum); permits += awaiting_permits; } } if (non_empty_queues.size() > 0) { break; } try { waitPoke(); } catch (InterruptedException e) { e.printStackTrace(); } } try { acquire(permits); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (RegulationException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } for (Channel channel : non_empty_queues) { channel.release(permits * channel.getWeight() / weights_sum); } } } } } // End ChannelQueue class