create_steam.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "context"
  17. "flag"
  18. "fmt"
  19. "os"
  20. "strings"
  21. "time"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/util/rand"
  25. api "yunion.io/x/onecloud/pkg/apis"
  26. "yunion.io/x/onecloud/pkg/apis/compute"
  27. "yunion.io/x/onecloud/pkg/mcclient"
  28. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  29. )
  30. const (
  31. NV_DEV_UVM = "/dev/nvidia-uvm"
  32. NV_DEV_UVM_TOOLS = "/dev/nvidia-uvm-tools"
  33. DEV_DRI = "/dev/dri/"
  34. NV_DEV_CAP1 = "/dev/nvidia-caps/nvidia-cap1"
  35. NV_DEV_CAP2 = "/dev/nvidia-caps/nvidia-cap2"
  36. NV_DEV_CTL = "/dev/nvidiactl"
  37. NV_DEV_MODESET = "/dev/nvidia-modeset"
  38. DEV_UINPUT = "/dev/uinput"
  39. DEV_UHID = "/dev/uhid"
  40. VOL_DEV = "/dev"
  41. VOL_RUN_UDEV = "/run/udev"
  42. CGROUP_RULE_13 = "c 13:* rmw"
  43. CGROUP_RULE_244 = "c 244:* rmw"
  44. )
  45. var (
  46. authUrl string
  47. user string
  48. password string
  49. region string
  50. podNet string
  51. podIP string
  52. podName string
  53. diskSizeGB int
  54. ncpu int
  55. mem int
  56. basePort int
  57. accessPort int
  58. wolfImage string
  59. steamImage string
  60. externalIP string
  61. enableLxcfs bool
  62. gpu string
  63. gpuModel string
  64. gpuType string
  65. gpuEnvId string
  66. // renderNode string
  67. overlay string
  68. alwaysMountDriverVol bool
  69. devs string
  70. devsList []string
  71. wolfAllGpu bool
  72. mounts string
  73. mountList []string
  74. appMounts string
  75. appMountList []string
  76. steamNoBigScreen bool
  77. )
  78. func init() {
  79. flag.StringVar(&authUrl, "auth-url", "", "auth url")
  80. flag.StringVar(&user, "user", "", "user")
  81. flag.StringVar(&password, "password", "", "password")
  82. flag.StringVar(&region, "region", "", "region")
  83. flag.StringVar(&podNet, "net", "", "pod net")
  84. flag.StringVar(&podIP, "ip", "", "pod ip")
  85. flag.StringVar(&podName, "name", "steam", "pod name")
  86. flag.IntVar(&ncpu, "ncpu", 8, "cpu count")
  87. flag.IntVar(&mem, "mem", 16, "memory in GB")
  88. flag.IntVar(&diskSizeGB, "disk-size", 10, "disk size in GB")
  89. flag.IntVar(&accessPort, "port", 20105, "moonlight access http port")
  90. // - registry.cn-beijing.aliyuncs.com/zexi/wolf:hook-0408.0: stable version
  91. flag.StringVar(&wolfImage, "wolf-image", "registry.cn-beijing.aliyuncs.com/zexi/wolf:patch-191-0420.0", "wolf image")
  92. flag.StringVar(&steamImage, "steam-image", "registry.cn-beijing.aliyuncs.com/zexi/steam:custom.4", "steam image")
  93. flag.StringVar(&externalIP, "eip", "", "external ip")
  94. flag.BoolVar(&enableLxcfs, "lxcfs", false, "enable lxcfs")
  95. flag.BoolVar(&alwaysMountDriverVol, "mount-driver-vol", false, "always mount driver volume")
  96. flag.StringVar(&gpu, "gpu", "", "gpu")
  97. flag.StringVar(&gpuEnvId, "gpu-env-id", "", "gpu env id")
  98. flag.StringVar(&gpuModel, "gpu-model", "", "gpu model")
  99. flag.StringVar(&gpuType, "gpu-type", "", "gpu type")
  100. // flag.StringVar(&renderNode, "render-node", "/dev/dri/renderD128", "render node")
  101. flag.StringVar(&overlay, "overlay", "", "overlay")
  102. flag.StringVar(&devs, "devs", "", "devs")
  103. flag.StringVar(&mounts, "mounts", "", "mounts")
  104. flag.StringVar(&appMounts, "app-mounts", "", "app mounts")
  105. flag.BoolVar(&wolfAllGpu, "wolf-all-gpu", false, "wolf all gpu")
  106. flag.BoolVar(&steamNoBigScreen, "steam-no-big-screen", false, "steam no big screen")
  107. flag.Parse()
  108. basePort = accessPort - 5
  109. initAuthInfo()
  110. log.Infof("Connecting to %s as %s", authUrl, user)
  111. devsList = strings.Split(devs, ",")
  112. mountList = strings.Split(mounts, ",")
  113. appMountList = strings.Split(appMounts, ",")
  114. }
  115. func initAuthInfo() {
  116. if authUrl == "" {
  117. authUrl = os.Getenv("OS_AUTH_URL")
  118. }
  119. if user == "" {
  120. user = os.Getenv("OS_USERNAME")
  121. }
  122. if password == "" {
  123. password = os.Getenv("OS_PASSWORD")
  124. }
  125. if region == "" {
  126. region = os.Getenv("OS_REGION_NAME")
  127. }
  128. }
  129. func NewHostDev(path string) *compute.ContainerDevice {
  130. return &compute.ContainerDevice{
  131. Type: api.CONTAINER_DEVICE_TYPE_HOST,
  132. Host: &compute.ContainerHostDevice{
  133. HostPath: path,
  134. ContainerPath: path,
  135. Permissions: "rwm",
  136. },
  137. }
  138. }
  139. func NewEnv(key, val string) *api.ContainerKeyValue {
  140. return &api.ContainerKeyValue{
  141. Key: key,
  142. Value: val,
  143. }
  144. }
  145. func getMounts(mountList []string) []*api.ContainerVolumeMount {
  146. ret := make([]*api.ContainerVolumeMount, len(mountList))
  147. for i, m := range mountList {
  148. parts := strings.Split(m, ":")
  149. if len(parts) != 2 {
  150. log.Fatalf("Invalid mount spec: %s", m)
  151. }
  152. uniqName := fmt.Sprintf("%s_%s", m, rand.String(2))
  153. ret[i] = &api.ContainerVolumeMount{
  154. UniqueName: uniqName,
  155. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  156. MountPath: parts[1],
  157. HostPath: &api.ContainerVolumeMountHostPath{
  158. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE,
  159. Path: parts[0],
  160. },
  161. }
  162. }
  163. return ret
  164. }
  165. func getTmpSocketsHostPath(name string) string {
  166. return fmt.Sprintf("/tmp/%s/sockets", name)
  167. }
  168. func NewPulseAudioContainer(podName string, enableLxcfs bool) *compute.PodContainerCreateInput {
  169. return &compute.PodContainerCreateInput{
  170. ContainerSpec: compute.ContainerSpec{
  171. ContainerSpec: api.ContainerSpec{
  172. Command: []string{"/entrypoint.sh"},
  173. Image: "registry.cn-beijing.aliyuncs.com/zexi/pulseaudio:master",
  174. ImagePullPolicy: api.ImagePullPolicyAlways,
  175. EnableLxcfs: enableLxcfs,
  176. Envs: []*api.ContainerKeyValue{
  177. NewEnv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"),
  178. NewEnv("UNAME", "retro"),
  179. NewEnv("UID", "1000"),
  180. NewEnv("GID", "1000"),
  181. NewEnv("XDG_RUNTIME_DIR", "/tmp/pulse"),
  182. NewEnv("UMASK", "000"),
  183. NewEnv("HOME", "/root"),
  184. },
  185. },
  186. VolumeMounts: []*api.ContainerVolumeMount{
  187. {
  188. UniqueName: "tmp-pulse-audio",
  189. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  190. MountPath: "/tmp/pulse",
  191. HostPath: &api.ContainerVolumeMountHostPath{
  192. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  193. Path: getTmpSocketsHostPath(podName),
  194. },
  195. },
  196. },
  197. },
  198. }
  199. }
  200. func getNvidiaManualBaseDevs() []*compute.ContainerDevice {
  201. return []*compute.ContainerDevice{
  202. NewHostDev(NV_DEV_UVM),
  203. NewHostDev(NV_DEV_UVM_TOOLS),
  204. NewHostDev(NV_DEV_CAP1),
  205. NewHostDev(NV_DEV_CAP2),
  206. NewHostDev(NV_DEV_CTL),
  207. NewHostDev(NV_DEV_MODESET),
  208. }
  209. }
  210. func getNvidiaNvDevs(idx int) []*compute.ContainerDevice {
  211. baseDevs := getNvidiaManualBaseDevs()
  212. dev := NewHostDev(fmt.Sprintf("/dev/nvidia%d", idx))
  213. baseDevs = append(baseDevs, dev)
  214. return baseDevs
  215. }
  216. func getNvidiaAppDevs(idx int) []*compute.ContainerDevice {
  217. devs := getNvidiaNvDevs(idx)
  218. driDev := NewHostDev(fmt.Sprintf("/dev/dri/card%d", idx))
  219. renderDev := NewHostDev(fmt.Sprintf("/dev/dri/renderD%d", idx+128))
  220. devs = append(devs, driDev, renderDev)
  221. return devs
  222. }
  223. func NewWolfContainer(i CreateInput) *compute.PodContainerCreateInput {
  224. zero := 0
  225. eip := i.IP
  226. if i.ExternalIP != "" {
  227. eip = i.ExternalIP
  228. }
  229. envs := []*api.ContainerKeyValue{
  230. // NewEnv("WOLF_LOG_LEVEL", "DEBUG"),
  231. NewEnv("WOLF_BASE_PORT", fmt.Sprintf("%d", i.BasePort)),
  232. NewEnv("WOLF_EXTERNAL_IP", eip),
  233. NewEnv("HOST_APPS_STATE_FOLDER", "/etc/wolf"),
  234. NewEnv("XDG_RUNTIME_DIR", "/tmp/sockets"),
  235. // NewEnv("WOLF_RENDER_NODE", i.RenderNode),
  236. }
  237. envs = append(envs, getPortEnvs(i.BasePort)...)
  238. if i.GPU == "" || i.WolfAllGpu {
  239. envs = append(envs, NewEnv("NVIDIA_DRIVER_VOLUME_NAME", "nvidia-driver-vol"))
  240. }
  241. if i.WolfAllGpu {
  242. envs = append(envs,
  243. NewEnv("NVIDIA_VISIBLE_DEVICES", "all"),
  244. NewEnv("NVIDIA_DRIVER_CAPABILITIES", "all"))
  245. }
  246. devs := []*compute.ContainerDevice{
  247. NewHostDev(DEV_UINPUT),
  248. NewHostDev(DEV_UHID),
  249. }
  250. if i.GPU == "" && i.GPUModel == "" && i.GPUType == "" {
  251. if !i.WolfAllGpu {
  252. devs = append(devs, NewHostDev(DEV_DRI))
  253. devs = append(devs, getNvidiaNvDevs(0)...)
  254. }
  255. } else {
  256. id0 := 0
  257. if !i.WolfAllGpu {
  258. devs = append(devs, &compute.ContainerDevice{
  259. Type: api.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
  260. IsolatedDevice: &compute.ContainerIsolatedDevice{
  261. Index: &id0,
  262. },
  263. })
  264. } else {
  265. devs = append(devs, &compute.ContainerDevice{
  266. Type: api.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
  267. IsolatedDevice: &compute.ContainerIsolatedDevice{
  268. Index: &id0,
  269. OnlyEnv: []*api.ContainerIsolatedDeviceOnlyEnv{
  270. {
  271. Key: "WOLF_RENDER_NODE",
  272. FromRenderPath: true,
  273. },
  274. },
  275. },
  276. })
  277. }
  278. }
  279. vms := []*api.ContainerVolumeMount{
  280. /*{
  281. UniqueName: "etc-wolf",
  282. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  283. MountPath: "/etc/wolf",
  284. HostPath: &api.ContainerVolumeMountHostPath{
  285. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  286. Path: "/etc/wolf",
  287. },
  288. Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  289. },*/
  290. {
  291. UniqueName: "wolf-data",
  292. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  293. MountPath: "/etc/wolf",
  294. Disk: &api.ContainerVolumeMountDisk{
  295. Index: &zero,
  296. SubDirectory: "wolf",
  297. },
  298. },
  299. {
  300. UniqueName: "run-udev",
  301. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  302. MountPath: VOL_RUN_UDEV,
  303. HostPath: &api.ContainerVolumeMountHostPath{
  304. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  305. Path: VOL_RUN_UDEV,
  306. },
  307. //Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  308. },
  309. {
  310. UniqueName: "dev",
  311. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  312. MountPath: VOL_DEV,
  313. HostPath: &api.ContainerVolumeMountHostPath{
  314. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  315. Path: VOL_DEV,
  316. },
  317. // WARN: 这里不能用 bidirectional mount
  318. // Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  319. },
  320. {
  321. UniqueName: "tmp-sockets",
  322. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  323. MountPath: "/tmp/sockets",
  324. HostPath: &api.ContainerVolumeMountHostPath{
  325. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  326. //Path: "/tmp/sockets",
  327. Path: getTmpSocketsHostPath(i.Name),
  328. },
  329. Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  330. },
  331. // {
  332. // UniqueName: "tmp-pulse-audio",
  333. // Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  334. // MountPath: "/tmp/sockets/pulse-socket",
  335. // HostPath: &api.ContainerVolumeMountHostPath{
  336. // Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE,
  337. // Path: "/tmp/sockets/pulse-socket",
  338. // },
  339. // // Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  340. // },
  341. {
  342. UniqueName: "docker-socket",
  343. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  344. MountPath: "/var/run/docker.sock",
  345. HostPath: &api.ContainerVolumeMountHostPath{
  346. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE,
  347. Path: "/var/run/docker.sock",
  348. },
  349. },
  350. }
  351. vms = append(vms, getMounts(i.MountList)...)
  352. if i.GPU == "" {
  353. vms = append(vms,
  354. &api.ContainerVolumeMount{
  355. UniqueName: "nvidia-driver-vol",
  356. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  357. MountPath: "/usr/nvidia",
  358. HostPath: &api.ContainerVolumeMountHostPath{
  359. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  360. Path: "/var/lib/docker/volumes/nvidia-driver-vol/_data",
  361. },
  362. })
  363. }
  364. return &compute.PodContainerCreateInput{
  365. ContainerSpec: compute.ContainerSpec{
  366. ContainerSpec: api.ContainerSpec{
  367. EnableLxcfs: i.EnableLxcfs,
  368. Image: i.WolfImage,
  369. ImagePullPolicy: api.ImagePullPolicyAlways,
  370. CgroupDevicesAllow: []string{
  371. CGROUP_RULE_13,
  372. },
  373. Envs: envs,
  374. },
  375. Devices: devs,
  376. VolumeMounts: vms,
  377. },
  378. }
  379. }
  380. func NewAppSteamContainer(i CreateInput) *compute.PodContainerCreateInput {
  381. // TODO: 设置 ulimit 和 ipc host
  382. // --ipc host --ulimit nofile=10240:10240
  383. zero := 0
  384. devs := []*compute.ContainerDevice{
  385. NewHostDev(DEV_UINPUT),
  386. NewHostDev(DEV_UHID),
  387. }
  388. envs := []*api.ContainerKeyValue{
  389. NewEnv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"),
  390. NewEnv("UNAME", "retro"),
  391. NewEnv("UMASK", "000"),
  392. NewEnv("HOME", "/home/retro"),
  393. NewEnv("TZ", "Europe/London"),
  394. NewEnv("NEEDRESTART_SUSPEND", "1"),
  395. NewEnv("GAMESCOPE_VERSION", "3.15.14"),
  396. NewEnv("BUILD_ARCHITECTURE", "amd64"),
  397. NewEnv("DEBIAN_FRONTEND", "noninteractive"),
  398. NewEnv("DEB_BUILD_OPTIONS", "noddeb"),
  399. NewEnv("XDG_RUNTIME_DIR", "/tmp/sockets"),
  400. }
  401. if steamNoBigScreen {
  402. envs = append(envs, NewEnv("STEAM_STARTUP_FLAGS", "-fullscreen"))
  403. }
  404. if i.GPU == "" && i.GPUEnvId == "" && i.GPUModel == "" && i.GPUType == "" {
  405. devs = append(devs, getNvidiaAppDevs(0)...)
  406. } else if i.GPUEnvId != "" {
  407. envs = append(envs,
  408. NewEnv("NVIDIA_VISIBLE_DEVICES", i.GPUEnvId),
  409. NewEnv("NVIDIA_DRIVER_CAPABILITIES", "all"))
  410. } else {
  411. devs = append(devs, &compute.ContainerDevice{
  412. Type: api.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
  413. IsolatedDevice: &compute.ContainerIsolatedDevice{
  414. Index: &zero,
  415. },
  416. })
  417. }
  418. dataVol := &api.ContainerVolumeMount{
  419. UniqueName: "home-data",
  420. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  421. MountPath: "/home/retro",
  422. Disk: &api.ContainerVolumeMountDisk{
  423. Index: &zero,
  424. SubDirectory: "home",
  425. },
  426. }
  427. if i.Overlay != "" {
  428. overlayParts := strings.Split(i.Overlay, ":")
  429. dataVol.Disk.Overlay = &api.ContainerVolumeMountDiskOverlay{
  430. LowerDir: overlayParts,
  431. }
  432. }
  433. vols := []*api.ContainerVolumeMount{
  434. {
  435. UniqueName: "fake-udev",
  436. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  437. MountPath: "/usr/bin/fake-udev",
  438. HostPath: &api.ContainerVolumeMountHostPath{
  439. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE,
  440. Path: "/etc/wolf/fake-udev",
  441. },
  442. ReadOnly: true,
  443. },
  444. dataVol,
  445. /*{
  446. UniqueName: "home-data",
  447. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  448. MountPath: "/home/retro",
  449. HostPath: &api.ContainerVolumeMountHostPath{
  450. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  451. Path: "/etc/wolf/18046928878093460462/Steam",
  452. },
  453. },*/
  454. {
  455. UniqueName: "steam-tmp-sockets",
  456. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  457. MountPath: "/tmp/sockets",
  458. HostPath: &api.ContainerVolumeMountHostPath{
  459. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  460. Path: getTmpSocketsHostPath(i.Name),
  461. },
  462. },
  463. // {
  464. // UniqueName: "steam-tmp-pulse-audio",
  465. // Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  466. // MountPath: "/tmp/sockets/pulse-socket",
  467. // HostPath: &api.ContainerVolumeMountHostPath{
  468. // Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE,
  469. // Path: "/tmp/sockets/pulse-socket",
  470. // },
  471. // // Propagation: api.MOUNTPROPAGATION_PROPAGATION_BIDIRECTIONAL,
  472. // },
  473. {
  474. UniqueName: "steam-run-udev",
  475. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  476. MountPath: VOL_RUN_UDEV,
  477. HostPath: &api.ContainerVolumeMountHostPath{
  478. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  479. Path: "/etc/wolf/18046928878093460462/Steam/udev",
  480. },
  481. },
  482. }
  483. vols = append(vols, getMounts(i.MountList)...)
  484. vols = append(vols, getMounts(i.AppMountList)...)
  485. if i.GPU == "" || i.AlwaysMountDriverVol {
  486. vols = append(vols,
  487. &api.ContainerVolumeMount{
  488. UniqueName: "steam-nvidia-driver-vol",
  489. Type: api.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
  490. MountPath: "/usr/nvidia",
  491. HostPath: &api.ContainerVolumeMountHostPath{
  492. Type: api.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
  493. Path: "/var/lib/docker/volumes/nvidia-driver-vol/_data",
  494. },
  495. },
  496. )
  497. }
  498. return &compute.PodContainerCreateInput{
  499. ContainerSpec: compute.ContainerSpec{
  500. ContainerSpec: api.ContainerSpec{
  501. AlwaysRestart: true,
  502. EnableLxcfs: i.EnableLxcfs,
  503. Image: i.SteamImage,
  504. ImagePullPolicy: api.ImagePullPolicyAlways,
  505. Command: []string{"/opt/bin/wolf-hook",
  506. "-addr", "127.0.0.1",
  507. "-ulimit-nofile-hard", "524288",
  508. "-ulimit-nofile-soft", "524288",
  509. },
  510. CgroupDevicesAllow: []string{
  511. CGROUP_RULE_13,
  512. CGROUP_RULE_244,
  513. },
  514. Envs: envs,
  515. Capabilities: &api.ContainerCapability{
  516. Add: []string{"SYS_ADMIN", "SYS_NICE", "SYS_PTRACE", "NET_RAW", "MKNOD", "NET_ADMIN"},
  517. },
  518. },
  519. Devices: devs,
  520. VolumeMounts: vols,
  521. },
  522. }
  523. }
  524. func HTTPSPort(basePort int) int {
  525. return basePort
  526. }
  527. func HTTPPort(basePort int) int {
  528. return basePort + 5
  529. }
  530. func ControlUDPPort(basePort int) int {
  531. return basePort + 15
  532. }
  533. func VideoUDPPingPort(basePort int) int {
  534. return basePort + 116
  535. }
  536. func AudioUDPPingPort(basePort int) int {
  537. return basePort + 216
  538. }
  539. func RTSPTCPSetupPort(basePort int) int {
  540. return basePort + 26
  541. }
  542. func getPortEnvs(basePort int) []*api.ContainerKeyValue {
  543. httpsPort := HTTPSPort(basePort)
  544. httpPort := HTTPPort(basePort)
  545. controlUDPPort := ControlUDPPort(basePort)
  546. videoUDPPingPort := VideoUDPPingPort(basePort)
  547. audioUDPPingPort := AudioUDPPingPort(basePort)
  548. rtspTCPSetupPort := RTSPTCPSetupPort(basePort)
  549. newV := func(key string, port int) *api.ContainerKeyValue {
  550. return &api.ContainerKeyValue{
  551. Key: key,
  552. Value: fmt.Sprintf("%d", port),
  553. }
  554. }
  555. return []*api.ContainerKeyValue{
  556. newV("WOLF_HTTP_PORT", httpPort),
  557. newV("WOLF_HTTPS_PORT", httpsPort),
  558. newV("WOLF_CONTROL_PORT", controlUDPPort),
  559. newV("WOLF_RTSP_SETUP_PORT", rtspTCPSetupPort),
  560. newV("WOLF_VIDEO_PING_PORT", videoUDPPingPort),
  561. newV("WOLF_AUDIO_PING_PORT", audioUDPPingPort),
  562. }
  563. }
  564. func getPortMappings(basePort int) compute.GuestPortMappings {
  565. httpsPort := HTTPSPort(basePort)
  566. httpPort := HTTPPort(basePort)
  567. controlUDPPort := ControlUDPPort(basePort)
  568. videoUDPPingPort := VideoUDPPingPort(basePort)
  569. audioUDPPingPort := AudioUDPPingPort(basePort)
  570. rtspTCPSetupPort := RTSPTCPSetupPort(basePort)
  571. return compute.GuestPortMappings{
  572. {
  573. // HTTPS
  574. Protocol: "tcp",
  575. Port: httpsPort,
  576. HostPort: &httpsPort,
  577. },
  578. {
  579. // HTTP
  580. Protocol: "tcp",
  581. Port: httpPort,
  582. HostPort: &httpPort,
  583. },
  584. {
  585. // Control UDP
  586. Protocol: "udp",
  587. Port: controlUDPPort,
  588. HostPort: &controlUDPPort,
  589. },
  590. {
  591. // Video UDP Ping
  592. Protocol: "udp",
  593. Port: videoUDPPingPort,
  594. HostPort: &videoUDPPingPort,
  595. },
  596. {
  597. // Audio UDP Ping
  598. Protocol: "udp",
  599. Port: audioUDPPingPort,
  600. HostPort: &audioUDPPingPort,
  601. },
  602. {
  603. // RTSP TCP Setup
  604. Protocol: "tcp",
  605. Port: rtspTCPSetupPort,
  606. HostPort: &rtspTCPSetupPort,
  607. },
  608. }
  609. }
  610. type CreateInput struct {
  611. Name string
  612. BasePort int
  613. NCPU int
  614. MemGB int
  615. DiskSizeGB int
  616. Network string
  617. IP string
  618. GPU string
  619. GPUType string
  620. GPUModel string
  621. GPUEnvId string
  622. EnableLxcfs bool
  623. WolfImage string
  624. WolfAllGpu bool
  625. MountList []string
  626. AppMountList []string
  627. // RenderNode string
  628. ExternalIP string
  629. Overlay string
  630. AlwaysMountDriverVol bool
  631. SteamImage string
  632. }
  633. func GetCreateParams(i CreateInput) *compute.ServerCreateInput {
  634. input := &compute.ServerCreateInput{
  635. ServerConfigs: &compute.ServerConfigs{
  636. Hypervisor: compute.HYPERVISOR_POD,
  637. },
  638. }
  639. input.Name = i.Name
  640. input.VcpuCount = i.NCPU
  641. input.VmemSize = i.MemGB * 1024
  642. fv := false
  643. input.DisableDelete = &fv
  644. input.AutoStart = true
  645. input.Disks = []*compute.DiskConfig{
  646. {
  647. SizeMb: i.DiskSizeGB * 1024,
  648. Format: "raw",
  649. Fs: "ext4",
  650. },
  651. }
  652. net := &compute.NetworkConfig{
  653. Network: i.Network,
  654. Address: i.IP,
  655. PortMappings: getPortMappings(i.BasePort),
  656. }
  657. input.Networks = []*compute.NetworkConfig{net}
  658. input.Pod = &compute.PodCreateInput{
  659. HostIPC: true,
  660. Containers: []*compute.PodContainerCreateInput{
  661. NewPulseAudioContainer(i.Name, i.EnableLxcfs),
  662. NewWolfContainer(i),
  663. NewAppSteamContainer(i),
  664. },
  665. }
  666. if i.GPU != "" || i.GPUModel != "" || i.GPUType != "" {
  667. input.IsolatedDevices = []*compute.IsolatedDeviceConfig{
  668. {
  669. Id: i.GPU,
  670. DevType: i.GPUType,
  671. Model: i.GPUModel,
  672. },
  673. }
  674. }
  675. return input
  676. }
  677. func getSession() *mcclient.ClientSession {
  678. client := mcclient.NewClient(authUrl, 60, true, true, "", "")
  679. token, err := client.Authenticate(user, password, "Default", "system", "Default")
  680. if err != nil {
  681. log.Fatalf("Failed to authenticate: %v", err)
  682. }
  683. s := client.NewSession(context.Background(), region, "", "publicURL", token)
  684. return s
  685. }
  686. func main() {
  687. s := getSession()
  688. input := CreateInput{
  689. Name: podName,
  690. BasePort: basePort,
  691. NCPU: ncpu,
  692. MemGB: mem,
  693. DiskSizeGB: diskSizeGB,
  694. Network: podNet,
  695. IP: podIP,
  696. GPU: gpu,
  697. GPUEnvId: gpuEnvId,
  698. GPUModel: gpuModel,
  699. GPUType: gpuType,
  700. EnableLxcfs: enableLxcfs,
  701. WolfImage: wolfImage,
  702. WolfAllGpu: wolfAllGpu,
  703. MountList: mountList,
  704. AppMountList: appMountList,
  705. // RenderNode: renderNode,
  706. ExternalIP: externalIP,
  707. Overlay: overlay,
  708. AlwaysMountDriverVol: alwaysMountDriverVol,
  709. SteamImage: steamImage,
  710. }
  711. obj, err := modules.Servers.Create(s, jsonutils.Marshal(GetCreateParams(input)))
  712. if err != nil {
  713. log.Errorf("Failed to create server: %v", err)
  714. return
  715. }
  716. log.Infof("Created server: %v", obj.PrettyString())
  717. hostId := ""
  718. var srvDetails *compute.ServerDetails
  719. for hostId == "" {
  720. time.Sleep(1 * time.Second)
  721. id, _ := obj.GetString("id")
  722. log.Infof("Waiting for server to be scheduled to host...")
  723. obj, err = modules.Servers.Get(s, id, nil)
  724. if err != nil {
  725. log.Errorf("Failed to get server: %v", err)
  726. return
  727. }
  728. guestDetails := &compute.ServerDetails{}
  729. if err := obj.Unmarshal(guestDetails); err != nil {
  730. log.Errorf("Failed to unmarshal guest details: %v", err)
  731. return
  732. }
  733. for _, failMsg := range []string{"failed", "fail"} {
  734. if strings.Contains(guestDetails.Status, failMsg) {
  735. log.Errorf("Server creation failed: %s", guestDetails.Status)
  736. return
  737. }
  738. }
  739. hostId = guestDetails.HostId
  740. srvDetails = guestDetails
  741. }
  742. accessIp := srvDetails.HostEIP
  743. if accessIp == "" {
  744. accessIp = srvDetails.HostAccessIp
  745. }
  746. if accessIp == "" {
  747. accessIp = srvDetails.IPs
  748. }
  749. log.Infof("Access URL: %s:%d , port_mappings: %s", accessIp, accessPort, getPortMappings(input.BasePort).String())
  750. }