Jelajahi Sumber

Merge pull request #7867 from overleaf/jpa-compile-group

[web] pass compileGroup to clsi-lb

GitOrigin-RevId: c15adbff27e702b3e0f29be5b57f7a9520d8d02f
Jakob Ackermann 4 tahun lalu
induk
melakukan
52073a13a9

+ 40 - 22
services/web/app/src/Features/Compile/ClsiCookieManager.js

@@ -12,6 +12,7 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
 let rclient_secondary
 let rclient_secondary
+const { URL, URLSearchParams } = require('url')
 const OError = require('@overleaf/o-error')
 const OError = require('@overleaf/o-error')
 const Settings = require('@overleaf/settings')
 const Settings = require('@overleaf/settings')
 const request = require('request').defaults({ timeout: 30 * 1000 })
 const request = require('request').defaults({ timeout: 30 * 1000 })
@@ -38,7 +39,7 @@ module.exports = function (backendGroup) {
       }
       }
     },
     },
 
 
-    _getServerId(project_id, user_id, callback) {
+    _getServerId(project_id, user_id, compileGroup, callback) {
       if (callback == null) {
       if (callback == null) {
         callback = function () {}
         callback = function () {}
       }
       }
@@ -52,6 +53,7 @@ module.exports = function (backendGroup) {
             return this._populateServerIdViaRequest(
             return this._populateServerIdViaRequest(
               project_id,
               project_id,
               user_id,
               user_id,
+              compileGroup,
               callback
               callback
             )
             )
           } else {
           } else {
@@ -61,12 +63,15 @@ module.exports = function (backendGroup) {
       )
       )
     },
     },
 
 
