]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/server/server.ts
Rename Pod -> Server
[github/Chocobozzz/PeerTube.git] / server / models / server / server.ts
1 import { map } from 'lodash'
2 import * as Sequelize from 'sequelize'
3
4 import { FRIEND_SCORE, SERVERS_SCORE } from '../../initializers'
5 import { logger, isHostValid } from '../../helpers'
6
7 import { addMethodsToModel, getSort } from '../utils'
8 import {
9 ServerInstance,
10 ServerAttributes,
11
12 ServerMethods
13 } from './server-interface'
14
15 let Server: Sequelize.Model<ServerInstance, ServerAttributes>
16 let countAll: ServerMethods.CountAll
17 let incrementScores: ServerMethods.IncrementScores
18 let list: ServerMethods.List
19 let listForApi: ServerMethods.ListForApi
20 let listAllIds: ServerMethods.ListAllIds
21 let listRandomServerIdsWithRequest: ServerMethods.ListRandomServerIdsWithRequest
22 let listBadServers: ServerMethods.ListBadServers
23 let load: ServerMethods.Load
24 let loadByHost: ServerMethods.LoadByHost
25 let removeAll: ServerMethods.RemoveAll
26 let updateServersScore: ServerMethods.UpdateServersScore
27
28 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
29 Server = sequelize.define<ServerInstance, ServerAttributes>('Server',
30 {
31 host: {
32 type: DataTypes.STRING,
33 allowNull: false,
34 validate: {
35 isHost: value => {
36 const res = isHostValid(value)
37 if (res === false) throw new Error('Host not valid.')
38 }
39 }
40 },
41 score: {
42 type: DataTypes.INTEGER,
43 defaultValue: FRIEND_SCORE.BASE,
44 allowNull: false,
45 validate: {
46 isInt: true,
47 max: FRIEND_SCORE.MAX
48 }
49 }
50 },
51 {
52 indexes: [
53 {
54 fields: [ 'host' ],
55 unique: true
56 },
57 {
58 fields: [ 'score' ]
59 }
60 ]
61 }
62 )
63
64 const classMethods = [
65 countAll,
66 incrementScores,
67 list,
68 listForApi,
69 listAllIds,
70 listRandomServerIdsWithRequest,
71 listBadServers,
72 load,
73 loadByHost,
74 updateServersScore,
75 removeAll
76 ]
77 addMethodsToModel(Server, classMethods)
78
79 return Server
80 }
81
82 // ------------------------------ Statics ------------------------------
83
84 countAll = function () {
85 return Server.count()
86 }
87
88 incrementScores = function (ids: number[], value: number) {
89 const update = {
90 score: Sequelize.literal('score +' + value)
91 }
92
93 const options = {
94 where: {
95 id: {
96 [Sequelize.Op.in]: ids
97 }
98 },
99 // In this case score is a literal and not an integer so we do not validate it
100 validate: false
101 }
102
103 return Server.update(update, options)
104 }
105
106 list = function () {
107 return Server.findAll()
108 }
109
110 listForApi = function (start: number, count: number, sort: string) {
111 const query = {
112 offset: start,
113 limit: count,
114 order: [ getSort(sort) ]
115 }
116
117 return Server.findAndCountAll(query).then(({ rows, count }) => {
118 return {
119 data: rows,
120 total: count
121 }
122 })
123 }
124
125 listAllIds = function (transaction: Sequelize.Transaction) {
126 const query = {
127 attributes: [ 'id' ],
128 transaction
129 }
130
131 return Server.findAll(query).then(servers => {
132 return map(servers, 'id')
133 })
134 }
135
136 listRandomServerIdsWithRequest = function (limit: number, tableWithServers: string, tableWithServersJoins: string) {
137 return Server.count().then(count => {
138 // Optimization...
139 if (count === 0) return []
140
141 let start = Math.floor(Math.random() * count) - limit
142 if (start < 0) start = 0
143
144 const subQuery = `(SELECT DISTINCT "${tableWithServers}"."serverId" FROM "${tableWithServers}" ${tableWithServersJoins})`
145 const query = {
146 attributes: [ 'id' ],
147 order: [
148 [ 'id', 'ASC' ]
149 ],
150 offset: start,
151 limit: limit,
152 where: {
153 id: {
154 [Sequelize.Op.in]: Sequelize.literal(subQuery)
155 }
156 }
157 }
158
159 return Server.findAll(query).then(servers => {
160 return map(servers, 'id')
161 })
162 })
163 }
164
165 listBadServers = function () {
166 const query = {
167 where: {
168 score: {
169 [Sequelize.Op.lte]: 0
170 }
171 }
172 }
173
174 return Server.findAll(query)
175 }
176
177 load = function (id: number) {
178 return Server.findById(id)
179 }
180
181 loadByHost = function (host: string) {
182 const query = {
183 where: {
184 host: host
185 }
186 }
187
188 return Server.findOne(query)
189 }
190
191 removeAll = function () {
192 return Server.destroy()
193 }
194
195 updateServersScore = function (goodServers: number[], badServers: number[]) {
196 logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length)
197
198 if (goodServers.length !== 0) {
199 incrementScores(goodServers, SERVERS_SCORE.BONUS).catch(err => {
200 logger.error('Cannot increment scores of good servers.', err)
201 })
202 }
203
204 if (badServers.length !== 0) {
205 incrementScores(badServers, SERVERS_SCORE.PENALTY)
206 .then(() => removeBadServers())
207 .catch(err => {
208 if (err) logger.error('Cannot decrement scores of bad servers.', err)
209 })
210 }
211 }
212
213 // ---------------------------------------------------------------------------
214
215 // Remove servers with a score of 0 (too many requests where they were unreachable)
216 async function removeBadServers () {
217 try {
218 const servers = await listBadServers()
219
220 const serversRemovePromises = servers.map(server => server.destroy())
221 await Promise.all(serversRemovePromises)
222
223 const numberOfServersRemoved = servers.length
224
225 if (numberOfServersRemoved) {
226 logger.info('Removed %d servers.', numberOfServersRemoved)
227 } else {
228 logger.info('No need to remove bad servers.')
229 }
230 } catch (err) {
231 logger.error('Cannot remove bad servers.', err)
232 }
233 }