datacenter.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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 esxi
  15. import (
  16. "regexp"
  17. "strings"
  18. "sync"
  19. "github.com/vmware/govmomi/object"
  20. "github.com/vmware/govmomi/property"
  21. "github.com/vmware/govmomi/view"
  22. "github.com/vmware/govmomi/vim25/mo"
  23. "github.com/vmware/govmomi/vim25/types"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/sets"
  26. api "yunion.io/x/cloudmux/pkg/apis/compute"
  27. "yunion.io/x/cloudmux/pkg/cloudprovider"
  28. )
  29. var DATACENTER_PROPS = []string{"name", "parent", "datastore", "network"}
  30. type SDatacenter struct {
  31. SManagedObject
  32. ihosts []cloudprovider.ICloudHost
  33. istorages []cloudprovider.ICloudStorage
  34. inetworks map[string]IVMNetwork
  35. iresoucePool []cloudprovider.ICloudProject
  36. clusters []*SCluster
  37. netLock sync.Mutex
  38. Name string
  39. }
  40. func newDatacenter(manager *SESXiClient, dc *mo.Datacenter) *SDatacenter {
  41. obj := SDatacenter{SManagedObject: newManagedObject(manager, dc, nil)}
  42. obj.datacenter = &obj
  43. return &obj
  44. }
  45. func (dc *SDatacenter) isDefaultDc() bool {
  46. return dc.object == &defaultDc
  47. }
  48. func (dc *SDatacenter) getDatacenter() *mo.Datacenter {
  49. return dc.object.(*mo.Datacenter)
  50. }
  51. func (dc *SDatacenter) getObjectDatacenter() *object.Datacenter {
  52. if dc.isDefaultDc() {
  53. return nil
  54. }
  55. return object.NewDatacenter(dc.manager.client.Client, dc.object.Reference())
  56. }
  57. func (dc *SDatacenter) scanResourcePool() error {
  58. if dc.iresoucePool == nil {
  59. pools, err := dc.listResourcePools()
  60. if err != nil {
  61. return errors.Wrap(err, "listResourcePools")
  62. }
  63. dc.iresoucePool = []cloudprovider.ICloudProject{}
  64. for i := 0; i < len(pools); i++ {
  65. p := NewResourcePool(dc.manager, &pools[i], dc)
  66. dc.iresoucePool = append(dc.iresoucePool, p)
  67. }
  68. }
  69. return nil
  70. }
  71. func (dc *SDatacenter) scanHosts() error {
  72. if dc.ihosts == nil {
  73. var hosts []mo.HostSystem
  74. if dc.isDefaultDc() {
  75. err := dc.manager.scanAllMObjects(HOST_SYSTEM_PROPS, &hosts)
  76. if err != nil {
  77. return errors.Wrap(err, "dc.manager.scanAllMObjects")
  78. }
  79. } else {
  80. err := dc.manager.scanMObjects(dc.object.Entity().Self, HOST_SYSTEM_PROPS, &hosts)
  81. if err != nil {
  82. return errors.Wrap(err, "dc.manager.scanMObjects")
  83. }
  84. }
  85. dc.ihosts = make([]cloudprovider.ICloudHost, 0)
  86. for i := 0; i < len(hosts); i += 1 {
  87. h := NewHost(dc.manager, &hosts[i], dc)
  88. if h != nil {
  89. dc.ihosts = append(dc.ihosts, h)
  90. }
  91. }
  92. }
  93. return nil
  94. }
  95. func (dc *SDatacenter) GetResourcePools() ([]cloudprovider.ICloudProject, error) {
  96. err := dc.scanResourcePool()
  97. if err != nil {
  98. return nil, errors.Wrap(err, "dc.scanResourcePool")
  99. }
  100. return dc.iresoucePool, nil
  101. }
  102. func (dc *SDatacenter) listResourcePools() ([]mo.ResourcePool, error) {
  103. var pools []mo.ResourcePool
  104. err := dc.manager.scanMObjects(dc.object.Entity().Self, RESOURCEPOOL_PROPS, &pools)
  105. if err != nil {
  106. return nil, errors.Wrap(err, "scanMObjects")
  107. }
  108. return pools, nil
  109. }
  110. func (dc *SDatacenter) ListClusters() ([]*SCluster, error) {
  111. return dc.listClusters()
  112. }
  113. func (dc *SDatacenter) GetCluster(cluster string) (*SCluster, error) {
  114. clusters, err := dc.ListClusters()
  115. if err != nil {
  116. return nil, errors.Wrap(err, "ListClusters")
  117. }
  118. for i := range clusters {
  119. if clusters[i].GetName() == cluster {
  120. return clusters[i], nil
  121. }
  122. }
  123. return nil, cloudprovider.ErrNotFound
  124. }
  125. func (dc *SDatacenter) listClusters() ([]*SCluster, error) {
  126. if dc.clusters != nil {
  127. return dc.clusters, nil
  128. }
  129. clusters := []mo.ClusterComputeResource{}
  130. err := dc.manager.scanMObjects(dc.object.Entity().Self, RESOURCEPOOL_PROPS, &clusters)
  131. if err != nil {
  132. return nil, errors.Wrap(err, "scanMObjects")
  133. }
  134. ret := []*SCluster{}
  135. for i := range clusters {
  136. c := NewCluster(dc.manager, &clusters[i], dc)
  137. ret = append(ret, c)
  138. }
  139. return ret, nil
  140. }
  141. func (dc *SDatacenter) GetIHosts() ([]cloudprovider.ICloudHost, error) {
  142. err := dc.scanHosts()
  143. if err != nil {
  144. return nil, errors.Wrap(err, "dc.scanHosts")
  145. }
  146. return dc.ihosts, nil
  147. }
  148. func (dc *SDatacenter) scanDatastores() error {
  149. if dc.istorages == nil {
  150. var stores []mo.Datastore
  151. if dc.isDefaultDc() {
  152. err := dc.manager.scanAllMObjects(DATASTORE_PROPS, &stores)
  153. if err != nil {
  154. return errors.Wrap(err, "dc.manager.scanAllMObjects")
  155. }
  156. } else {
  157. dsList := dc.getDatacenter().Datastore
  158. if dsList != nil {
  159. err := dc.manager.references2Objects(dsList, DATASTORE_PROPS, &stores)
  160. if err != nil {
  161. return errors.Wrap(err, "dc.manager.references2Objects")
  162. }
  163. }
  164. }
  165. dc.istorages = make([]cloudprovider.ICloudStorage, 0)
  166. for i := 0; i < len(stores); i += 1 {
  167. ds := NewDatastore(dc.manager, &stores[i], dc)
  168. dsId := ds.GetGlobalId()
  169. if len(dsId) > 0 {
  170. dc.istorages = append(dc.istorages, ds)
  171. }
  172. }
  173. }
  174. return nil
  175. }
  176. func (dc *SDatacenter) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
  177. err := dc.scanDatastores()
  178. if err != nil {
  179. return nil, errors.Wrap(err, "dc.scanDatastores")
  180. }
  181. return dc.istorages, nil
  182. }
  183. func (dc *SDatacenter) GetIHostByMoId(idstr string) (cloudprovider.ICloudHost, error) {
  184. ihosts, err := dc.GetIHosts()
  185. if err != nil {
  186. return nil, errors.Wrap(err, "dc.GetIHosts")
  187. }
  188. for i := 0; i < len(ihosts); i += 1 {
  189. if ihosts[i].GetId() == idstr {
  190. return ihosts[i], nil
  191. }
  192. }
  193. return nil, cloudprovider.ErrNotFound
  194. }
  195. func (dc *SDatacenter) GetIStorageByMoId(idstr string) (cloudprovider.ICloudStorage, error) {
  196. istorages, err := dc.GetIStorages()
  197. if err != nil {
  198. return nil, errors.Wrap(err, "dc.GetIStorages")
  199. }
  200. for i := 0; i < len(istorages); i += 1 {
  201. if istorages[i].GetId() == idstr {
  202. return istorages[i], nil
  203. }
  204. }
  205. return nil, cloudprovider.ErrNotFound
  206. }
  207. func (dc *SDatacenter) getDcObj() *object.Datacenter {
  208. if dc.isDefaultDc() {
  209. return nil
  210. }
  211. return object.NewDatacenter(dc.manager.client.Client, dc.object.Reference())
  212. }
  213. func (dc *SDatacenter) fetchMoVms(filter property.Match, props []string) ([]mo.VirtualMachine, error) {
  214. odc := dc.getObjectDatacenter()
  215. root := odc.Reference()
  216. var movms []mo.VirtualMachine
  217. err := dc.manager.scanMObjectsWithFilter(root, props, &movms, filter)
  218. if err != nil {
  219. return nil, errors.Wrap(err, "scanMObjectsWithFilter")
  220. }
  221. return movms, nil
  222. }
  223. func (dc *SDatacenter) moVms2Vm(movms []mo.VirtualMachine, all *bool) ([]*SVirtualMachine, error) {
  224. // avoid applying new memory and copying
  225. vms := make([]*SVirtualMachine, 0, len(movms))
  226. for i := range movms {
  227. if (all != nil && *all) || !strings.HasPrefix(movms[i].Name, api.ESXI_IMAGE_CACHE_TMP_PREFIX) {
  228. vm := NewVirtualMachine(dc.manager, &movms[i], dc)
  229. // must
  230. if vm == nil {
  231. continue
  232. }
  233. vms = append(vms, vm)
  234. }
  235. }
  236. return vms, nil
  237. }
  238. func (dc *SDatacenter) fetchFakeTemplateVMs(movms []mo.VirtualMachine, regex string) ([]*SVirtualMachine, error) {
  239. var (
  240. tNameRegex *regexp.Regexp
  241. err error
  242. )
  243. if len(regex) == 0 {
  244. tNameRegex = tempalteNameRegex
  245. } else {
  246. tNameRegex, err = regexp.Compile(regex)
  247. if err != nil {
  248. return nil, errors.Wrap(err, "generate regexp")
  249. }
  250. }
  251. objs := make([]types.ManagedObjectReference, 0)
  252. for i := range movms {
  253. name := movms[i].Name
  254. if tNameRegex != nil && tNameRegex.MatchString(name) {
  255. objs = append(objs, movms[i].Reference())
  256. }
  257. }
  258. if len(objs) == 0 {
  259. return nil, nil
  260. }
  261. return dc.fetchVms(objs, false)
  262. }
  263. func (dc *SDatacenter) fetchVmsFromCache(vmRefs []types.ManagedObjectReference) ([]*SVirtualMachine, error) {
  264. vmRefSet := sets.NewString()
  265. for i := range vmRefs {
  266. vmRefSet.Insert(vmRefs[i].String())
  267. }
  268. ihosts, err := dc.GetIHosts()
  269. if err != nil {
  270. return nil, err
  271. }
  272. ret := make([]*SVirtualMachine, 0, len(vmRefs))
  273. for i := range ihosts {
  274. ivms, err := ihosts[i].GetIVMs()
  275. if err != nil {
  276. return nil, errors.Wrapf(err, "GetIVMs")
  277. }
  278. for i := range ivms {
  279. vm := ivms[i].(*SVirtualMachine)
  280. s := vm.getVirtualMachine().Self.String()
  281. if vmRefSet.Has(s) {
  282. ret = append(ret, vm)
  283. vmRefSet.Delete(s)
  284. }
  285. }
  286. }
  287. return ret, nil
  288. }
  289. func (dc *SDatacenter) fetchVms(vmRefs []types.ManagedObjectReference, all bool) ([]*SVirtualMachine, error) {
  290. var movms []mo.VirtualMachine
  291. if vmRefs != nil {
  292. err := dc.manager.references2Objects(vmRefs, VIRTUAL_MACHINE_PROPS, &movms)
  293. if err != nil {
  294. return nil, errors.Wrap(err, "dc.manager.references2Objects")
  295. }
  296. }
  297. return dc.moVms2Vm(movms, &all)
  298. }
  299. func (dc *SDatacenter) fetchHostsInternal(vmRefs []types.ManagedObjectReference, all bool) ([]*SHost, error) {
  300. var movms []mo.HostSystem
  301. if vmRefs != nil {
  302. err := dc.manager.references2Objects(vmRefs, VIRTUAL_MACHINE_PROPS, &movms)
  303. if err != nil {
  304. return nil, errors.Wrap(err, "dc.manager.references2Objects")
  305. }
  306. }
  307. // avoid applying new memory and copying
  308. vms := make([]*SHost, 0, len(movms))
  309. for i := range movms {
  310. if all || !strings.HasPrefix(movms[i].Entity().Name, api.ESXI_IMAGE_CACHE_TMP_PREFIX) {
  311. vms = append(vms, NewHost(dc.manager, &movms[i], dc))
  312. }
  313. }
  314. return vms, nil
  315. }
  316. func (dc *SDatacenter) FetchVMs() ([]*SVirtualMachine, error) {
  317. return dc.fetchVMsWithFilter(property.Match{})
  318. }
  319. func (dc *SDatacenter) FetchNoTemplateVMs() ([]*SVirtualMachine, error) {
  320. filter := property.Match{}
  321. filter["config.template"] = false
  322. return dc.fetchVMsWithFilter(filter)
  323. }
  324. func (dc *SDatacenter) FetchFakeTempateVMs(regex string) ([]*SVirtualMachine, error) {
  325. filter := property.Match{}
  326. filter["summary.runtime.powerState"] = types.VirtualMachinePowerStatePoweredOff
  327. movms, err := dc.fetchMoVms(filter, []string{"name"})
  328. if err != nil {
  329. return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines")
  330. }
  331. return dc.fetchFakeTemplateVMs(movms, regex)
  332. }
  333. func (dc *SDatacenter) fetchVMsWithFilter(filter property.Match) ([]*SVirtualMachine, error) {
  334. movms, err := dc.fetchMoVms(filter, VIRTUAL_MACHINE_PROPS)
  335. if err != nil {
  336. return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines")
  337. }
  338. ret, err := dc.moVms2Vm(movms, nil)
  339. if err != nil {
  340. return nil, errors.Wrap(err, "unable to convert mo.VirtualMachine to VirtualMachine")
  341. }
  342. return ret, err
  343. }
  344. func (dc *SDatacenter) FetchNoTemplateVMEntityReferens() ([]types.ManagedObjectReference, error) {
  345. filter := property.Match{}
  346. filter["config.template"] = false
  347. odc := dc.getObjectDatacenter()
  348. root := odc.Reference()
  349. m := view.NewManager(dc.manager.client.Client)
  350. v, err := m.CreateContainerView(dc.manager.context, root, []string{"VirtualMachine"}, true)
  351. if err != nil {
  352. return nil, err
  353. }
  354. defer func() {
  355. _ = v.Destroy(dc.manager.context)
  356. }()
  357. objs, err := v.Find(dc.manager.context, []string{"VirtualMachine"}, filter)
  358. if err != nil {
  359. return nil, err
  360. }
  361. return objs, nil
  362. }
  363. func (dc *SDatacenter) FetchNoTemplateHostEntityReferens() ([]types.ManagedObjectReference, error) {
  364. filter := property.Match{}
  365. odc := dc.getObjectDatacenter()
  366. root := odc.Reference()
  367. m := view.NewManager(dc.manager.client.Client)
  368. v, err := m.CreateContainerView(dc.manager.context, root, []string{"HostSystem"}, true)
  369. if err != nil {
  370. return nil, err
  371. }
  372. defer func() {
  373. _ = v.Destroy(dc.manager.context)
  374. }()
  375. objs, err := v.Find(dc.manager.context, []string{"HostSystem"}, filter)
  376. if err != nil {
  377. return nil, err
  378. }
  379. return objs, nil
  380. }
  381. func (dc *SDatacenter) fetchHosts(filter property.Match) ([]*SHost, error) {
  382. odc := dc.getObjectDatacenter()
  383. root := odc.Reference()
  384. m := view.NewManager(dc.manager.client.Client)
  385. v, err := m.CreateContainerView(dc.manager.context, root, []string{"HostSystem"}, true)
  386. if err != nil {
  387. return nil, err
  388. }
  389. defer func() {
  390. _ = v.Destroy(dc.manager.context)
  391. }()
  392. objs, err := v.Find(dc.manager.context, []string{"HostSystem"}, filter)
  393. if err != nil {
  394. return nil, err
  395. }
  396. hosts, err := dc.fetchHostsInternal(objs, false)
  397. return hosts, err
  398. }
  399. func (dc *SDatacenter) FetchTemplateVMs() ([]*SVirtualMachine, error) {
  400. filter := property.Match{}
  401. filter["config.template"] = true
  402. return dc.fetchVMsWithFilter(filter)
  403. }
  404. func (dc *SDatacenter) FetchTemplateVMById(id string) (*SVirtualMachine, error) {
  405. filter := property.Match{}
  406. filter["config.template"] = true
  407. filter["summary.config.uuid"] = id
  408. vms, err := dc.fetchVMsWithFilter(filter)
  409. if err != nil {
  410. return nil, err
  411. }
  412. if len(vms) == 0 {
  413. return nil, errors.ErrNotFound
  414. }
  415. return vms[0], nil
  416. }
  417. func (dc *SDatacenter) FetchVMById(id string) (*SVirtualMachine, error) {
  418. filter := property.Match{}
  419. filter["summary.config.uuid"] = id
  420. vms, err := dc.fetchVMsWithFilter(filter)
  421. if err != nil {
  422. return nil, err
  423. }
  424. for i := range vms {
  425. return vms[i], nil
  426. }
  427. return dc.FetchVMByName(id)
  428. }
  429. func (dc *SDatacenter) FetchVMByName(name string) (*SVirtualMachine, error) {
  430. filter := property.Match{}
  431. filter["name"] = name
  432. vms, err := dc.fetchVMsWithFilter(filter)
  433. if err != nil {
  434. return nil, err
  435. }
  436. for i := range vms {
  437. if vms[i].GetName() == name {
  438. return vms[i], nil
  439. }
  440. }
  441. return nil, errors.Wrapf(errors.ErrNotFound, "FetchVMByName %s", name)
  442. }
  443. func (dc *SDatacenter) fetchDatastores(datastoreRefs []types.ManagedObjectReference) ([]cloudprovider.ICloudStorage, error) {
  444. var dss []mo.Datastore
  445. if datastoreRefs != nil {
  446. err := dc.manager.references2Objects(datastoreRefs, DATASTORE_PROPS, &dss)
  447. if err != nil {
  448. return nil, errors.Wrap(err, "dc.manager.references2Objects")
  449. }
  450. }
  451. retDatastores := make([]cloudprovider.ICloudStorage, 0, len(dss))
  452. for i := range dss {
  453. retDatastores = append(retDatastores, NewDatastore(dc.manager, &dss[i], dc))
  454. }
  455. return retDatastores, nil
  456. }
  457. func (dc *SDatacenter) scanNetworks() error {
  458. if dc.inetworks == nil {
  459. dc.inetworks = make(map[string]IVMNetwork)
  460. if dc.isDefaultDc() {
  461. return dc.scanDefaultNetworks()
  462. } else {
  463. return dc.scanDcNetworks()
  464. }
  465. }
  466. return nil
  467. }
  468. func (dc *SDatacenter) scanDefaultNetworks() error {
  469. err := dc.scanAllDvPortgroups()
  470. if err != nil {
  471. return errors.Wrap(err, "dc.scanAllDvPortgroups")
  472. }
  473. err = dc.scanAllNetworks()
  474. if err != nil {
  475. return errors.Wrap(err, "dc.scanAllNetworks")
  476. }
  477. return nil
  478. }
  479. func (dc *SDatacenter) scanAllDvPortgroups() error {
  480. var dvports []mo.DistributedVirtualPortgroup
  481. err := dc.manager.scanAllMObjects(DVPORTGROUP_PROPS, &dvports)
  482. if err != nil {
  483. return errors.Wrap(err, "dc.manager.scanAllMObjects mo.DistributedVirtualPortgroup")
  484. }
  485. dc.netLock.Lock()
  486. defer dc.netLock.Unlock()
  487. for i := range dvports {
  488. net := NewDistributedVirtualPortgroup(dc.manager, &dvports[i], dc)
  489. dc.inetworks[net.GetName()] = net
  490. }
  491. return nil
  492. }
  493. func (dc *SDatacenter) scanAllNetworks() error {
  494. var nets []mo.Network
  495. err := dc.manager.scanAllMObjects(NETWORK_PROPS, &nets)
  496. if err != nil {
  497. return errors.Wrap(err, "dc.manager.scanAllMObjects mo.Network")
  498. }
  499. dc.netLock.Lock()
  500. defer dc.netLock.Unlock()
  501. for i := range nets {
  502. net := NewNetwork(dc.manager, &nets[i], dc)
  503. dc.inetworks[net.GetName()] = net
  504. }
  505. return nil
  506. }
  507. func (dc *SDatacenter) scanDcNetworks() error {
  508. nets, err := dc.resolveNetworks(dc.getDatacenter().Network)
  509. if err != nil {
  510. return errors.Wrap(err, "resolveNetworks")
  511. }
  512. dc.netLock.Lock()
  513. defer dc.netLock.Unlock()
  514. for i := range nets {
  515. net := nets[i]
  516. dc.inetworks[net.GetName()] = net
  517. }
  518. return nil
  519. }
  520. func (dc *SDatacenter) getNetworkByName(name string) (IVMNetwork, error) {
  521. err := dc.scanNetworks()
  522. if err != nil {
  523. return nil, errors.Wrap(err, "dc.scanNetworks")
  524. }
  525. if net, ok := dc.inetworks[name]; !ok {
  526. return nil, errors.Wrap(errors.ErrNotFound, name)
  527. } else {
  528. return net, nil
  529. }
  530. }
  531. func (dc *SDatacenter) GetNetworks() ([]IVMNetwork, error) {
  532. err := dc.scanNetworks()
  533. if err != nil {
  534. return nil, errors.Wrap(err, "dc.scanNetworks")
  535. }
  536. nets := make([]IVMNetwork, 0, len(dc.inetworks))
  537. for _, net := range dc.inetworks {
  538. nets = append(nets, net)
  539. }
  540. return nets, nil
  541. }
  542. func (dc *SDatacenter) GetTemplateVMs() ([]*SVirtualMachine, error) {
  543. hosts, err := dc.GetIHosts()
  544. if err != nil {
  545. return nil, errors.Wrap(err, "SDatacenter.GetIHosts")
  546. }
  547. templateVms := make([]*SVirtualMachine, 5)
  548. for _, ihost := range hosts {
  549. host := ihost.(*SHost)
  550. tvms, err := host.GetTemplateVMs()
  551. if err != nil {
  552. return nil, errors.Wrap(err, "host.GetTemplateVMs")
  553. }
  554. templateVms = append(templateVms, tvms...)
  555. }
  556. return templateVms, nil
  557. }