All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kurento.tree.server.treemanager.LessLoadedOnlySource2TM Maven / Gradle / Ivy

/*
 * (C) Copyright 2016 Kurento (http://kurento.org/)
 *
 * 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 org.kurento.tree.server.treemanager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.kurento.client.IceCandidate;
import org.kurento.commons.exception.KurentoException;
import org.kurento.jsonrpc.Session;
import org.kurento.tree.client.TreeEndpoint;
import org.kurento.tree.client.TreeException;
import org.kurento.tree.server.app.TreeElementSession;
import org.kurento.tree.server.kms.Element;
import org.kurento.tree.server.kms.Kms;
import org.kurento.tree.server.kms.Pipeline;
import org.kurento.tree.server.kms.Plumber;
import org.kurento.tree.server.kms.WebRtc;
import org.kurento.tree.server.kmsmanager.KmsLoad;
import org.kurento.tree.server.kmsmanager.KmsManager;
import org.kurento.tree.server.kmsmanager.ReserveKmsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This TreeManager has the following characteristics:
 * 
    *
  • It allows N trees
  • *
  • Creates WebRtcEndpoint for sinks (viewers) in any node (including KMSs with sources)
  • *
  • Create source webrtc in less loaded node, but webrtc for sinks in the same kms than source * while space available
  • *
  • It considers new KMSs after start.
  • *
  • It is possible to connect more than 2 KMSs with plumbers
  • *
* * @author [email protected] */ public class LessLoadedOnlySource2TM extends AbstractNTreeTM implements KmsListener { private static final Logger log = LoggerFactory.getLogger(LessLoadedOnlySource2TM.class); public class LessLoadedTreeInfo extends TreeInfo { // Number of webrtcs reserved in each KMS when the source is connected // to it. This allows create new pipelines in another machines to be // connected to source pipeline. private static final int NUM_WEBRTC_FOR_TREE = 2; private String treeId; private Kms sourceKms; private Pipeline sourcePipeline; private WebRtc source; private List sourcePlumbers = new ArrayList<>(); private List leafPipelines = new ArrayList<>(); private List leafPlumbers = new ArrayList<>(); private Map sinks = new ConcurrentHashMap<>(); private Map ownPipelineByKms = new ConcurrentHashMap<>(); private Map webRtcsById = new ConcurrentHashMap<>(); public LessLoadedTreeInfo(String treeId) { this.treeId = treeId; if (kmsManager.getKmss().isEmpty()) { throw new KurentoException("LessLoadedNElasticTM cannot be used without initial kmss"); } } @Override public void release() { freeWebRtc(sourceKms, 1 + NUM_WEBRTC_FOR_TREE); for (Plumber p : sourcePlumbers) { if (p.getLinkedTo() != null) { throw new TreeException("Exception removing TreeSource. Some plumbers are connected"); } else { p.release(); } } source.release(); for (WebRtc webRtc : sinks.values()) { webRtc.release(); } } @Override public synchronized String setTreeSource(Session session, String offerSdp) { log.info("setTreeSource treeId={}", treeId); if (source != null) { removeTreeSource(); } if (sourcePipeline == null) { for (KmsLoad kmsLoad : kmsManager.getKmssSortedByLoad()) { sourceKms = kmsLoad.getKms(); if (reserveWebRtc(sourceKms, 1 + NUM_WEBRTC_FOR_TREE)) { break; } } sourcePipeline = sourceKms.createPipeline(); sourcePipeline.setLabel(treeId); ownPipelineByKms.put(sourceKms, sourcePipeline); } source = sourcePipeline.createWebRtc(new TreeElementSession(session, treeId, null)); source.setLabel(treeId + "_source"); for (int i = 0; i < NUM_WEBRTC_FOR_TREE; i++) { Plumber sourcePipelinePlumber = sourcePipeline.createPlumber(); sourcePipelinePlumber.setLabel(treeId + "_plumber" + i); source.connect(sourcePipelinePlumber); this.sourcePlumbers.add(sourcePipelinePlumber); } String sdpAnswer = source.processSdpOffer(offerSdp); source.gatherCandidates(); System.out.println("Holes: " + remainingHoles); return sdpAnswer; } @Override public void removeTreeSource() { if (source != null) { if (source.getSinks().size() > NUM_WEBRTC_FOR_TREE) { throw new TreeException( "Removing or changing TreeSource with sinks is not currently supported"); } freeWebRtc(sourceKms, 1 + NUM_WEBRTC_FOR_TREE); source.release(); source = null; } } @Override public TreeEndpoint addTreeSink(Session session, String sdpOffer) { log.info("addTreeSink treeId={}", treeId); List kmssSortedByLoad = kmsManager.getKmssSortedByLoad(); Kms selectedKms = null; Pipeline pipeline = null; if (reserveWebRtc(sourceKms, 1)) { selectedKms = sourceKms; pipeline = sourcePipeline; } else { Plumber freeSourcePlumber = null; for (Plumber p : this.sourcePlumbers) { if (p.getLinkedTo() == null) { freeSourcePlumber = p; break; } } selectedKms = selectKmsForSink(kmssSortedByLoad); pipeline = ownPipelineByKms.get(selectedKms); if (pipeline == null) { if (freeSourcePlumber == null) { if (!allowMoreThan2KmsPerTree) { throw new TreeException("Source KMS doesn't allow new webRtc and hasn't free " + "plumbers to connect to another KMS"); } else { freeSourcePlumber = selectKmsForSinkWithFreePlumber(kmssSortedByLoad); if (freeSourcePlumber == null) { throw new TreeException( "There are no plumbers to connect a new " + "pipeline for tree. This is a bug"); } } } pipeline = selectedKms.createPipeline(); pipeline.setLabel(treeId); ownPipelineByKms.put(selectedKms, pipeline); leafPipelines.add(pipeline); Plumber sinkPipelinePlumber = pipeline.createPlumber(); sinkPipelinePlumber.setLabel(treeId + "_sinkPlumber_" + pipeline.getKms().getLabel()); freeSourcePlumber.link(sinkPipelinePlumber); for (int i = 0; i < NUM_WEBRTC_FOR_TREE; i++) { Plumber leafOutputPlumber = pipeline.createPlumber(); leafOutputPlumber .setLabel(treeId + "_plumber" + i + "_" + pipeline.getKms().getLabel()); sinkPipelinePlumber.connect(leafOutputPlumber); this.leafPlumbers.add(sinkPipelinePlumber); } } } String id = UUID.randomUUID().toString(); WebRtc webRtc = pipeline.createWebRtc(new TreeElementSession(session, treeId, id)); webRtc.setLabel(treeId + "_sink_" + id.substring(0, id.indexOf('-'))); if (pipeline != sourcePipeline) { pipeline.getPlumbers().get(0).connect(webRtc); } else { source.connect(webRtc); } String sdpAnswer = webRtc.processSdpOffer(sdpOffer); webRtc.gatherCandidates(); webRtcsById.put(id, webRtc); System.out.println("Holes: " + remainingHoles); return new TreeEndpoint(sdpAnswer, id); } private Plumber selectKmsForSinkWithFreePlumber(List kmssSortedByLoad) { for (KmsLoad kmsLoad : kmssSortedByLoad) { Kms kms = kmsLoad.getKms(); Pipeline pipeline = ownPipelineByKms.get(kms); if (pipeline != null) { for (Plumber p : pipeline.getPlumbers()) { if (p.getLinkedTo() == null) { return p; } } } } throw new TreeException("There are no pipeline with free plumber"); } private Kms selectKmsForSink(List kmssSortedByLoad) { Kms selectedKms = null; for (KmsLoad kmsLoad : kmssSortedByLoad) { Kms kms = kmsLoad.getKms(); int numWebRtcs = 1; boolean pipelineTreeInKms = ownPipelineByKms.get(kms) != null; if (!pipelineTreeInKms) { numWebRtcs += 1 + NUM_WEBRTC_FOR_TREE; } if (reserveWebRtc(kms, numWebRtcs)) { selectedKms = kmsLoad.getKms(); break; } } if (selectedKms == null) { log.warn("remainingHoles: " + remainingHoles); throw new TreeException("No kms allows more WebRtcEndpoints"); } return selectedKms; } @Override public void removeTreeSink(String sinkId) { log.info("removeTreeSink treeId={}", treeId); WebRtc webRtc = webRtcsById.get(sinkId); Element elem = webRtc.getSource(); freeWebRtc(webRtc.getPipeline().getKms(), 1); webRtc.release(); if (elem instanceof Plumber) { removePlumberIfNotConnected((Plumber) elem); } System.out.println("Holes: " + remainingHoles); } private void removePlumberIfNotConnected(Plumber plumber) { if (plumber.getSinks().size() == NUM_WEBRTC_FOR_TREE) { boolean remove = true; for (Element e : plumber.getSinks()) { if (((Plumber) e).getLinkedTo() != null) { remove = false; break; } } if (remove) { Pipeline pipeline = plumber.getPipeline(); ownPipelineByKms.remove(pipeline.getKms()); freeWebRtc(pipeline.getKms(), 1 + NUM_WEBRTC_FOR_TREE); pipeline.release(); Element elem = plumber.getLinkedTo().getSource(); if (elem instanceof Plumber) { removePlumberIfNotConnected((Plumber) elem); } } } } @Override public void addSinkIceCandidate(String sinkId, IceCandidate iceCandidate) { WebRtc webRtc = webRtcsById.get(sinkId); if (webRtc != null) { webRtc.addIceCandidate(iceCandidate); } else { log.warn("Reciving iceCandidate, but sink {} has no WebRtc", sinkId); } } @Override public void addTreeIceCandidate(IceCandidate iceCandidate) { source.addIceCandidate(iceCandidate); } } private KmsManager kmsManager; private int maxWebRtcsPerKMS; private ConcurrentMap remainingHoles = new ConcurrentHashMap<>(); private boolean allowMoreThan2KmsPerTree = true; public LessLoadedOnlySource2TM(KmsManager kmsManager, int maxWebRtcsPerKMS) { this.kmsManager = kmsManager; this.maxWebRtcsPerKMS = maxWebRtcsPerKMS; } public LessLoadedOnlySource2TM(KmsManager kmsManager) { this.kmsManager = kmsManager; this.maxWebRtcsPerKMS = ReserveKmsManager.KMS_MAX_WEBRTC; } @Override public KmsManager getKmsManager() { return kmsManager; } @Override protected TreeInfo createTreeInfo(String treeId) { return new LessLoadedTreeInfo(treeId); } private boolean reserveWebRtc(Kms kms, int numWebRtc) { AtomicInteger space = remainingHoles.computeIfAbsent(kms, k -> new AtomicInteger(maxWebRtcsPerKMS)); checkLoad(kms, space); int holes = space.addAndGet(-numWebRtc); if (holes >= 0) { log.debug("Reserve {} #webRtc {} => {}", kms, numWebRtc, holes); return true; } else { space.addAndGet(numWebRtc); return false; } } private void checkLoad(Kms kms, AtomicInteger space) { int count = countWebRtcsAndPlumbers(kms); if (maxWebRtcsPerKMS - count != space.get()) { throw new TreeException("Incongruent count in kms " + kms.getLabel() + ". There are " + space.get() + " holes but should be " + (maxWebRtcsPerKMS - count)); } } private int countWebRtcsAndPlumbers(Kms kms) { int count = 0; for (Pipeline pipeline : kms.getPipelines()) { count += pipeline.getWebRtcs().size(); count += pipeline.getPlumbers().size(); } return count; } public void freeWebRtc(Kms kms, int numWebRtc) { AtomicInteger space = remainingHoles.get(kms); checkLoad(kms, space); int holes = space.addAndGet(numWebRtc); log.debug("Free {} #webRtc {} => {}", kms, numWebRtc, holes); } @Override public void kmsRemoved(Kms kms) { this.remainingHoles.remove(kms); } @Override public void kmsAdded(Kms kms) { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy