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