collector.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package property
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "sync"
  19. "github.com/vmware/govmomi/vim25"
  20. "github.com/vmware/govmomi/vim25/methods"
  21. "github.com/vmware/govmomi/vim25/mo"
  22. "github.com/vmware/govmomi/vim25/soap"
  23. "github.com/vmware/govmomi/vim25/types"
  24. )
  25. // ErrConcurrentCollector is returned from WaitForUpdates, WaitForUpdatesEx,
  26. // or CheckForUpdates if any of those calls are unable to obtain an exclusive
  27. // lock for the property collector.
  28. var ErrConcurrentCollector = fmt.Errorf(
  29. "only one goroutine may invoke WaitForUpdates, WaitForUpdatesEx, " +
  30. "or CheckForUpdates on a given PropertyCollector")
  31. // Collector models the PropertyCollector managed object.
  32. //
  33. // For more information, see:
  34. // http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvmodl.query.PropertyCollector.html
  35. type Collector struct {
  36. mu sync.Mutex
  37. roundTripper soap.RoundTripper
  38. reference types.ManagedObjectReference
  39. }
  40. // DefaultCollector returns the session's default property collector.
  41. func DefaultCollector(c *vim25.Client) *Collector {
  42. p := Collector{
  43. roundTripper: c,
  44. reference: c.ServiceContent.PropertyCollector,
  45. }
  46. return &p
  47. }
  48. func (p *Collector) Reference() types.ManagedObjectReference {
  49. return p.reference
  50. }
  51. // Create creates a new session-specific Collector that can be used to
  52. // retrieve property updates independent of any other Collector.
  53. func (p *Collector) Create(ctx context.Context) (*Collector, error) {
  54. req := types.CreatePropertyCollector{
  55. This: p.Reference(),
  56. }
  57. res, err := methods.CreatePropertyCollector(ctx, p.roundTripper, &req)
  58. if err != nil {
  59. return nil, err
  60. }
  61. newp := Collector{
  62. roundTripper: p.roundTripper,
  63. reference: res.Returnval,
  64. }
  65. return &newp, nil
  66. }
  67. // Destroy destroys this Collector.
  68. func (p *Collector) Destroy(ctx context.Context) error {
  69. req := types.DestroyPropertyCollector{
  70. This: p.Reference(),
  71. }
  72. _, err := methods.DestroyPropertyCollector(ctx, p.roundTripper, &req)
  73. if err != nil {
  74. return err
  75. }
  76. p.reference = types.ManagedObjectReference{}
  77. return nil
  78. }
  79. func (p *Collector) CreateFilter(ctx context.Context, req types.CreateFilter) (*Filter, error) {
  80. req.This = p.Reference()
  81. resp, err := methods.CreateFilter(ctx, p.roundTripper, &req)
  82. if err != nil {
  83. return nil, err
  84. }
  85. return &Filter{roundTripper: p.roundTripper, reference: resp.Returnval}, nil
  86. }
  87. // Deprecated: Please use WaitForUpdatesEx instead.
  88. func (p *Collector) WaitForUpdates(
  89. ctx context.Context,
  90. version string,
  91. opts ...*types.WaitOptions) (*types.UpdateSet, error) {
  92. if !p.mu.TryLock() {
  93. return nil, ErrConcurrentCollector
  94. }
  95. defer p.mu.Unlock()
  96. req := types.WaitForUpdatesEx{
  97. This: p.Reference(),
  98. Version: version,
  99. }
  100. if len(opts) == 1 {
  101. req.Options = opts[0]
  102. } else if len(opts) > 1 {
  103. panic("only one option may be specified")
  104. }
  105. res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
  106. if err != nil {
  107. return nil, err
  108. }
  109. return res.Returnval, nil
  110. }
  111. func (p *Collector) CancelWaitForUpdates(ctx context.Context) error {
  112. req := &types.CancelWaitForUpdates{This: p.Reference()}
  113. _, err := methods.CancelWaitForUpdates(ctx, p.roundTripper, req)
  114. return err
  115. }
  116. // RetrieveProperties wraps RetrievePropertiesEx and ContinueRetrievePropertiesEx to collect properties in batches.
  117. func (p *Collector) RetrieveProperties(
  118. ctx context.Context,
  119. req types.RetrieveProperties,
  120. maxObjectsArgs ...int32) (*types.RetrievePropertiesResponse, error) {
  121. var opts types.RetrieveOptions
  122. if l := len(maxObjectsArgs); l > 1 {
  123. return nil, fmt.Errorf("maxObjectsArgs accepts a single value")
  124. } else if l == 1 {
  125. opts.MaxObjects = maxObjectsArgs[0]
  126. }
  127. objects, err := mo.RetrievePropertiesEx(ctx, p.roundTripper, types.RetrievePropertiesEx{
  128. This: p.Reference(),
  129. SpecSet: req.SpecSet,
  130. Options: opts,
  131. })
  132. if err != nil {
  133. return nil, err
  134. }
  135. return &types.RetrievePropertiesResponse{Returnval: objects}, nil
  136. }
  137. // Retrieve loads properties for a slice of managed objects. The dst argument
  138. // must be a pointer to a []interface{}, which is populated with the instances
  139. // of the specified managed objects, with the relevant properties filled in. If
  140. // the properties slice is nil, all properties are loaded.
  141. // Note that pointer types are optional fields that may be left as a nil value.
  142. // The caller should check such fields for a nil value before dereferencing.
  143. func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst interface{}) error {
  144. if len(objs) == 0 {
  145. return errors.New("object references is empty")
  146. }
  147. kinds := make(map[string]bool)
  148. var propSet []types.PropertySpec
  149. var objectSet []types.ObjectSpec
  150. for _, obj := range objs {
  151. if _, ok := kinds[obj.Type]; !ok {
  152. spec := types.PropertySpec{
  153. Type: obj.Type,
  154. }
  155. if len(ps) == 0 {
  156. spec.All = types.NewBool(true)
  157. } else {
  158. spec.PathSet = ps
  159. }
  160. propSet = append(propSet, spec)
  161. kinds[obj.Type] = true
  162. }
  163. objectSpec := types.ObjectSpec{
  164. Obj: obj,
  165. Skip: types.NewBool(false),
  166. }
  167. objectSet = append(objectSet, objectSpec)
  168. }
  169. req := types.RetrieveProperties{
  170. SpecSet: []types.PropertyFilterSpec{
  171. {
  172. ObjectSet: objectSet,
  173. PropSet: propSet,
  174. },
  175. },
  176. }
  177. res, err := p.RetrieveProperties(ctx, req)
  178. if err != nil {
  179. return err
  180. }
  181. if d, ok := dst.(*[]types.ObjectContent); ok {
  182. *d = res.Returnval
  183. return nil
  184. }
  185. return mo.LoadObjectContent(res.Returnval, dst)
  186. }
  187. // RetrieveWithFilter populates dst as Retrieve does, but only for entities
  188. // that match the specified filter.
  189. func (p *Collector) RetrieveWithFilter(
  190. ctx context.Context,
  191. objs []types.ManagedObjectReference,
  192. ps []string,
  193. dst interface{},
  194. filter Match) error {
  195. if len(filter) == 0 {
  196. return p.Retrieve(ctx, objs, ps, dst)
  197. }
  198. var content []types.ObjectContent
  199. err := p.Retrieve(ctx, objs, filter.Keys(), &content)
  200. if err != nil {
  201. return err
  202. }
  203. objs = filter.ObjectContent(content)
  204. if len(objs) == 0 {
  205. return nil
  206. }
  207. return p.Retrieve(ctx, objs, ps, dst)
  208. }
  209. // RetrieveOne calls Retrieve with a single managed object reference via Collector.Retrieve().
  210. func (p *Collector) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, ps []string, dst interface{}) error {
  211. var objs = []types.ManagedObjectReference{obj}
  212. return p.Retrieve(ctx, objs, ps, dst)
  213. }
  214. // WaitForUpdatesEx waits for any of the specified properties of the specified
  215. // managed object to change. It calls the specified function for every update it
  216. // receives. If this function returns false, it continues waiting for
  217. // subsequent updates. If this function returns true, it stops waiting and
  218. // returns.
  219. //
  220. // If the Context is canceled, a call to CancelWaitForUpdates() is made and its
  221. // error value is returned.
  222. //
  223. // By default, ObjectUpdate.MissingSet faults are not propagated to the returned
  224. // error, set WaitFilter.PropagateMissing=true to enable MissingSet fault
  225. // propagation.
  226. func (p *Collector) WaitForUpdatesEx(
  227. ctx context.Context,
  228. opts WaitOptions,
  229. onUpdatesFn func([]types.ObjectUpdate) bool) error {
  230. if !p.mu.TryLock() {
  231. return ErrConcurrentCollector
  232. }
  233. defer p.mu.Unlock()
  234. req := types.WaitForUpdatesEx{
  235. This: p.Reference(),
  236. Options: opts.Options,
  237. }
  238. for {
  239. res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
  240. if err != nil {
  241. if ctx.Err() == context.Canceled {
  242. return p.CancelWaitForUpdates(context.Background())
  243. }
  244. return err
  245. }
  246. set := res.Returnval
  247. if set == nil {
  248. if req.Options != nil && req.Options.MaxWaitSeconds != nil {
  249. return nil // WaitOptions.MaxWaitSeconds exceeded
  250. }
  251. // Retry if the result came back empty
  252. continue
  253. }
  254. req.Version = set.Version
  255. opts.Truncated = false
  256. if set.Truncated != nil {
  257. opts.Truncated = *set.Truncated
  258. }
  259. for _, fs := range set.FilterSet {
  260. if opts.PropagateMissing {
  261. for i := range fs.ObjectSet {
  262. for _, p := range fs.ObjectSet[i].MissingSet {
  263. // Same behavior as mo.ObjectContentToType()
  264. return soap.WrapVimFault(p.Fault.Fault)
  265. }
  266. }
  267. }
  268. if onUpdatesFn(fs.ObjectSet) {
  269. return nil
  270. }
  271. }
  272. }
  273. }