system.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. //go:build windows
  2. package hcs
  3. import (
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "sync"
  10. "syscall"
  11. "time"
  12. "github.com/Microsoft/hcsshim/internal/cow"
  13. "github.com/Microsoft/hcsshim/internal/hcs/schema1"
  14. hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
  15. "github.com/Microsoft/hcsshim/internal/jobobject"
  16. "github.com/Microsoft/hcsshim/internal/log"
  17. "github.com/Microsoft/hcsshim/internal/logfields"
  18. "github.com/Microsoft/hcsshim/internal/oc"
  19. "github.com/Microsoft/hcsshim/internal/timeout"
  20. "github.com/Microsoft/hcsshim/internal/vmcompute"
  21. "github.com/sirupsen/logrus"
  22. "go.opencensus.io/trace"
  23. )
  24. type System struct {
  25. handleLock sync.RWMutex
  26. handle vmcompute.HcsSystem
  27. id string
  28. callbackNumber uintptr
  29. closedWaitOnce sync.Once
  30. waitBlock chan struct{}
  31. waitError error
  32. exitError error
  33. os, typ, owner string
  34. startTime time.Time
  35. }
  36. var _ cow.Container = &System{}
  37. var _ cow.ProcessHost = &System{}
  38. func newSystem(id string) *System {
  39. return &System{
  40. id: id,
  41. waitBlock: make(chan struct{}),
  42. }
  43. }
  44. // Implementation detail for silo naming, this should NOT be relied upon very heavily.
  45. func siloNameFmt(containerID string) string {
  46. return fmt.Sprintf(`\Container_%s`, containerID)
  47. }
  48. // CreateComputeSystem creates a new compute system with the given configuration but does not start it.
  49. func CreateComputeSystem(ctx context.Context, id string, hcsDocumentInterface interface{}) (_ *System, err error) {
  50. operation := "hcs::CreateComputeSystem"
  51. // hcsCreateComputeSystemContext is an async operation. Start the outer span
  52. // here to measure the full create time.
  53. ctx, span := oc.StartSpan(ctx, operation)
  54. defer span.End()
  55. defer func() { oc.SetSpanStatus(span, err) }()
  56. span.AddAttributes(trace.StringAttribute("cid", id))
  57. computeSystem := newSystem(id)
  58. hcsDocumentB, err := json.Marshal(hcsDocumentInterface)
  59. if err != nil {
  60. return nil, err
  61. }
  62. hcsDocument := string(hcsDocumentB)
  63. var (
  64. identity syscall.Handle
  65. resultJSON string
  66. createError error
  67. )
  68. computeSystem.handle, resultJSON, createError = vmcompute.HcsCreateComputeSystem(ctx, id, hcsDocument, identity)
  69. if createError == nil || IsPending(createError) {
  70. defer func() {
  71. if err != nil {
  72. computeSystem.Close()
  73. }
  74. }()
  75. if err = computeSystem.registerCallback(ctx); err != nil {
  76. // Terminate the compute system if it still exists. We're okay to
  77. // ignore a failure here.
  78. _ = computeSystem.Terminate(ctx)
  79. return nil, makeSystemError(computeSystem, operation, err, nil)
  80. }
  81. }
  82. events, err := processAsyncHcsResult(ctx, createError, resultJSON, computeSystem.callbackNumber,
  83. hcsNotificationSystemCreateCompleted, &timeout.SystemCreate)
  84. if err != nil {
  85. if err == ErrTimeout {
  86. // Terminate the compute system if it still exists. We're okay to
  87. // ignore a failure here.
  88. _ = computeSystem.Terminate(ctx)
  89. }
  90. return nil, makeSystemError(computeSystem, operation, err, events)
  91. }
  92. go computeSystem.waitBackground()
  93. if err = computeSystem.getCachedProperties(ctx); err != nil {
  94. return nil, err
  95. }
  96. return computeSystem, nil
  97. }
  98. // OpenComputeSystem opens an existing compute system by ID.
  99. func OpenComputeSystem(ctx context.Context, id string) (*System, error) {
  100. operation := "hcs::OpenComputeSystem"
  101. computeSystem := newSystem(id)
  102. handle, resultJSON, err := vmcompute.HcsOpenComputeSystem(ctx, id)
  103. events := processHcsResult(ctx, resultJSON)
  104. if err != nil {
  105. return nil, makeSystemError(computeSystem, operation, err, events)
  106. }
  107. computeSystem.handle = handle
  108. defer func() {
  109. if err != nil {
  110. computeSystem.Close()
  111. }
  112. }()
  113. if err = computeSystem.registerCallback(ctx); err != nil {
  114. return nil, makeSystemError(computeSystem, operation, err, nil)
  115. }
  116. go computeSystem.waitBackground()
  117. if err = computeSystem.getCachedProperties(ctx); err != nil {
  118. return nil, err
  119. }
  120. return computeSystem, nil
  121. }
  122. func (computeSystem *System) getCachedProperties(ctx context.Context) error {
  123. props, err := computeSystem.Properties(ctx)
  124. if err != nil {
  125. return err
  126. }
  127. computeSystem.typ = strings.ToLower(props.SystemType)
  128. computeSystem.os = strings.ToLower(props.RuntimeOSType)
  129. computeSystem.owner = strings.ToLower(props.Owner)
  130. if computeSystem.os == "" && computeSystem.typ == "container" {
  131. // Pre-RS5 HCS did not return the OS, but it only supported containers
  132. // that ran Windows.
  133. computeSystem.os = "windows"
  134. }
  135. return nil
  136. }
  137. // OS returns the operating system of the compute system, "linux" or "windows".
  138. func (computeSystem *System) OS() string {
  139. return computeSystem.os
  140. }
  141. // IsOCI returns whether processes in the compute system should be created via
  142. // OCI.
  143. func (computeSystem *System) IsOCI() bool {
  144. return computeSystem.os == "linux" && computeSystem.typ == "container"
  145. }
  146. // GetComputeSystems gets a list of the compute systems on the system that match the query
  147. func GetComputeSystems(ctx context.Context, q schema1.ComputeSystemQuery) ([]schema1.ContainerProperties, error) {
  148. operation := "hcs::GetComputeSystems"
  149. queryb, err := json.Marshal(q)
  150. if err != nil {
  151. return nil, err
  152. }
  153. computeSystemsJSON, resultJSON, err := vmcompute.HcsEnumerateComputeSystems(ctx, string(queryb))
  154. events := processHcsResult(ctx, resultJSON)
  155. if err != nil {
  156. return nil, &HcsError{Op: operation, Err: err, Events: events}
  157. }
  158. if computeSystemsJSON == "" {
  159. return nil, ErrUnexpectedValue
  160. }
  161. computeSystems := []schema1.ContainerProperties{}
  162. if err = json.Unmarshal([]byte(computeSystemsJSON), &computeSystems); err != nil {
  163. return nil, err
  164. }
  165. return computeSystems, nil
  166. }
  167. // Start synchronously starts the computeSystem.
  168. func (computeSystem *System) Start(ctx context.Context) (err error) {
  169. operation := "hcs::System::Start"
  170. // hcsStartComputeSystemContext is an async operation. Start the outer span
  171. // here to measure the full start time.
  172. ctx, span := oc.StartSpan(ctx, operation)
  173. defer span.End()
  174. defer func() { oc.SetSpanStatus(span, err) }()
  175. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  176. computeSystem.handleLock.RLock()
  177. defer computeSystem.handleLock.RUnlock()
  178. // prevent starting an exited system because waitblock we do not recreate waitBlock
  179. // or rerun waitBackground, so we have no way to be notified of it closing again
  180. if computeSystem.handle == 0 {
  181. return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  182. }
  183. resultJSON, err := vmcompute.HcsStartComputeSystem(ctx, computeSystem.handle, "")
  184. events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber,
  185. hcsNotificationSystemStartCompleted, &timeout.SystemStart)
  186. if err != nil {
  187. return makeSystemError(computeSystem, operation, err, events)
  188. }
  189. computeSystem.startTime = time.Now()
  190. return nil
  191. }
  192. // ID returns the compute system's identifier.
  193. func (computeSystem *System) ID() string {
  194. return computeSystem.id
  195. }
  196. // Shutdown requests a compute system shutdown.
  197. func (computeSystem *System) Shutdown(ctx context.Context) error {
  198. computeSystem.handleLock.RLock()
  199. defer computeSystem.handleLock.RUnlock()
  200. operation := "hcs::System::Shutdown"
  201. if computeSystem.handle == 0 || computeSystem.stopped() {
  202. return nil
  203. }
  204. resultJSON, err := vmcompute.HcsShutdownComputeSystem(ctx, computeSystem.handle, "")
  205. events := processHcsResult(ctx, resultJSON)
  206. switch err {
  207. case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending:
  208. default:
  209. return makeSystemError(computeSystem, operation, err, events)
  210. }
  211. return nil
  212. }
  213. // Terminate requests a compute system terminate.
  214. func (computeSystem *System) Terminate(ctx context.Context) error {
  215. computeSystem.handleLock.RLock()
  216. defer computeSystem.handleLock.RUnlock()
  217. operation := "hcs::System::Terminate"
  218. if computeSystem.handle == 0 || computeSystem.stopped() {
  219. return nil
  220. }
  221. resultJSON, err := vmcompute.HcsTerminateComputeSystem(ctx, computeSystem.handle, "")
  222. events := processHcsResult(ctx, resultJSON)
  223. switch err {
  224. case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending:
  225. default:
  226. return makeSystemError(computeSystem, operation, err, events)
  227. }
  228. return nil
  229. }
  230. // waitBackground waits for the compute system exit notification. Once received
  231. // sets `computeSystem.waitError` (if any) and unblocks all `Wait` calls.
  232. //
  233. // This MUST be called exactly once per `computeSystem.handle` but `Wait` is
  234. // safe to call multiple times.
  235. func (computeSystem *System) waitBackground() {
  236. operation := "hcs::System::waitBackground"
  237. ctx, span := oc.StartSpan(context.Background(), operation)
  238. defer span.End()
  239. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  240. err := waitForNotification(ctx, computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
  241. switch err {
  242. case nil:
  243. log.G(ctx).Debug("system exited")
  244. case ErrVmcomputeUnexpectedExit:
  245. log.G(ctx).Debug("unexpected system exit")
  246. computeSystem.exitError = makeSystemError(computeSystem, operation, err, nil)
  247. err = nil
  248. default:
  249. err = makeSystemError(computeSystem, operation, err, nil)
  250. }
  251. computeSystem.closedWaitOnce.Do(func() {
  252. computeSystem.waitError = err
  253. close(computeSystem.waitBlock)
  254. })
  255. oc.SetSpanStatus(span, err)
  256. }
  257. func (computeSystem *System) WaitChannel() <-chan struct{} {
  258. return computeSystem.waitBlock
  259. }
  260. func (computeSystem *System) WaitError() error {
  261. return computeSystem.waitError
  262. }
  263. // Wait synchronously waits for the compute system to shutdown or terminate. If
  264. // the compute system has already exited returns the previous error (if any).
  265. func (computeSystem *System) Wait() error {
  266. <-computeSystem.WaitChannel()
  267. return computeSystem.WaitError()
  268. }
  269. // stopped returns true if the compute system stopped.
  270. func (computeSystem *System) stopped() bool {
  271. select {
  272. case <-computeSystem.waitBlock:
  273. return true
  274. default:
  275. }
  276. return false
  277. }
  278. // ExitError returns an error describing the reason the compute system terminated.
  279. func (computeSystem *System) ExitError() error {
  280. if !computeSystem.stopped() {
  281. return errors.New("container not exited")
  282. }
  283. if computeSystem.waitError != nil {
  284. return computeSystem.waitError
  285. }
  286. return computeSystem.exitError
  287. }
  288. // Properties returns the requested container properties targeting a V1 schema container.
  289. func (computeSystem *System) Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error) {
  290. computeSystem.handleLock.RLock()
  291. defer computeSystem.handleLock.RUnlock()
  292. operation := "hcs::System::Properties"
  293. if computeSystem.handle == 0 {
  294. return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  295. }
  296. queryBytes, err := json.Marshal(schema1.PropertyQuery{PropertyTypes: types})
  297. if err != nil {
  298. return nil, makeSystemError(computeSystem, operation, err, nil)
  299. }
  300. propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes))
  301. events := processHcsResult(ctx, resultJSON)
  302. if err != nil {
  303. return nil, makeSystemError(computeSystem, operation, err, events)
  304. }
  305. if propertiesJSON == "" {
  306. return nil, ErrUnexpectedValue
  307. }
  308. properties := &schema1.ContainerProperties{}
  309. if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil {
  310. return nil, makeSystemError(computeSystem, operation, err, nil)
  311. }
  312. return properties, nil
  313. }
  314. // queryInProc handles querying for container properties without reaching out to HCS. `props`
  315. // will be updated to contain any data returned from the queries present in `types`. If any properties
  316. // failed to be queried they will be tallied up and returned in as the first return value. Failures on
  317. // query are NOT considered errors; the only failure case for this method is if the containers job object
  318. // cannot be opened.
  319. func (computeSystem *System) queryInProc(
  320. ctx context.Context,
  321. props *hcsschema.Properties,
  322. types []hcsschema.PropertyType,
  323. ) ([]hcsschema.PropertyType, error) {
  324. // In the future we can make use of some new functionality in the HCS that allows you
  325. // to pass a job object for HCS to use for the container. Currently, the only way we'll
  326. // be able to open the job/silo is if we're running as SYSTEM.
  327. jobOptions := &jobobject.Options{
  328. UseNTVariant: true,
  329. Name: siloNameFmt(computeSystem.id),
  330. }
  331. job, err := jobobject.Open(ctx, jobOptions)
  332. if err != nil {
  333. return nil, err
  334. }
  335. defer job.Close()
  336. var fallbackQueryTypes []hcsschema.PropertyType
  337. for _, propType := range types {
  338. switch propType {
  339. case hcsschema.PTStatistics:
  340. // Handle a bad caller asking for the same type twice. No use in re-querying if this is
  341. // filled in already.
  342. if props.Statistics == nil {
  343. props.Statistics, err = computeSystem.statisticsInProc(job)
  344. if err != nil {
  345. log.G(ctx).WithError(err).Warn("failed to get statistics in-proc")
  346. fallbackQueryTypes = append(fallbackQueryTypes, propType)
  347. }
  348. }
  349. default:
  350. fallbackQueryTypes = append(fallbackQueryTypes, propType)
  351. }
  352. }
  353. return fallbackQueryTypes, nil
  354. }
  355. // statisticsInProc emulates what HCS does to grab statistics for a given container with a small
  356. // change to make grabbing the private working set total much more efficient.
  357. func (computeSystem *System) statisticsInProc(job *jobobject.JobObject) (*hcsschema.Statistics, error) {
  358. // Start timestamp for these stats before we grab them to match HCS
  359. timestamp := time.Now()
  360. memInfo, err := job.QueryMemoryStats()
  361. if err != nil {
  362. return nil, err
  363. }
  364. processorInfo, err := job.QueryProcessorStats()
  365. if err != nil {
  366. return nil, err
  367. }
  368. storageInfo, err := job.QueryStorageStats()
  369. if err != nil {
  370. return nil, err
  371. }
  372. // This calculates the private working set more efficiently than HCS does. HCS calls NtQuerySystemInformation
  373. // with the class SystemProcessInformation which returns an array containing system information for *every*
  374. // process running on the machine. They then grab the pids that are running in the container and filter down
  375. // the entries in the array to only what's running in that silo and start tallying up the total. This doesn't
  376. // work well as performance should get worse if more processess are running on the machine in general and not
  377. // just in the container. All of the additional information besides the WorkingSetPrivateSize field is ignored
  378. // as well which isn't great and is wasted work to fetch.
  379. //
  380. // HCS only let's you grab statistics in an all or nothing fashion, so we can't just grab the private
  381. // working set ourselves and ask for everything else separately. The optimization we can make here is
  382. // to open the silo ourselves and do the same queries for the rest of the info, as well as calculating
  383. // the private working set in a more efficient manner by:
  384. //
  385. // 1. Find the pids running in the silo
  386. // 2. Get a process handle for every process (only need PROCESS_QUERY_LIMITED_INFORMATION access)
  387. // 3. Call NtQueryInformationProcess on each process with the class ProcessVmCounters
  388. // 4. Tally up the total using the field PrivateWorkingSetSize in VM_COUNTERS_EX2.
  389. privateWorkingSet, err := job.QueryPrivateWorkingSet()
  390. if err != nil {
  391. return nil, err
  392. }
  393. return &hcsschema.Statistics{
  394. Timestamp: timestamp,
  395. ContainerStartTime: computeSystem.startTime,
  396. Uptime100ns: uint64(time.Since(computeSystem.startTime).Nanoseconds()) / 100,
  397. Memory: &hcsschema.MemoryStats{
  398. MemoryUsageCommitBytes: memInfo.JobMemory,
  399. MemoryUsageCommitPeakBytes: memInfo.PeakJobMemoryUsed,
  400. MemoryUsagePrivateWorkingSetBytes: privateWorkingSet,
  401. },
  402. Processor: &hcsschema.ProcessorStats{
  403. RuntimeKernel100ns: uint64(processorInfo.TotalKernelTime),
  404. RuntimeUser100ns: uint64(processorInfo.TotalUserTime),
  405. TotalRuntime100ns: uint64(processorInfo.TotalKernelTime + processorInfo.TotalUserTime),
  406. },
  407. Storage: &hcsschema.StorageStats{
  408. ReadCountNormalized: uint64(storageInfo.ReadStats.IoCount),
  409. ReadSizeBytes: storageInfo.ReadStats.TotalSize,
  410. WriteCountNormalized: uint64(storageInfo.WriteStats.IoCount),
  411. WriteSizeBytes: storageInfo.WriteStats.TotalSize,
  412. },
  413. }, nil
  414. }
  415. // hcsPropertiesV2Query is a helper to make a HcsGetComputeSystemProperties call using the V2 schema property types.
  416. func (computeSystem *System) hcsPropertiesV2Query(ctx context.Context, types []hcsschema.PropertyType) (*hcsschema.Properties, error) {
  417. operation := "hcs::System::PropertiesV2"
  418. if computeSystem.handle == 0 {
  419. return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  420. }
  421. queryBytes, err := json.Marshal(hcsschema.PropertyQuery{PropertyTypes: types})
  422. if err != nil {
  423. return nil, makeSystemError(computeSystem, operation, err, nil)
  424. }
  425. propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes))
  426. events := processHcsResult(ctx, resultJSON)
  427. if err != nil {
  428. return nil, makeSystemError(computeSystem, operation, err, events)
  429. }
  430. if propertiesJSON == "" {
  431. return nil, ErrUnexpectedValue
  432. }
  433. props := &hcsschema.Properties{}
  434. if err := json.Unmarshal([]byte(propertiesJSON), props); err != nil {
  435. return nil, makeSystemError(computeSystem, operation, err, nil)
  436. }
  437. return props, nil
  438. }
  439. // PropertiesV2 returns the requested compute systems properties targeting a V2 schema compute system.
  440. func (computeSystem *System) PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (_ *hcsschema.Properties, err error) {
  441. computeSystem.handleLock.RLock()
  442. defer computeSystem.handleLock.RUnlock()
  443. // Let HCS tally up the total for VM based queries instead of querying ourselves.
  444. if computeSystem.typ != "container" {
  445. return computeSystem.hcsPropertiesV2Query(ctx, types)
  446. }
  447. // Define a starter Properties struct with the default fields returned from every
  448. // query. Owner is only returned from Statistics but it's harmless to include.
  449. properties := &hcsschema.Properties{
  450. Id: computeSystem.id,
  451. SystemType: computeSystem.typ,
  452. RuntimeOsType: computeSystem.os,
  453. Owner: computeSystem.owner,
  454. }
  455. logEntry := log.G(ctx)
  456. // First lets try and query ourselves without reaching to HCS. If any of the queries fail
  457. // we'll take note and fallback to querying HCS for any of the failed types.
  458. fallbackTypes, err := computeSystem.queryInProc(ctx, properties, types)
  459. if err == nil && len(fallbackTypes) == 0 {
  460. return properties, nil
  461. } else if err != nil {
  462. logEntry = logEntry.WithError(fmt.Errorf("failed to query compute system properties in-proc: %w", err))
  463. fallbackTypes = types
  464. }
  465. logEntry.WithFields(logrus.Fields{
  466. logfields.ContainerID: computeSystem.id,
  467. "propertyTypes": fallbackTypes,
  468. }).Info("falling back to HCS for property type queries")
  469. hcsProperties, err := computeSystem.hcsPropertiesV2Query(ctx, fallbackTypes)
  470. if err != nil {
  471. return nil, err
  472. }
  473. // Now add in anything that we might have successfully queried in process.
  474. if properties.Statistics != nil {
  475. hcsProperties.Statistics = properties.Statistics
  476. hcsProperties.Owner = properties.Owner
  477. }
  478. // For future support for querying processlist in-proc as well.
  479. if properties.ProcessList != nil {
  480. hcsProperties.ProcessList = properties.ProcessList
  481. }
  482. return hcsProperties, nil
  483. }
  484. // Pause pauses the execution of the computeSystem. This feature is not enabled in TP5.
  485. func (computeSystem *System) Pause(ctx context.Context) (err error) {
  486. operation := "hcs::System::Pause"
  487. // hcsPauseComputeSystemContext is an async operation. Start the outer span
  488. // here to measure the full pause time.
  489. ctx, span := oc.StartSpan(ctx, operation)
  490. defer span.End()
  491. defer func() { oc.SetSpanStatus(span, err) }()
  492. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  493. computeSystem.handleLock.RLock()
  494. defer computeSystem.handleLock.RUnlock()
  495. if computeSystem.handle == 0 {
  496. return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  497. }
  498. resultJSON, err := vmcompute.HcsPauseComputeSystem(ctx, computeSystem.handle, "")
  499. events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber,
  500. hcsNotificationSystemPauseCompleted, &timeout.SystemPause)
  501. if err != nil {
  502. return makeSystemError(computeSystem, operation, err, events)
  503. }
  504. return nil
  505. }
  506. // Resume resumes the execution of the computeSystem. This feature is not enabled in TP5.
  507. func (computeSystem *System) Resume(ctx context.Context) (err error) {
  508. operation := "hcs::System::Resume"
  509. // hcsResumeComputeSystemContext is an async operation. Start the outer span
  510. // here to measure the full restore time.
  511. ctx, span := oc.StartSpan(ctx, operation)
  512. defer span.End()
  513. defer func() { oc.SetSpanStatus(span, err) }()
  514. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  515. computeSystem.handleLock.RLock()
  516. defer computeSystem.handleLock.RUnlock()
  517. if computeSystem.handle == 0 {
  518. return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  519. }
  520. resultJSON, err := vmcompute.HcsResumeComputeSystem(ctx, computeSystem.handle, "")
  521. events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber,
  522. hcsNotificationSystemResumeCompleted, &timeout.SystemResume)
  523. if err != nil {
  524. return makeSystemError(computeSystem, operation, err, events)
  525. }
  526. return nil
  527. }
  528. // Save the compute system
  529. func (computeSystem *System) Save(ctx context.Context, options interface{}) (err error) {
  530. operation := "hcs::System::Save"
  531. // hcsSaveComputeSystemContext is an async operation. Start the outer span
  532. // here to measure the full save time.
  533. ctx, span := oc.StartSpan(ctx, operation)
  534. defer span.End()
  535. defer func() { oc.SetSpanStatus(span, err) }()
  536. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  537. saveOptions, err := json.Marshal(options)
  538. if err != nil {
  539. return err
  540. }
  541. computeSystem.handleLock.RLock()
  542. defer computeSystem.handleLock.RUnlock()
  543. if computeSystem.handle == 0 {
  544. return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  545. }
  546. result, err := vmcompute.HcsSaveComputeSystem(ctx, computeSystem.handle, string(saveOptions))
  547. events, err := processAsyncHcsResult(ctx, err, result, computeSystem.callbackNumber,
  548. hcsNotificationSystemSaveCompleted, &timeout.SystemSave)
  549. if err != nil {
  550. return makeSystemError(computeSystem, operation, err, events)
  551. }
  552. return nil
  553. }
  554. func (computeSystem *System) createProcess(ctx context.Context, operation string, c interface{}) (*Process, *vmcompute.HcsProcessInformation, error) {
  555. computeSystem.handleLock.RLock()
  556. defer computeSystem.handleLock.RUnlock()
  557. if computeSystem.handle == 0 {
  558. return nil, nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  559. }
  560. configurationb, err := json.Marshal(c)
  561. if err != nil {
  562. return nil, nil, makeSystemError(computeSystem, operation, err, nil)
  563. }
  564. configuration := string(configurationb)
  565. processInfo, processHandle, resultJSON, err := vmcompute.HcsCreateProcess(ctx, computeSystem.handle, configuration)
  566. events := processHcsResult(ctx, resultJSON)
  567. if err != nil {
  568. if v2, ok := c.(*hcsschema.ProcessParameters); ok {
  569. operation += ": " + v2.CommandLine
  570. } else if v1, ok := c.(*schema1.ProcessConfig); ok {
  571. operation += ": " + v1.CommandLine
  572. }
  573. return nil, nil, makeSystemError(computeSystem, operation, err, events)
  574. }
  575. log.G(ctx).WithField("pid", processInfo.ProcessId).Debug("created process pid")
  576. return newProcess(processHandle, int(processInfo.ProcessId), computeSystem), &processInfo, nil
  577. }
  578. // CreateProcess launches a new process within the computeSystem.
  579. func (computeSystem *System) CreateProcess(ctx context.Context, c interface{}) (cow.Process, error) {
  580. operation := "hcs::System::CreateProcess"
  581. process, processInfo, err := computeSystem.createProcess(ctx, operation, c)
  582. if err != nil {
  583. return nil, err
  584. }
  585. defer func() {
  586. if err != nil {
  587. process.Close()
  588. }
  589. }()
  590. pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
  591. if err != nil {
  592. return nil, makeSystemError(computeSystem, operation, err, nil)
  593. }
  594. process.stdin = pipes[0]
  595. process.stdout = pipes[1]
  596. process.stderr = pipes[2]
  597. process.hasCachedStdio = true
  598. if err = process.registerCallback(ctx); err != nil {
  599. return nil, makeSystemError(computeSystem, operation, err, nil)
  600. }
  601. go process.waitBackground()
  602. return process, nil
  603. }
  604. // OpenProcess gets an interface to an existing process within the computeSystem.
  605. func (computeSystem *System) OpenProcess(ctx context.Context, pid int) (*Process, error) {
  606. computeSystem.handleLock.RLock()
  607. defer computeSystem.handleLock.RUnlock()
  608. operation := "hcs::System::OpenProcess"
  609. if computeSystem.handle == 0 {
  610. return nil, makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  611. }
  612. processHandle, resultJSON, err := vmcompute.HcsOpenProcess(ctx, computeSystem.handle, uint32(pid))
  613. events := processHcsResult(ctx, resultJSON)
  614. if err != nil {
  615. return nil, makeSystemError(computeSystem, operation, err, events)
  616. }
  617. process := newProcess(processHandle, pid, computeSystem)
  618. if err = process.registerCallback(ctx); err != nil {
  619. return nil, makeSystemError(computeSystem, operation, err, nil)
  620. }
  621. go process.waitBackground()
  622. return process, nil
  623. }
  624. // Close cleans up any state associated with the compute system but does not terminate or wait for it.
  625. func (computeSystem *System) Close() (err error) {
  626. operation := "hcs::System::Close"
  627. ctx, span := oc.StartSpan(context.Background(), operation)
  628. defer span.End()
  629. defer func() { oc.SetSpanStatus(span, err) }()
  630. span.AddAttributes(trace.StringAttribute("cid", computeSystem.id))
  631. computeSystem.handleLock.Lock()
  632. defer computeSystem.handleLock.Unlock()
  633. // Don't double free this
  634. if computeSystem.handle == 0 {
  635. return nil
  636. }
  637. if err = computeSystem.unregisterCallback(ctx); err != nil {
  638. return makeSystemError(computeSystem, operation, err, nil)
  639. }
  640. err = vmcompute.HcsCloseComputeSystem(ctx, computeSystem.handle)
  641. if err != nil {
  642. return makeSystemError(computeSystem, operation, err, nil)
  643. }
  644. computeSystem.handle = 0
  645. computeSystem.closedWaitOnce.Do(func() {
  646. computeSystem.waitError = ErrAlreadyClosed
  647. close(computeSystem.waitBlock)
  648. })
  649. return nil
  650. }
  651. func (computeSystem *System) registerCallback(ctx context.Context) error {
  652. callbackContext := &notificationWatcherContext{
  653. channels: newSystemChannels(),
  654. systemID: computeSystem.id,
  655. }
  656. callbackMapLock.Lock()
  657. callbackNumber := nextCallback
  658. nextCallback++
  659. callbackMap[callbackNumber] = callbackContext
  660. callbackMapLock.Unlock()
  661. callbackHandle, err := vmcompute.HcsRegisterComputeSystemCallback(ctx, computeSystem.handle,
  662. notificationWatcherCallback, callbackNumber)
  663. if err != nil {
  664. return err
  665. }
  666. callbackContext.handle = callbackHandle
  667. computeSystem.callbackNumber = callbackNumber
  668. return nil
  669. }
  670. func (computeSystem *System) unregisterCallback(ctx context.Context) error {
  671. callbackNumber := computeSystem.callbackNumber
  672. callbackMapLock.RLock()
  673. callbackContext := callbackMap[callbackNumber]
  674. callbackMapLock.RUnlock()
  675. if callbackContext == nil {
  676. return nil
  677. }
  678. handle := callbackContext.handle
  679. if handle == 0 {
  680. return nil
  681. }
  682. // hcsUnregisterComputeSystemCallback has its own synchronization
  683. // to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
  684. err := vmcompute.HcsUnregisterComputeSystemCallback(ctx, handle)
  685. if err != nil {
  686. return err
  687. }
  688. closeChannels(callbackContext.channels)
  689. callbackMapLock.Lock()
  690. delete(callbackMap, callbackNumber)
  691. callbackMapLock.Unlock()
  692. handle = 0 //nolint:ineffassign
  693. return nil
  694. }
  695. // Modify the System by sending a request to HCS
  696. func (computeSystem *System) Modify(ctx context.Context, config interface{}) error {
  697. computeSystem.handleLock.RLock()
  698. defer computeSystem.handleLock.RUnlock()
  699. operation := "hcs::System::Modify"
  700. if computeSystem.handle == 0 {
  701. return makeSystemError(computeSystem, operation, ErrAlreadyClosed, nil)
  702. }
  703. requestBytes, err := json.Marshal(config)
  704. if err != nil {
  705. return err
  706. }
  707. requestJSON := string(requestBytes)
  708. resultJSON, err := vmcompute.HcsModifyComputeSystem(ctx, computeSystem.handle, requestJSON)
  709. events := processHcsResult(ctx, resultJSON)
  710. if err != nil {
  711. return makeSystemError(computeSystem, operation, err, events)
  712. }
  713. return nil
  714. }