localVmStartup.live.test.ts 5.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. // @vitest-environment node
  2. import {expect,it} from 'vitest'
  3. import Koa from 'koa'
  4. import {createServer} from 'node:http'
  5. import {mkdtemp,mkdir,rm} from 'node:fs/promises'
  6. import {tmpdir} from 'node:os'
  7. import {join} from 'node:path'
  8. import {createHash} from 'node:crypto'
  9. import {DatabaseSync} from 'node:sqlite'
  10. import {WorkspaceStore} from '../../src/server/workspaceStore'
  11. import {WorkspaceNodes,type GatewayTarget} from '../../src/server/workspaceGateway'
  12. import {LocalAuthStore} from '../../src/server/localAuth'
  13. import {RunnerHub} from '../../src/server/runnerHub'
  14. import {SharedComputers} from '../../src/server/sharedComputers'
  15. import {LocalVmService} from '../../src/server/localVm'
  16. import {saveHostTools} from '../../src/server/hostToolSettings'
  17. import {ComputerRuntime} from '../../src/runner/worker/gateway'
  18. import {LocalVmImages} from '../../src/runner/computers/localVm'
  19. import type {RunnerConfiguration} from '../../src/shared/runner'
  20. class Auth extends LocalAuthStore {
  21. override isUserActive(owner:string){return owner==='startup-fixture'}
  22. override isAdminActive(owner:string){return this.isUserActive(owner)}
  23. override pushAuthorizationVersion(){return 1}
  24. override canUseSource(){return true}
  25. }
  26. it.skipIf(!process.env.YAOYAO_COMPUTER_IMAGE)('starts a real saved VM when the managed service restarts, before any Bot command',async()=>{
  27. const home=await mkdtemp(join(tmpdir(),'yaoyao-startup-live-')),store=new WorkspaceStore(home),auth=new Auth(home)
  28. const owner='startup-fixture',ownerKey=createHash('sha256').update(owner).digest('hex')
  29. const app=new Koa(),server=createServer(app.callback())
  30. const target={url:new URL('http://127.0.0.1:1')} as GatewayTarget
  31. const hub=new RunnerHub(store,auth,target)
  32. app.use(async(ctx,next)=>{try{await next()}catch(error){ctx.status=(error as any).status??500;ctx.body={error:(error as Error).message,code:(error as any).code}}})
  33. app.use(hub.middleware())
  34. await new Promise<void>(done=>server.listen(0,'127.0.0.1',done))
  35. const url=`http://127.0.0.1:${(server.address() as {port:number}).port}`
  36. const config={home,upstream:target.url} as any,nodes=new WorkspaceNodes(store,config,target)
  37. const registered=hub.enroll(owner,{name:'启动验收',sourceNodeId:'local',allowedProfiles:['default']})
  38. const runnerConfig:RunnerConfiguration={protocol:1,runnerId:registered.runner.id,token:registered.token,serverURL:url,hermesURL:target.url.href,allowedProfiles:['default'],artifactRoots:[],computers:{runtime:'docker',imageId:process.env.YAOYAO_COMPUTER_IMAGE!,python:'/unused',hermesSource:home,hermesHome:home,network:'none'}}
  39. store.put('_system','local-vm-managed','local',{sealed:nodes.seal(runnerConfig)});saveHostTools(home,{vm:true})
  40. const bot=store.createAgent(owner,{name:'启动验收 Bot',profile:'default'})
  41. const directory=join(home,'runner-state','local-vm',runnerConfig.runnerId);await mkdir(directory,{recursive:true})
  42. const db=new DatabaseSync(join(directory,'runner-commands.sqlite3')),seed=new ComputerRuntime(db,runnerConfig,directory),images=new LocalVmImages(seed,db)
  43. const service=new LocalVmService(store,auth,nodes,hub,new SharedComputers(store,auth,nodes,hub),config)
  44. const spec={id:bot.id,ownerKey,imageId:runnerConfig.computers!.imageId,cwd:'/home/cua/workspace',network:'none' as const}
  45. let seeded=false
  46. try{
  47. await images.ready;await images.idlePolicy(0)
  48. await seed.pool.desktop(spec,'create',()=>{})
  49. await seed.provider.execute(spec,['/bin/sh','-c','printf startup-proof > startup.txt'],{authorize:()=>{}})
  50. await images.close();await seed.controls.close();await seed.pool.close();db.close();seeded=true
  51. expect(await seed.provider.inspect(spec)).toBeUndefined()
  52. await service.start(url)
  53. await expect.poll(async()=>(await seed.provider.inspect(spec))?.running,{timeout:30000,interval:250}).toBe(true)
  54. await (service as any).managed.warmup
  55. expect((service as any).problem).toBeUndefined()
  56. expect((await seed.provider.execute(spec,['cat','startup.txt'],{authorize:()=>{}})).stdout).toBe('startup-proof')
  57. const managed=(service as any).managed.agent.computers as ComputerRuntime
  58. expect(managed.pool.idleStopMinutes).toBe(0)
  59. expect(managed.pool.status(ownerKey)).toEqual([expect.objectContaining({status:'idle',holderIds:[]})])
  60. expect(store.list(owner,'turn')).toEqual([])
  61. await service.close();expect(await seed.provider.inspect(spec)).toBeUndefined()
  62. await service.start(url)
  63. await expect.poll(async()=>(await seed.provider.inspect(spec))?.running,{timeout:30000,interval:250}).toBe(true)
  64. await (service as any).managed.warmup
  65. expect((await seed.provider.execute(spec,['cat','startup.txt'],{authorize:()=>{}})).stdout).toBe('startup-proof')
  66. }finally{
  67. await service.close();hub.close()
  68. if(!seeded){await images.close();await seed.controls.close();await seed.pool.close();db.close()}
  69. await new Promise<void>(done=>{server.close(()=>done());server.closeAllConnections()})
  70. nodes.close();store.close();await rm(home,{recursive:true,force:true})
  71. }
  72. },120000)