]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/friends.js
Server: move remote routes in their own directory
[github/Chocobozzz/PeerTube.git] / server / lib / friends.js
CommitLineData
9f10b292
C
1'use strict'
2
1a42c9e2
C
3const each = require('async/each')
4const eachLimit = require('async/eachLimit')
5const eachSeries = require('async/eachSeries')
f0f5567b
C
6const fs = require('fs')
7const request = require('request')
1a42c9e2 8const waterfall = require('async/waterfall')
f0f5567b
C
9
10const constants = require('../initializers/constants')
feb4bdfd 11const db = require('../initializers/database')
f0f5567b 12const logger = require('../helpers/logger')
f0f5567b 13const requests = require('../helpers/requests')
f0f5567b 14
a3ee6fa2 15const friends = {
c4403b29 16 addVideoToFriends,
7b1f49de 17 updateVideoToFriends,
c4403b29
C
18 hasFriends,
19 getMyCertificate,
20 makeFriends,
21 quitFriends,
22 removeVideoToFriends,
23 sendOwnedVideosToPod
9f10b292
C
24}
25
26function addVideoToFriends (video) {
4b08096b 27 createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, video)
9f10b292
C
28}
29
7b1f49de
C
30function updateVideoToFriends (video) {
31 createRequest('update', constants.REQUEST_ENDPOINTS.VIDEOS, video)
32}
33
9f10b292 34function hasFriends (callback) {
feb4bdfd 35 db.Pod.countAll(function (err, count) {
9f10b292
C
36 if (err) return callback(err)
37
bc503c2a
C
38 const hasFriends = (count !== 0)
39 callback(null, hasFriends)
9f10b292
C
40 })
41}
42
cbe2f7c3 43function getMyCertificate (callback) {
e861452f 44 fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback)
cbe2f7c3
C
45}
46
49abbbbe 47function makeFriends (hosts, callback) {
bc503c2a 48 const podsScore = {}
9f10b292
C
49
50 logger.info('Make friends!')
cbe2f7c3 51 getMyCertificate(function (err, cert) {
9f10b292
C
52 if (err) {
53 logger.error('Cannot read public cert.')
54 return callback(err)
55 }
c173e565 56
49abbbbe
C
57 eachSeries(hosts, function (host, callbackEach) {
58 computeForeignPodsList(host, podsScore, callbackEach)
89d1d8ba 59 }, function (err) {
c173e565
C
60 if (err) return callback(err)
61
bc503c2a 62 logger.debug('Pods scores computed.', { podsScore: podsScore })
49abbbbe 63 const podsList = computeWinningPods(hosts, podsScore)
bc503c2a 64 logger.debug('Pods that we keep.', { podsToKeep: podsList })
9f10b292 65
bc503c2a 66 makeRequestsToWinningPods(cert, podsList, callback)
c173e565 67 })
9f10b292 68 })
9f10b292
C
69}
70
71function quitFriends (callback) {
72 // Stop pool requests
feb4bdfd 73 db.Request.deactivate()
9f10b292 74
1a42c9e2 75 waterfall([
7920c273
C
76 function flushRequests (callbackAsync) {
77 db.Request.flush(callbackAsync)
78 },
79
e7ea2817 80 function getPodsList (callbackAsync) {
feb4bdfd 81 return db.Pod.list(callbackAsync)
e7ea2817
C
82 },
83
84 function announceIQuitMyFriends (pods, callbackAsync) {
528a9efa 85 const requestParams = {
e7ea2817
C
86 method: 'POST',
87 path: '/api/' + constants.API_VERSION + '/pods/remove',
528a9efa 88 sign: true
c173e565
C
89 }
90
e7ea2817 91 // Announce we quit them
528a9efa
C
92 // We don't care if the request fails
93 // The other pod will exclude us automatically after a while
1a42c9e2 94 eachLimit(pods, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
528a9efa
C
95 requestParams.toPod = pod
96 requests.makeSecureRequest(requestParams, callbackEach)
97 }, function (err) {
98 if (err) {
99 logger.error('Some errors while quitting friends.', { err: err })
100 // Don't stop the process
101 }
102
80a6c9e7 103 return callbackAsync(null, pods)
e7ea2817
C
104 })
105 },
c173e565 106
80a6c9e7
C
107 function removePodsFromDB (pods, callbackAsync) {
108 each(pods, function (pod, callbackEach) {
feb4bdfd 109 pod.destroy().asCallback(callbackEach)
aaf61f38 110 }, callbackAsync)
e7ea2817
C
111 }
112 ], function (err) {
113 // Don't forget to re activate the scheduler, even if there was an error
feb4bdfd 114 db.Request.activate()
e7ea2817
C
115
116 if (err) return callback(err)
117
118 logger.info('Removed all remote videos.')
119 return callback(null)
9f10b292
C
120 })
121}
c173e565 122
00057e85 123function removeVideoToFriends (videoParams) {
4b08096b 124 createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams)
528a9efa
C
125}
126
127function sendOwnedVideosToPod (podId) {
7920c273 128 db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) {
528a9efa
C
129 if (err) {
130 logger.error('Cannot get the list of videos we own.')
131 return
132 }
133
134 videosList.forEach(function (video) {
7b1f49de 135 video.toAddRemoteJSON(function (err, remoteVideo) {
528a9efa
C
136 if (err) {
137 logger.error('Cannot convert video to remote.', { error: err })
138 // Don't break the process
139 return
140 }
141
4b08096b 142 createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, remoteVideo, [ podId ])
528a9efa
C
143 })
144 })
145 })
9f10b292 146}
c173e565 147
9f10b292 148// ---------------------------------------------------------------------------
c173e565 149
a3ee6fa2 150module.exports = friends
c173e565 151
9f10b292 152// ---------------------------------------------------------------------------
c173e565 153
49abbbbe
C
154function computeForeignPodsList (host, podsScore, callback) {
155 getForeignPodsList(host, function (err, foreignPodsList) {
89d1d8ba 156 if (err) return callback(err)
528a9efa
C
157
158 if (!foreignPodsList) foreignPodsList = []
159
160 // Let's give 1 point to the pod we ask the friends list
49abbbbe 161 foreignPodsList.push({ host })
89d1d8ba 162
bc503c2a 163 foreignPodsList.forEach(function (foreignPod) {
49abbbbe 164 const foreignPodHost = foreignPod.host
89d1d8ba 165
49abbbbe
C
166 if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
167 else podsScore[foreignPodHost] = 1
89d1d8ba 168 })
cbe2f7c3
C
169
170 callback()
89d1d8ba
C
171 })
172}
173
49abbbbe 174function computeWinningPods (hosts, podsScore) {
89d1d8ba
C
175 // Build the list of pods to add
176 // Only add a pod if it exists in more than a half base pods
bc503c2a 177 const podsList = []
49abbbbe
C
178 const baseScore = hosts.length / 2
179 Object.keys(podsScore).forEach(function (podHost) {
2c49ca42 180 // If the pod is not me and with a good score we add it
49abbbbe
C
181 if (isMe(podHost) === false && podsScore[podHost] > baseScore) {
182 podsList.push({ host: podHost })
2c49ca42 183 }
89d1d8ba 184 })
e7ea2817 185
bc503c2a 186 return podsList
89d1d8ba
C
187}
188
49abbbbe 189function getForeignPodsList (host, callback) {
f0f5567b 190 const path = '/api/' + constants.API_VERSION + '/pods'
c173e565 191
49abbbbe 192 request.get(constants.REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) {
9f10b292 193 if (err) return callback(err)
8425cb89 194
39f87cb2
C
195 try {
196 const json = JSON.parse(body)
197 return callback(null, json)
198 } catch (err) {
199 return callback(err)
200 }
9f10b292
C
201 })
202}
89d1d8ba 203
bc503c2a 204function makeRequestsToWinningPods (cert, podsList, callback) {
89d1d8ba 205 // Stop pool requests
feb4bdfd 206 db.Request.deactivate()
89d1d8ba 207 // Flush pool requests
feb4bdfd 208 db.Request.forceSend()
89d1d8ba 209
1a42c9e2 210 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
528a9efa 211 const params = {
49abbbbe 212 url: constants.REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + constants.API_VERSION + '/pods/',
528a9efa
C
213 method: 'POST',
214 json: {
49abbbbe 215 host: constants.CONFIG.WEBSERVER.HOST,
528a9efa
C
216 publicKey: cert
217 }
89d1d8ba
C
218 }
219
528a9efa
C
220 requests.makeRetryRequest(params, function (err, res, body) {
221 if (err) {
49abbbbe 222 logger.error('Error with adding %s pod.', pod.host, { error: err })
528a9efa
C
223 // Don't break the process
224 return callbackEach()
225 }
89d1d8ba 226
528a9efa 227 if (res.statusCode === 200) {
feb4bdfd
C
228 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert })
229 podObj.save().asCallback(function (err, podCreated) {
a3ee6fa2 230 if (err) {
49abbbbe 231 logger.error('Cannot add friend %s pod.', pod.host, { error: err })
a3ee6fa2
C
232 return callbackEach()
233 }
89d1d8ba 234
528a9efa 235 // Add our videos to the request scheduler
7920c273 236 sendOwnedVideosToPod(podCreated.id)
89d1d8ba 237
528a9efa
C
238 return callbackEach()
239 })
240 } else {
49abbbbe 241 logger.error('Status not 200 for %s pod.', pod.host)
528a9efa 242 return callbackEach()
89d1d8ba 243 }
528a9efa
C
244 })
245 }, function endRequests () {
246 // Final callback, we've ended all the requests
247 // Now we made new friends, we can re activate the pool of requests
feb4bdfd 248 db.Request.activate()
528a9efa
C
249
250 logger.debug('makeRequestsToWinningPods finished.')
251 return callback()
89d1d8ba
C
252 })
253}
00057e85 254
feb4bdfd 255// Wrapper that populate "to" argument with all our friends if it is not specified
4b08096b 256function createRequest (type, endpoint, data, to) {
feb4bdfd
C
257 if (to) return _createRequest(type, endpoint, data, to)
258
259 // If the "to" pods is not specified, we send the request to all our friends
260 db.Pod.listAllIds(function (err, podIds) {
261 if (err) {
262 logger.error('Cannot get pod ids', { error: err })
263 return
264 }
265
266 return _createRequest(type, endpoint, data, podIds)
267 })
268}
269
270function _createRequest (type, endpoint, data, to) {
271 const pods = []
272
273 // If there are no destination pods abort
274 if (to.length === 0) return
275
276 to.forEach(function (toPod) {
277 pods.push(db.Pod.build({ id: toPod }))
278 })
279
280 const createQuery = {
4b08096b 281 endpoint,
00057e85
C
282 request: {
283 type: type,
284 data: data
285 }
00057e85
C
286 }
287
feb4bdfd
C
288 // We run in transaction to keep coherency between Request and RequestToPod tables
289 db.sequelize.transaction(function (t) {
290 const dbRequestOptions = {
291 transaction: t
292 }
293
294 return db.Request.create(createQuery, dbRequestOptions).then(function (request) {
295 return request.setPods(pods, dbRequestOptions)
296 })
297 }).asCallback(function (err) {
298 if (err) logger.error('Error in createRequest transaction.', { error: err })
00057e85
C
299 })
300}
2c49ca42 301
49abbbbe
C
302function isMe (host) {
303 return host === constants.CONFIG.WEBSERVER.HOST
2c49ca42 304}