-    _populateServerIdViaRequest(project_id, user_id, callback) {
+    _populateServerIdViaRequest(project_id, user_id, compileGroup, callback) {
       if (callback == null) {
       if (callback == null) {
         callback = function () {}
         callback = function () {}
       }
       }
-      const url = `${Settings.apis.clsi.url}/project/${project_id}/status`
-      request.post(url, (err, res, body) => {
+      const u = new URL(
+        `${Settings.apis.clsi.url}/project/${project_id}/status`
+      )
+      u.search = new URLSearchParams({ compileGroup }).toString()
+      request.post(u.href, (err, res, body) => {
         if (err != null) {
         if (err != null) {
           OError.tag(err, 'error getting initial server id for project', {
           OError.tag(err, 'error getting initial server id for project', {
             project_id,
             project_id,
@@ -76,6 +81,7 @@ module.exports = function (backendGroup) {
         this.setServerId(
         this.setServerId(
           project_id,
           project_id,
           user_id,
           user_id,
+          compileGroup,
           res,
           res,
           null,
           null,
           function (err, serverId) {
           function (err, serverId) {
@@ -100,11 +106,11 @@ module.exports = function (backendGroup) {
       return cookies != null ? cookies[Settings.clsiCookie.key] : undefined
       return cookies != null ? cookies[Settings.clsiCookie.key] : undefined
     },
     },
 
 
-    checkIsLoadSheddingEvent(clsiserverid) {
+    checkIsLoadSheddingEvent(clsiserverid, compileGroup) {
       request.get(
       request.get(
         {
         {
           url: `${Settings.apis.clsi.url}/instance-state`,
           url: `${Settings.apis.clsi.url}/instance-state`,
-          qs: { clsiserverid },
+          qs: { clsiserverid, compileGroup },
         },
         },
         (err, res, body) => {
         (err, res, body) => {
           if (err) {
           if (err) {
@@ -123,7 +129,14 @@ module.exports = function (backendGroup) {
       )
       )
     },
     },
 
 
-    setServerId(project_id, user_id, response, previous, callback) {
+    setServerId(
+      project_id,
+      user_id,
+      compileGroup,
+      response,
+      previous,
+      callback
+    ) {
       if (callback == null) {
       if (callback == null) {
         callback = function () {}
         callback = function () {}
       }
       }
@@ -143,7 +156,7 @@ module.exports = function (backendGroup) {
         // Initial assignment of a user+project or after clearing cache.
         // Initial assignment of a user+project or after clearing cache.
         Metrics.inc('clsi-lb-assign-initial-backend')
         Metrics.inc('clsi-lb-assign-initial-backend')
       } else {
       } else {
-        this.checkIsLoadSheddingEvent(previous)
+        this.checkIsLoadSheddingEvent(previous, compileGroup)
       }
       }
       if (rclient_secondary != null) {
       if (rclient_secondary != null) {
         this._setServerIdInRedis(
         this._setServerIdInRedis(
@@ -181,27 +194,32 @@ module.exports = function (backendGroup) {
       return rclient.del(this.buildKey(project_id, user_id), callback)
       return rclient.del(this.buildKey(project_id, user_id), callback)
     },
     },
 
 
-    getCookieJar(project_id, user_id, callback) {
+    getCookieJar(project_id, user_id, compileGroup, callback) {
       if (callback == null) {
       if (callback == null) {
         callback = function () {}
         callback = function () {}
       }
       }
       if (!clsiCookiesEnabled) {
       if (!clsiCookiesEnabled) {
         return callback(null, request.jar(), undefined)
         return callback(null, request.jar(), undefined)
       }
       }
-      return this._getServerId(project_id, user_id, (err, serverId) => {
-        if (err != null) {
-          OError.tag(err, 'error getting server id', {
-            project_id,
-          })
-          return callback(err)
+      return this._getServerId(
+        project_id,
+        user_id,
+        compileGroup,
+        (err, serverId) => {
+          if (err != null) {
+            OError.tag(err, 'error getting server id', {
+              project_id,
+            })
+            return callback(err)
+          }
+          const serverCookie = request.cookie(
+            `${Settings.clsiCookie.key}=${serverId}`
+          )
+          const jar = request.jar()
+          jar.setCookie(serverCookie, Settings.apis.clsi.url)
+          return callback(null, jar, serverId)
         }
         }
-        const serverCookie = request.cookie(
-          `${Settings.clsiCookie.key}=${serverId}`
-        )
-        const jar = request.jar()
-        jar.setCookie(serverCookie, Settings.apis.clsi.url)
-        return callback(null, jar, serverId)
-      })
+      )
     },
     },
   }
   }
 }
 }

+ 51 - 26
services/web/app/src/Features/Compile/ClsiManager.js

@@ -4,7 +4,7 @@ const request = require('request')
 const ProjectGetter = require('../Project/ProjectGetter')
 const ProjectGetter = require('../Project/ProjectGetter')
 const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
 const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
 const logger = require('@overleaf/logger')
 const logger = require('@overleaf/logger')
-const { URL } = require('url')
+const { URL, URLSearchParams } = require('url')
 const OError = require('@overleaf/o-error')
 const OError = require('@overleaf/o-error')
 
 
 const ClsiCookieManager = require('./ClsiCookieManager')(
 const ClsiCookieManager = require('./ClsiCookieManager')(
@@ -122,8 +122,9 @@ const ClsiManager = {
     if (options == null) {
     if (options == null) {
       options = {}
       options = {}
     }
     }
+    const { compileGroup } = options
     const compilerUrl = this._getCompilerUrl(
     const compilerUrl = this._getCompilerUrl(
-      options.compileGroup,
+      compileGroup,
       projectId,
       projectId,
       userId,
       userId,
       'compile/stop'
       'compile/stop'
@@ -132,18 +133,15 @@ const ClsiManager = {
       url: compilerUrl,
       url: compilerUrl,
       method: 'POST',
       method: 'POST',
     }
     }
-    ClsiManager._makeRequest(projectId, userId, opts, callback)
+    ClsiManager._makeRequest(projectId, userId, compileGroup, opts, callback)
   },
   },
 
 
   deleteAuxFiles(projectId, userId, options, clsiserverid, callback) {
   deleteAuxFiles(projectId, userId, options, clsiserverid, callback) {
     if (options == null) {
     if (options == null) {
       options = {}
       options = {}
     }
     }
-    const compilerUrl = this._getCompilerUrl(
-      options.compileGroup,
-      projectId,
-      userId
-    )
+    const { compileGroup } = options
+    const compilerUrl = this._getCompilerUrl(compileGroup, projectId, userId)
     const opts = {
     const opts = {
       url: compilerUrl,
       url: compilerUrl,
       method: 'DELETE',
       method: 'DELETE',
@@ -151,6 +149,7 @@ const ClsiManager = {
     ClsiManager._makeRequestWithClsiServerId(
     ClsiManager._makeRequestWithClsiServerId(
       projectId,
       projectId,
       userId,
       userId,
+      compileGroup,
       opts,
       opts,
       clsiserverid,
       clsiserverid,
       clsiErr => {
       clsiErr => {
@@ -274,13 +273,14 @@ const ClsiManager = {
   _makeRequestWithClsiServerId(
   _makeRequestWithClsiServerId(
     projectId,
     projectId,
     userId,
     userId,
+    compileGroup,
     opts,
     opts,
     clsiserverid,
     clsiserverid,
     callback
     callback
   ) {
   ) {
     if (clsiserverid) {
     if (clsiserverid) {
       // ignore cookies and newBackend, go straight to the clsi node
       // ignore cookies and newBackend, go straight to the clsi node
-      opts.qs = Object.assign({ clsiserverid }, opts.qs)
+      opts.qs = Object.assign({ compileGroup, clsiserverid }, opts.qs)
       request(opts, (err, response, body) => {
       request(opts, (err, response, body) => {
         if (err) {
         if (err) {
           return callback(
           return callback(
@@ -290,11 +290,11 @@ const ClsiManager = {
         callback(null, response, body)
         callback(null, response, body)
       })
       })
     } else {
     } else {
-      ClsiManager._makeRequest(projectId, userId, opts, callback)
+      ClsiManager._makeRequest(projectId, userId, compileGroup, opts, callback)
     }
     }
   },
   },
 
 
-  _makeRequest(projectId, userId, opts, callback) {
+  _makeRequest(projectId, userId, compileGroup, opts, callback) {
     async.series(
     async.series(
       {
       {
         currentBackend(cb) {
         currentBackend(cb) {
@@ -302,6 +302,7 @@ const ClsiManager = {
           ClsiCookieManager.getCookieJar(
           ClsiCookieManager.getCookieJar(
             projectId,
             projectId,
             userId,
             userId,
+            compileGroup,
             (err, jar, clsiServerId) => {
             (err, jar, clsiServerId) => {
               if (err != null) {
               if (err != null) {
                 return callback(
                 return callback(
@@ -327,6 +328,7 @@ const ClsiManager = {
                 ClsiCookieManager.setServerId(
                 ClsiCookieManager.setServerId(
                   projectId,
                   projectId,
                   userId,
                   userId,
+                  compileGroup,
                   response,
                   response,
                   clsiServerId,
                   clsiServerId,
                   (err, newClsiServerId) => {
                   (err, newClsiServerId) => {
@@ -361,6 +363,7 @@ const ClsiManager = {
           ClsiManager._makeNewBackendRequest(
           ClsiManager._makeNewBackendRequest(
             projectId,
             projectId,
             userId,
             userId,
+            compileGroup,
             opts,
             opts,
             (err, response, body) => {
             (err, response, body) => {
               if (err != null) {
               if (err != null) {
@@ -407,7 +410,7 @@ const ClsiManager = {
     )
     )
   },
   },
 
 
-  _makeNewBackendRequest(projectId, userId, baseOpts, callback) {
+  _makeNewBackendRequest(projectId, userId, compileGroup, baseOpts, callback) {
     if (Settings.apis.clsi_new == null || Settings.apis.clsi_new.url == null) {
     if (Settings.apis.clsi_new == null || Settings.apis.clsi_new.url == null) {
       return callback()
       return callback()
     }
     }
@@ -421,6 +424,7 @@ const ClsiManager = {
     NewBackendCloudClsiCookieManager.getCookieJar(
     NewBackendCloudClsiCookieManager.getCookieJar(
       projectId,
       projectId,
       userId,
       userId,
+      compileGroup,
       (err, jar, clsiServerId) => {
       (err, jar, clsiServerId) => {
         if (err != null) {
         if (err != null) {
           return callback(
           return callback(
@@ -444,6 +448,7 @@ const ClsiManager = {
           NewBackendCloudClsiCookieManager.setServerId(
           NewBackendCloudClsiCookieManager.setServerId(
             projectId,
             projectId,
             userId,
             userId,
+            compileGroup,
             response,
             response,
             clsiServerId,
             clsiServerId,
             err => {
             err => {
@@ -463,15 +468,15 @@ const ClsiManager = {
   },
   },
 
 
   _getCompilerUrl(compileGroup, projectId, userId, action) {
   _getCompilerUrl(compileGroup, projectId, userId, action) {
-    const host = Settings.apis.clsi.url
-    let path = `/project/${projectId}`
+    const u = new URL(`/project/${projectId}`, Settings.apis.clsi.url)
     if (userId != null) {
     if (userId != null) {
-      path += `/user/${userId}`
+      u.pathname += `/user/${userId}`
     }
     }
     if (action != null) {
     if (action != null) {
-      path += `/${action}`
+      u.pathname += `/${action}`
     }
     }
-    return `${host}${path}`
+    u.search = new URLSearchParams({ compileGroup }).toString()
+    return u.href
   },
   },
 
 
   _postToClsi(projectId, userId, req, compileGroup, callback) {
   _postToClsi(projectId, userId, req, compileGroup, callback) {
@@ -489,16 +494,21 @@ const ClsiManager = {
     ClsiManager._makeRequest(
     ClsiManager._makeRequest(
       projectId,
       projectId,
       userId,
       userId,
+      compileGroup,
       opts,
       opts,
       (err, response, body, clsiServerId) => {
       (err, response, body, clsiServerId) => {
         if (err != null) {
         if (err != null) {
           return callback(
           return callback(
-            new OError('failed to make request to CLSI', {
-              projectId,
-              userId,
-              compileOptions: req.compile.options,
-              rootResourcePath: req.compile.rootResourcePath,
-            })
+            new OError(
+              'failed to make request to CLSI',
+              {
+                projectId,
+                userId,
+                compileOptions: req.compile.options,
+                rootResourcePath: req.compile.rootResourcePath,
+              },
+              err
+            )
           )
           )
         }
         }
         if (response.statusCode >= 200 && response.statusCode < 300) {
         if (response.statusCode >= 200 && response.statusCode < 300) {
@@ -655,9 +665,18 @@ const ClsiManager = {
     )
     )
   },
   },
 
 
-  getOutputFileStream(projectId, userId, buildId, outputFilePath, callback) {
+  getOutputFileStream(
+    projectId,
+    userId,
+    compileGroup,
+    clsiServerId,
+    buildId,
+    outputFilePath,
+    callback
+  ) {
     const url = `${Settings.apis.clsi.url}/project/${projectId}/user/${userId}/build/${buildId}/output/${outputFilePath}`
     const url = `${Settings.apis.clsi.url}/project/${projectId}/user/${userId}/build/${buildId}/output/${outputFilePath}`
-    ClsiCookieManager.getCookieJar(projectId, userId, (err, jar) => {
+    // TODO(das7pad): remove one week after landing frontend changes.
+    ClsiCookieManager.getCookieJar(projectId, userId, '', (err, jar) => {
       if (err != null) {
       if (err != null) {
         return callback(
         return callback(
           OError.tag(err, 'Failed to get cookie jar', {
           OError.tag(err, 'Failed to get cookie jar', {
@@ -669,6 +688,10 @@ const ClsiManager = {
         )
         )
       }
       }
       const options = { url, method: 'GET', timeout: 60 * 1000, jar }
       const options = { url, method: 'GET', timeout: 60 * 1000, jar }
+      if (clsiServerId) {
+        options.qs = { compileGroup, clsiserverid: clsiServerId }
+        delete options.jar
+      }
       const readStream = request(options)
       const readStream = request(options)
       callback(null, readStream)
       callback(null, readStream)
     })
     })
@@ -863,6 +886,7 @@ const ClsiManager = {
   },
   },
 
 
   wordCount(projectId, userId, file, options, clsiserverid, callback) {
   wordCount(projectId, userId, file, options, clsiserverid, callback) {
+    const { compileGroup } = options
     ClsiManager._buildRequest(projectId, options, (err, req) => {
     ClsiManager._buildRequest(projectId, options, (err, req) => {
       if (err != null) {
       if (err != null) {
         return callback(
         return callback(
@@ -874,7 +898,7 @@ const ClsiManager = {
       }
       }
       const filename = file || req.compile.rootResourcePath
       const filename = file || req.compile.rootResourcePath
       const wordCountUrl = ClsiManager._getCompilerUrl(
       const wordCountUrl = ClsiManager._getCompilerUrl(
-        options.compileGroup,
+        compileGroup,
         projectId,
         projectId,
         userId,
         userId,
         'wordcount'
         'wordcount'
@@ -891,6 +915,7 @@ const ClsiManager = {
       ClsiManager._makeRequestWithClsiServerId(
       ClsiManager._makeRequestWithClsiServerId(
         projectId,
         projectId,
         userId,
         userId,
+        compileGroup,
         opts,
         opts,
         clsiserverid,
         clsiserverid,
         (err, response, body) => {
         (err, response, body) => {

+ 65 - 54
services/web/app/src/Features/Compile/CompileController.js

@@ -336,6 +336,7 @@ module.exports = CompileController = {
     const limits = {
     const limits = {
       compileGroup:
       compileGroup:
         (req.body != null ? req.body.compileGroup : undefined) ||
         (req.body != null ? req.body.compileGroup : undefined) ||
+        req.query?.compileGroup ||
         Settings.defaultFeatures.compileGroup,
         Settings.defaultFeatures.compileGroup,
     }
     }
     return CompileController.proxyToClsiWithLimits(
     return CompileController.proxyToClsiWithLimits(
@@ -489,59 +490,64 @@ module.exports = CompileController = {
     if (next == null) {
     if (next == null) {
       next = function () {}
       next = function () {}
     }
     }
-    _getPersistenceOptions(req, project_id, (err, persistenceOptions) => {
-      let qs
-      if (err != null) {
-        OError.tag(err, 'error getting cookie jar for clsi request')
-        return next(err)
-      }
-      // expand any url parameter passed in as {url:..., qs:...}
-      if (typeof url === 'object') {
-        ;({ url, qs } = url)
-      }
-      const compilerUrl = Settings.apis.clsi.url
-      url = `${compilerUrl}${url}`
-      const oneMinute = 60 * 1000
-      // the base request
-      const options = {
-        url,
-        method: req.method,
-        timeout: oneMinute,
-        ...persistenceOptions,
-      }
-      // add any provided query string
-      if (qs != null) {
-        options.qs = Object.assign(options.qs || {}, qs)
-      }
-      // if we have a build parameter, pass it through to the clsi
-      if (
-        (req.query != null ? req.query.pdfng : undefined) &&
-        (req.query != null ? req.query.build : undefined) != null
-      ) {
-        // only for new pdf viewer
-        if (options.qs == null) {
-          options.qs = {}
+    _getPersistenceOptions(
+      req,
+      project_id,
+      limits.compileGroup,
+      (err, persistenceOptions) => {
+        let qs
+        if (err != null) {
+          OError.tag(err, 'error getting cookie jar for clsi request')
+          return next(err)
         }
         }
-        options.qs.build = req.query.build
-      }
-      // if we are byte serving pdfs, pass through If-* and Range headers
-      // do not send any others, there's a proxying loop if Host: is passed!
-      if (req.query != null ? req.query.pdfng : undefined) {
-        const newHeaders = {}
-        for (const h in req.headers) {
-          const v = req.headers[h]
-          if (/^(If-|Range)/i.test(h)) {
-            newHeaders[h] = req.headers[h]
+        // expand any url parameter passed in as {url:..., qs:...}
+        if (typeof url === 'object') {
+          ;({ url, qs } = url)
+        }
+        const compilerUrl = Settings.apis.clsi.url
+        url = `${compilerUrl}${url}`
+        const oneMinute = 60 * 1000
+        // the base request
+        const options = {
+          url,
+          method: req.method,
+          timeout: oneMinute,
+          ...persistenceOptions,
+        }
+        // add any provided query string
+        if (qs != null) {
+          options.qs = Object.assign(options.qs || {}, qs)
+        }
+        // if we have a build parameter, pass it through to the clsi
+        if (
+          (req.query != null ? req.query.pdfng : undefined) &&
+          (req.query != null ? req.query.build : undefined) != null
+        ) {
+          // only for new pdf viewer
+          if (options.qs == null) {
+            options.qs = {}
+          }
+          options.qs.build = req.query.build
+        }
+        // if we are byte serving pdfs, pass through If-* and Range headers
+        // do not send any others, there's a proxying loop if Host: is passed!
+        if (req.query != null ? req.query.pdfng : undefined) {
+          const newHeaders = {}
+          for (const h in req.headers) {
+            const v = req.headers[h]
+            if (/^(If-|Range)/i.test(h)) {
+              newHeaders[h] = req.headers[h]
+            }
           }
           }
+          options.headers = newHeaders
         }
         }
-        options.headers = newHeaders
+        const proxy = request(options)
+        proxy.pipe(res)
+        return proxy.on('error', error =>
+          logger.warn({ err: error, url }, 'CLSI proxy error')
+        )
       }
       }
-      const proxy = request(options)
-      proxy.pipe(res)
-      return proxy.on('error', error =>
-        logger.warn({ err: error, url }, 'CLSI proxy error')
-      )
-    })
+    )
   },
   },
 
 
   wordCount(req, res, next) {
   wordCount(req, res, next) {
@@ -568,14 +574,19 @@ module.exports = CompileController = {
   },
   },
 }
 }
 
 
-function _getPersistenceOptions(req, projectId, callback) {
+function _getPersistenceOptions(req, projectId, compileGroup, callback) {
   const { clsiserverid } = req.query
   const { clsiserverid } = req.query
   const user_id = SessionManager.getLoggedInUserId(req)
   const user_id = SessionManager.getLoggedInUserId(req)
   if (clsiserverid && typeof clsiserverid === 'string') {
   if (clsiserverid && typeof clsiserverid === 'string') {
-    callback(null, { qs: { clsiserverid } })
+    callback(null, { qs: { clsiserverid, compileGroup } })
   } else {
   } else {
-    ClsiCookieManager.getCookieJar(projectId, user_id, (err, jar) => {
-      callback(err, { jar })
-    })
+    ClsiCookieManager.getCookieJar(
+      projectId,
+      user_id,
+      compileGroup,
+      (err, jar) => {
+        callback(err, { jar })
+      }
+    )
   }
   }
 }
 }

+ 13 - 3
services/web/app/src/Features/LinkedFiles/ProjectOutputFileAgent.js

@@ -131,6 +131,8 @@ function _sanitizeData(data) {
     source_project_id: data.source_project_id,
     source_project_id: data.source_project_id,
     source_output_file_path: data.source_output_file_path,
     source_output_file_path: data.source_output_file_path,
     build_id: data.build_id,
     build_id: data.build_id,
+    clsiServerId: data.clsiServerId,
+    compileGroup: data.compileGroup,
   }
   }
 }
 }
 
 
@@ -168,8 +170,12 @@ function _checkAuth(projectId, data, currentUserId, callback) {
 
 
 function _getFileStream(linkedFileData, userId, callback) {
 function _getFileStream(linkedFileData, userId, callback) {
   callback = _.once(callback)
   callback = _.once(callback)
-  const { source_output_file_path: sourceOutputFilePath, build_id: buildId } =
-    linkedFileData
+  const {
+    source_output_file_path: sourceOutputFilePath,
+    build_id: buildId,
+    clsiServerId,
+    compileGroup,
+  } = linkedFileData
   LinkedFilesHandler.getSourceProject(linkedFileData, (err, project) => {
   LinkedFilesHandler.getSourceProject(linkedFileData, (err, project) => {
     if (err) {
     if (err) {
       return callback(err)
       return callback(err)
@@ -178,6 +184,8 @@ function _getFileStream(linkedFileData, userId, callback) {
     ClsiManager.getOutputFileStream(
     ClsiManager.getOutputFileStream(
       sourceProjectId,
       sourceProjectId,
       userId,
       userId,
+      compileGroup,
+      clsiServerId,
       buildId,
       buildId,
       sourceOutputFilePath,
       sourceOutputFilePath,
       (err, readStream) => {
       (err, readStream) => {
@@ -203,7 +211,7 @@ function _compileAndGetFileStream(linkedFileData, userId, callback) {
       sourceProjectId,
       sourceProjectId,
       userId,
       userId,
       {},
       {},
-      (err, status, outputFiles) => {
+      (err, status, outputFiles, clsiServerId, limits) => {
         if (err) {
         if (err) {
           return callback(err)
           return callback(err)
         }
         }
@@ -221,6 +229,8 @@ function _compileAndGetFileStream(linkedFileData, userId, callback) {
         ClsiManager.getOutputFileStream(
         ClsiManager.getOutputFileStream(
           sourceProjectId,
           sourceProjectId,
           userId,
           userId,
+          limits.compileGroup,
+          clsiServerId,
           buildId,
           buildId,
           sourceOutputFilePath,
           sourceOutputFilePath,
           (err, readStream) => {
           (err, readStream) => {

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js

@@ -94,6 +94,8 @@ export default function FileTreeImportFromProject() {
           source_project_id: selectedProject._id,
           source_project_id: selectedProject._id,
           source_output_file_path: selectedProjectOutputFile.path,
           source_output_file_path: selectedProjectOutputFile.path,
           build_id: selectedProjectOutputFile.build,
           build_id: selectedProjectOutputFile.build,
+          clsiServerId: selectedProjectOutputFile.clsiServerId,
+          compileGroup: selectedProjectOutputFile.compileGroup,
         },
         },
       })
       })
     } else {
     } else {

+ 4 - 1
services/web/frontend/js/features/file-tree/hooks/use-project-output-files.js

@@ -31,7 +31,10 @@ export function useProjectOutputFiles(projectId) {
             const filteredFiles = data.outputFiles.filter(file =>
             const filteredFiles = data.outputFiles.filter(file =>
               file.path.match(/.*\.(pdf|png|jpeg|jpg|gif)/)
               file.path.match(/.*\.(pdf|png|jpeg|jpg|gif)/)
             )
             )
-
+            data.outputFiles.forEach(file => {
+              file.clsiServerId = data.clsiServerId
+              file.compileGroup = data.compileGroup
+            })
             setData(filteredFiles.sort(alphabetical))
             setData(filteredFiles.sort(alphabetical))
           } else {
           } else {
             setError('linked-project-compile-error')
             setError('linked-project-compile-error')

+ 4 - 1
services/web/frontend/js/features/pdf-preview/util/file-list.js

@@ -1,7 +1,7 @@
 const topFileTypes = ['bbl', 'gls', 'ind']
 const topFileTypes = ['bbl', 'gls', 'ind']
 const ignoreFiles = ['output.fls', 'output.fdb_latexmk']
 const ignoreFiles = ['output.fls', 'output.fdb_latexmk']
 
 
-export const buildFileList = (outputFiles, clsiServerId) => {
+export const buildFileList = (outputFiles, clsiServerId, compileGroup) => {
   const files = { top: [], other: [] }
   const files = { top: [], other: [] }
 
 
   if (outputFiles) {
   if (outputFiles) {
@@ -10,6 +10,9 @@ export const buildFileList = (outputFiles, clsiServerId) => {
     if (clsiServerId) {
     if (clsiServerId) {
       params.set('clsiserverid', clsiServerId)
       params.set('clsiserverid', clsiServerId)
     }
     }
+    if (compileGroup) {
+      params.set('compileGroup', compileGroup)
+    }
 
 
     const queryString = params.toString()
     const queryString = params.toString()
 
 

+ 3 - 1
services/web/frontend/js/shared/context/local-compile-context.js

@@ -253,7 +253,9 @@ export function LocalCompileProvider({ children }) {
             setPdfUrl(result.pdfUrl)
             setPdfUrl(result.pdfUrl)
           }
           }
 
 
-          setFileList(buildFileList(outputFiles, data.clsiServerId))
+          setFileList(
+            buildFileList(outputFiles, data.clsiServerId, data.compileGroup)
+          )
 
 
           // handle log files
           // handle log files
           // asynchronous (TODO: cancel on new compile?)
           // asynchronous (TODO: cancel on new compile?)

+ 17 - 4
services/web/test/unit/src/Compile/ClsiCookieManagerTests.js

@@ -67,6 +67,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager._getServerId(
       return this.ClsiCookieManager._getServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, serverId) => {
         (err, serverId) => {
           this.redis.get
           this.redis.get
             .calledWith(`clsiserver:${this.project_id}:${this.user_id}`)
             .calledWith(`clsiserver:${this.project_id}:${this.user_id}`)
@@ -80,11 +81,12 @@ describe('ClsiCookieManager', function () {
     it('should _populateServerIdViaRequest if no key is found', function (done) {
     it('should _populateServerIdViaRequest if no key is found', function (done) {
       this.ClsiCookieManager._populateServerIdViaRequest = sinon
       this.ClsiCookieManager._populateServerIdViaRequest = sinon
         .stub()
         .stub()
-        .callsArgWith(2)
+        .yields(null)
       this.redis.get.callsArgWith(1, null)
       this.redis.get.callsArgWith(1, null)
       return this.ClsiCookieManager._getServerId(
       return this.ClsiCookieManager._getServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, serverId) => {
         (err, serverId) => {
           this.ClsiCookieManager._populateServerIdViaRequest
           this.ClsiCookieManager._populateServerIdViaRequest
             .calledWith(this.project_id, this.user_id)
             .calledWith(this.project_id, this.user_id)
@@ -97,11 +99,12 @@ describe('ClsiCookieManager', function () {
     it('should _populateServerIdViaRequest if no key is blank', function (done) {
     it('should _populateServerIdViaRequest if no key is blank', function (done) {
       this.ClsiCookieManager._populateServerIdViaRequest = sinon
       this.ClsiCookieManager._populateServerIdViaRequest = sinon
         .stub()
         .stub()
-        .callsArgWith(2)
+        .yields(null)
       this.redis.get.callsArgWith(1, null, '')
       this.redis.get.callsArgWith(1, null, '')
       return this.ClsiCookieManager._getServerId(
       return this.ClsiCookieManager._getServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, serverId) => {
         (err, serverId) => {
           this.ClsiCookieManager._populateServerIdViaRequest
           this.ClsiCookieManager._populateServerIdViaRequest
             .calledWith(this.project_id, this.user_id)
             .calledWith(this.project_id, this.user_id)
@@ -125,11 +128,13 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager._populateServerIdViaRequest(
       return this.ClsiCookieManager._populateServerIdViaRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         (err, serverId) => {
         (err, serverId) => {
           const args = this.ClsiCookieManager.setServerId.args[0]
           const args = this.ClsiCookieManager.setServerId.args[0]
           args[0].should.equal(this.project_id)
           args[0].should.equal(this.project_id)
           args[1].should.equal(this.user_id)
           args[1].should.equal(this.user_id)
-          args[2].should.deep.equal(this.response)
+          args[2].should.equal('standard')
+          args[3].should.deep.equal(this.response)
           return done()
           return done()
         }
         }
       )
       )
@@ -139,6 +144,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager._populateServerIdViaRequest(
       return this.ClsiCookieManager._populateServerIdViaRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, serverId) => {
         (err, serverId) => {
           serverId.should.equal('clsi-9')
           serverId.should.equal('clsi-9')
           return done()
           return done()
@@ -159,6 +165,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.setServerId(
       return this.ClsiCookieManager.setServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.response,
         this.response,
         null,
         null,
         err => {
         err => {
@@ -178,6 +185,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.setServerId(
       return this.ClsiCookieManager.setServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.response,
         this.response,
         null,
         null,
         (err, serverId) => {
         (err, serverId) => {
@@ -198,6 +206,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.setServerId(
       return this.ClsiCookieManager.setServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.response,
         this.response,
         null,
         null,
         (err, serverId) => {
         (err, serverId) => {
@@ -214,6 +223,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.setServerId(
       return this.ClsiCookieManager.setServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.response,
         this.response,
         null,
         null,
         (err, serverId) => {
         (err, serverId) => {
@@ -243,6 +253,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.setServerId(
       return this.ClsiCookieManager.setServerId(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.response,
         this.response,
         null,
         null,
         (err, serverId) => {
         (err, serverId) => {
@@ -263,13 +274,14 @@ describe('ClsiCookieManager', function () {
     beforeEach(function () {
     beforeEach(function () {
       return (this.ClsiCookieManager._getServerId = sinon
       return (this.ClsiCookieManager._getServerId = sinon
         .stub()
         .stub()
-        .callsArgWith(2, null, 'clsi-11'))
+        .yields(null, 'clsi-11'))
     })
     })
 
 
     it('should return a jar with the cookie set populated from redis', function (done) {
     it('should return a jar with the cookie set populated from redis', function (done) {
       return this.ClsiCookieManager.getCookieJar(
       return this.ClsiCookieManager.getCookieJar(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, jar) => {
         (err, jar) => {
           jar._jar.store.idx['clsi.example.com']['/'][
           jar._jar.store.idx['clsi.example.com']['/'][
             this.settings.clsiCookie.key
             this.settings.clsiCookie.key
@@ -293,6 +305,7 @@ describe('ClsiCookieManager', function () {
       return this.ClsiCookieManager.getCookieJar(
       return this.ClsiCookieManager.getCookieJar(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        '',
         (err, jar) => {
         (err, jar) => {
           assert.deepEqual(jar, realRequst.jar())
           assert.deepEqual(jar, realRequst.jar())
           return done()
           return done()

+ 43 - 29
services/web/test/unit/src/Compile/ClsiManagerTests.js

@@ -8,7 +8,7 @@ describe('ClsiManager', function () {
     this.jar = { cookie: 'stuff' }
     this.jar = { cookie: 'stuff' }
     this.ClsiCookieManager = {
     this.ClsiCookieManager = {
       clearServerId: sinon.stub().yields(),
       clearServerId: sinon.stub().yields(),
-      getCookieJar: sinon.stub().callsArgWith(2, null, this.jar),
+      getCookieJar: sinon.stub().yields(null, this.jar),
       setServerId: sinon.stub().yields(null),
       setServerId: sinon.stub().yields(null),
       _getServerId: sinon.stub(),
       _getServerId: sinon.stub(),
     }
     }
@@ -491,9 +491,10 @@ describe('ClsiManager', function () {
           .calledWith(
           .calledWith(
             this.project_id,
             this.project_id,
             this.user_id,
             this.user_id,
+            'standard',
             {
             {
               method: 'DELETE',
               method: 'DELETE',
-              url: `${this.settings.apis.clsi.url}/project/${this.project_id}/user/${this.user_id}`,
+              url: `${this.settings.apis.clsi.url}/project/${this.project_id}/user/${this.user_id}?compileGroup=standard`,
             },
             },
             'node-1'
             'node-1'
           )
           )
@@ -922,12 +923,7 @@ describe('ClsiManager', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.ClsiManager._makeRequest = sinon
         this.ClsiManager._makeRequest = sinon
           .stub()
           .stub()
-          .callsArgWith(
-            3,
-            null,
-            { statusCode: 204 },
-            (this.body = { mock: 'foo' })
-          )
+          .yields(null, { statusCode: 204 }, (this.body = { mock: 'foo' }))
         this.ClsiManager._postToClsi(
         this.ClsiManager._postToClsi(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
@@ -938,9 +934,9 @@ describe('ClsiManager', function () {
       })
       })
 
 
       it('should send the request to the CLSI', function () {
       it('should send the request to the CLSI', function () {
-        const url = `${this.settings.apis.clsi.url}/project/${this.project_id}/user/${this.user_id}/compile`
+        const url = `${this.settings.apis.clsi.url}/project/${this.project_id}/user/${this.user_id}/compile?compileGroup=standard`
         this.ClsiManager._makeRequest
         this.ClsiManager._makeRequest
-          .calledWith(this.project_id, this.user_id, {
+          .calledWith(this.project_id, this.user_id, 'standard', {
             method: 'POST',
             method: 'POST',
             url,
             url,
             json: this.req,
             json: this.req,
@@ -957,12 +953,7 @@ describe('ClsiManager', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.ClsiManager._makeRequest = sinon
         this.ClsiManager._makeRequest = sinon
           .stub()
           .stub()
-          .callsArgWith(
-            3,
-            null,
-            { statusCode: 500 },
-            (this.body = { mock: 'foo' })
-          )
+          .yields(null, { statusCode: 500 }, (this.body = { mock: 'foo' }))
         this.ClsiManager._postToClsi(
         this.ClsiManager._postToClsi(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
@@ -983,8 +974,7 @@ describe('ClsiManager', function () {
       this.ClsiManager._makeRequestWithClsiServerId = sinon
       this.ClsiManager._makeRequestWithClsiServerId = sinon
         .stub()
         .stub()
         .yields(null, { statusCode: 200 }, (this.body = { mock: 'foo' }))
         .yields(null, { statusCode: 200 }, (this.body = { mock: 'foo' }))
-      this.ClsiManager._buildRequest = sinon.stub().callsArgWith(
-        2,
+      this.ClsiManager._buildRequest = sinon.stub().yields(
         null,
         null,
         (this.req = {
         (this.req = {
           compile: { rootResourcePath: 'rootfile.text', options: {} },
           compile: { rootResourcePath: 'rootfile.text', options: {} },
@@ -998,7 +988,7 @@ describe('ClsiManager', function () {
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
           false,
           false,
-          {},
+          { compileGroup: 'standard' },
           'node-1',
           'node-1',
           this.callback
           this.callback
         )
         )
@@ -1009,9 +999,10 @@ describe('ClsiManager', function () {
           .calledWith(
           .calledWith(
             this.project_id,
             this.project_id,
             this.user_id,
             this.user_id,
+            'standard',
             {
             {
               method: 'GET',
               method: 'GET',
-              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount`,
+              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount?compileGroup=standard`,
               qs: {
               qs: {
                 file: 'rootfile.text',
                 file: 'rootfile.text',
                 image: undefined,
                 image: undefined,
@@ -1034,7 +1025,7 @@ describe('ClsiManager', function () {
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
           'main.tex',
           'main.tex',
-          {},
+          { compileGroup: 'standard' },
           'node-2',
           'node-2',
           this.callback
           this.callback
         )
         )
@@ -1045,9 +1036,10 @@ describe('ClsiManager', function () {
           .calledWith(
           .calledWith(
             this.project_id,
             this.project_id,
             this.user_id,
             this.user_id,
+            'standard',
             {
             {
               method: 'GET',
               method: 'GET',
-              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount`,
+              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount?compileGroup=standard`,
               qs: { file: 'main.tex', image: undefined },
               qs: { file: 'main.tex', image: undefined },
               json: true,
               json: true,
             },
             },
@@ -1065,7 +1057,7 @@ describe('ClsiManager', function () {
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
           'main.tex',
           'main.tex',
-          {},
+          { compileGroup: 'standard' },
           'node-3',
           'node-3',
           this.callback
           this.callback
         )
         )
@@ -1076,9 +1068,10 @@ describe('ClsiManager', function () {
           .calledWith(
           .calledWith(
             this.project_id,
             this.project_id,
             this.user_id,
             this.user_id,
+            'standard',
             {
             {
               method: 'GET',
               method: 'GET',
-              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount`,
+              url: `http://clsi.example.com/project/${this.project_id}/user/${this.user_id}/wordcount?compileGroup=standard`,
               qs: { file: 'main.tex', image: this.image },
               qs: { file: 'main.tex', image: this.image },
               json: true,
               json: true,
             },
             },
@@ -1103,6 +1096,7 @@ describe('ClsiManager', function () {
       this.ClsiManager._makeRequest(
       this.ClsiManager._makeRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.opts,
         this.opts,
         () => {
         () => {
           const args = this.request.args[0]
           const args = this.request.args[0]
@@ -1118,10 +1112,16 @@ describe('ClsiManager', function () {
       this.ClsiManager._makeRequest(
       this.ClsiManager._makeRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.opts,
         this.opts,
         () => {
         () => {
           this.ClsiCookieManager.setServerId
           this.ClsiCookieManager.setServerId
-            .calledWith(this.project_id, this.user_id, this.response)
+            .calledWith(
+              this.project_id,
+              this.user_id,
+              'standard',
+              this.response
+            )
             .should.equal(true)
             .should.equal(true)
           done()
           done()
         }
         }
@@ -1144,6 +1144,7 @@ describe('ClsiManager', function () {
         this.ClsiManager._makeRequestWithClsiServerId(
         this.ClsiManager._makeRequestWithClsiServerId(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
+          'standard',
           this.opts,
           this.opts,
           undefined,
           undefined,
           err => {
           err => {
@@ -1162,12 +1163,18 @@ describe('ClsiManager', function () {
         this.ClsiManager._makeRequestWithClsiServerId(
         this.ClsiManager._makeRequestWithClsiServerId(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
+          'standard',
           this.opts,
           this.opts,
           undefined,
           undefined,
           err => {
           err => {
             if (err) return done(err)
             if (err) return done(err)
             this.ClsiCookieManager.setServerId
             this.ClsiCookieManager.setServerId
-              .calledWith(this.project_id, this.user_id, this.response)
+              .calledWith(
+                this.project_id,
+                this.user_id,
+                'standard',
+                this.response
+              )
               .should.equal(true)
               .should.equal(true)
             done()
             done()
           }
           }
@@ -1180,6 +1187,7 @@ describe('ClsiManager', function () {
         this.ClsiManager._makeRequestWithClsiServerId(
         this.ClsiManager._makeRequestWithClsiServerId(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
+          'standard',
           this.opts,
           this.opts,
           'node-1',
           'node-1',
           err => {
           err => {
@@ -1188,7 +1196,10 @@ describe('ClsiManager', function () {
             expect(requestOpts.method).to.equal(this.opts.method)
             expect(requestOpts.method).to.equal(this.opts.method)
             expect(requestOpts.url).to.equal(this.opts.url)
             expect(requestOpts.url).to.equal(this.opts.url)
             expect(requestOpts.jar).to.not.exist
             expect(requestOpts.jar).to.not.exist
-            expect(requestOpts.qs).to.deep.equal({ clsiserverid: 'node-1' })
+            expect(requestOpts.qs).to.deep.equal({
+              clsiserverid: 'node-1',
+              compileGroup: 'standard',
+            })
             done()
             done()
           }
           }
         )
         )
@@ -1198,6 +1209,7 @@ describe('ClsiManager', function () {
         this.ClsiManager._makeRequestWithClsiServerId(
         this.ClsiManager._makeRequestWithClsiServerId(
           this.project_id,
           this.project_id,
           this.user_id,
           this.user_id,
+          'standard',
           this.opts,
           this.opts,
           'node-1',
           'node-1',
           err => {
           err => {
@@ -1216,7 +1228,7 @@ describe('ClsiManager', function () {
       this.response = { there: 'something' }
       this.response = { there: 'something' }
       this.request.callsArgWith(1, null, this.response)
       this.request.callsArgWith(1, null, this.response)
       this.opts = {
       this.opts = {
-        url: this.ClsiManager._getCompilerUrl(null, this.project_id),
+        url: this.ClsiManager._getCompilerUrl('standard', this.project_id),
       }
       }
     })
     })
 
 
@@ -1224,11 +1236,12 @@ describe('ClsiManager', function () {
       this.ClsiManager._makeNewBackendRequest(
       this.ClsiManager._makeNewBackendRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.opts,
         this.opts,
         () => {
         () => {
           const args = this.request.args[0]
           const args = this.request.args[0]
           args[0].url.should.equal(
           args[0].url.should.equal(
-            `https://compiles.somewhere.test/project/${this.project_id}`
+            `https://compiles.somewhere.test/project/${this.project_id}?compileGroup=standard`
           )
           )
           done()
           done()
         }
         }
@@ -1240,6 +1253,7 @@ describe('ClsiManager', function () {
       this.ClsiManager._makeNewBackendRequest(
       this.ClsiManager._makeNewBackendRequest(
         this.project_id,
         this.project_id,
         this.user_id,
         this.user_id,
+        'standard',
         this.opts,
         this.opts,
         err => {
         err => {
           expect(err).to.equal(undefined)
           expect(err).to.equal(undefined)

+ 1 - 1
services/web/test/unit/src/Compile/CompileControllerTests.js

@@ -49,7 +49,7 @@ describe('CompileController', function () {
     }
     }
     this.jar = { cookie: 'stuff' }
     this.jar = { cookie: 'stuff' }
     this.ClsiCookieManager = {
     this.ClsiCookieManager = {
-      getCookieJar: sinon.stub().callsArgWith(2, null, this.jar),
+      getCookieJar: sinon.stub().yields(null, this.jar),
     }
     }
     this.SessionManager = {
     this.SessionManager = {
       getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
       getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),

+ 2 - 0
services/web/test/unit/src/Compile/CompileManagerTests.js

@@ -58,6 +58,7 @@ describe('CompileManager', function () {
     this.callback = sinon.stub()
     this.callback = sinon.stub()
     return (this.limits = {
     return (this.limits = {
       timeout: 42,
       timeout: 42,
+      compileGroup: 'standard',
     })
     })
   })
   })
 
 
@@ -120,6 +121,7 @@ describe('CompileManager', function () {
         return this.ClsiManager.sendRequest
         return this.ClsiManager.sendRequest
           .calledWith(this.project_id, this.user_id, {
           .calledWith(this.project_id, this.user_id, {
             timeout: this.limits.timeout,
             timeout: this.limits.timeout,
+            compileGroup: 'standard',
           })
           })
           .should.equal(true)
           .should.equal(true)
       })
       })