cloudaccount_vmware.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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 models
  15. import (
  16. "context"
  17. "fmt"
  18. "sort"
  19. "yunion.io/x/cloudmux/pkg/multicloud/esxi"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/tristate"
  24. "yunion.io/x/pkg/util/netutils"
  25. "yunion.io/x/pkg/util/rbacscope"
  26. "yunion.io/x/pkg/utils"
  27. api "yunion.io/x/onecloud/pkg/apis/compute"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  30. "yunion.io/x/onecloud/pkg/compute/options"
  31. "yunion.io/x/onecloud/pkg/httperrors"
  32. "yunion.io/x/onecloud/pkg/mcclient"
  33. )
  34. type CASimpleNetConf struct {
  35. IpStart string `json:"guest_ip_start"`
  36. IpEnd string `json:"guest_ip_end"`
  37. IpMask int8 `json:"guest_ip_mask"`
  38. Gateway string `json:"guest_gateway"`
  39. VlanID int32 `json:"vlan_id"`
  40. WireId string `json:"wire_id"`
  41. }
  42. type CANetConf struct {
  43. CASimpleNetConf
  44. Name string `json:"name"`
  45. Description string `json:"description"`
  46. }
  47. var ipMaskLen int8 = 24
  48. func (account *SCloudaccount) PrepareEsxiHostNetwork(ctx context.Context, userCred mcclient.TokenCredential, zoneId string) error {
  49. cProvider, err := account.GetProvider(ctx)
  50. if err != nil {
  51. return errors.Wrap(err, "account.GetProvider")
  52. }
  53. // fetch esxiclient
  54. iregion, err := cProvider.GetOnPremiseIRegion()
  55. if err != nil {
  56. return errors.Wrap(err, "cProvider.GetOnPremiseIRegion")
  57. }
  58. esxiClient, ok := iregion.(*esxi.SESXiClient)
  59. if !ok {
  60. return errors.Wrap(httperrors.ErrNotSupported, "not a esxi provider")
  61. }
  62. iHosts, err := esxiClient.GetIHosts()
  63. if err != nil {
  64. return errors.Wrap(err, "esxiClient.GetIHosts")
  65. }
  66. hostIps := make([]netutils.IPV4Addr, 0)
  67. for i := range iHosts {
  68. accessIp := iHosts[i].GetAccessIp()
  69. hostNics, err := iHosts[i].GetIHostNics()
  70. if err != nil {
  71. return errors.Wrapf(err, "iHosts[%d].GetIHostNics()", i)
  72. }
  73. findAccessIp := false
  74. for _, hn := range hostNics {
  75. if len(hn.GetBridge()) > 0 {
  76. // a bridged nic, must be a virtual port group, skip
  77. continue
  78. }
  79. ipAddrStr := hn.GetIpAddr()
  80. if len(ipAddrStr) == 0 {
  81. // skip interface without a valid ip address
  82. continue
  83. }
  84. if accessIp == ipAddrStr {
  85. findAccessIp = true
  86. }
  87. ipAddr, err := netutils.NewIPV4Addr(ipAddrStr)
  88. if err != nil {
  89. log.Errorf("fail to parse ipv4 addr %s: %s", ipAddrStr, err)
  90. } else {
  91. hostIps = append(hostIps, ipAddr)
  92. }
  93. }
  94. if !findAccessIp {
  95. log.Errorf("Fail to find access ip %s NIC for esxi host %s", accessIp, iHosts[i].GetName())
  96. }
  97. }
  98. onPremiseNets, err := NetworkManager.fetchAllOnpremiseNetworks("", tristate.None)
  99. if err != nil {
  100. return errors.Wrap(err, "NetworkManager.fetchAllOnpremiseNetworks")
  101. }
  102. if zoneId == "" {
  103. zoneIds, err := fetchOnpremiseZoneIds(onPremiseNets)
  104. if err != nil {
  105. return errors.Wrap(err, "fetchOnpremiseZoneIds")
  106. }
  107. if len(zoneIds) == 0 {
  108. return errors.Wrap(httperrors.ErrInvalidStatus, "empty zone id?")
  109. }
  110. if len(zoneIds) == 1 {
  111. zoneId = zoneIds[0]
  112. } else {
  113. zoneId, err = guessEsxiZoneId(hostIps, onPremiseNets)
  114. if err != nil {
  115. return errors.Wrap(err, "fail to find zone of esxi")
  116. }
  117. }
  118. }
  119. netConfs, err := guessEsxiNetworks(hostIps, account.Name, onPremiseNets)
  120. if err != nil {
  121. return errors.Wrap(err, "guessEsxiNetworks")
  122. }
  123. log.Infof("netConfs: %s", jsonutils.Marshal(netConfs))
  124. {
  125. err := account.createNetworks(ctx, account.Name, zoneId, netConfs)
  126. if err != nil {
  127. return errors.Wrap(err, "account.createNetworks")
  128. }
  129. }
  130. return nil
  131. }
  132. func fetchOnpremiseZoneIds(onPremiseNets []SNetwork) ([]string, error) {
  133. var zoneIds []string
  134. for i := range onPremiseNets {
  135. zone, err := onPremiseNets[i].GetZone()
  136. if err != nil {
  137. return nil, errors.Wrapf(err, "onPremiseNets[%d].GetZone", i)
  138. }
  139. if !utils.IsInArray(zone.Id, zoneIds) {
  140. zoneIds = append(zoneIds, zone.Id)
  141. }
  142. }
  143. return zoneIds, nil
  144. }
  145. func guessEsxiZoneId(hostIps []netutils.IPV4Addr, onPremiseNets []SNetwork) (string, error) {
  146. zoneIds, err := ZoneManager.getOnpremiseZoneIds()
  147. if err != nil {
  148. return "", errors.Wrap(err, "getOnpremiseZoneIds")
  149. }
  150. if len(zoneIds) == 1 {
  151. return zoneIds[0], nil
  152. } else if len(zoneIds) == 0 {
  153. return "", errors.Wrap(httperrors.ErrNotFound, "no valid on-premise zone")
  154. }
  155. // there are multiple zones
  156. zoneIds = make([]string, 0)
  157. for _, ip := range hostIps {
  158. for _, net := range onPremiseNets {
  159. if net.IsAddressInRange(ip) || net.IsAddressInNet(ip) {
  160. zone, _ := net.GetZone()
  161. if zone != nil && !utils.IsInStringArray(zone.Id, zoneIds) {
  162. zoneIds = append(zoneIds, zone.Id)
  163. }
  164. }
  165. }
  166. }
  167. if len(zoneIds) == 0 {
  168. // no any clue
  169. return "", errors.Wrap(httperrors.ErrNotFound, "no valid on-premise networks")
  170. }
  171. if len(zoneIds) > 1 {
  172. // network span multiple zones
  173. return "", errors.Wrap(httperrors.ErrConflict, "spans multiple zones?")
  174. }
  175. return zoneIds[0], nil
  176. }
  177. type sIpv4List []netutils.IPV4Addr
  178. func (a sIpv4List) Len() int { return len(a) }
  179. func (a sIpv4List) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  180. func (a sIpv4List) Less(i, j int) bool { return uint32(a[i]) < uint32(a[j]) }
  181. func guessEsxiNetworks(hostIps []netutils.IPV4Addr, accountName string, onPremiseNets []SNetwork) ([]CANetConf, error) {
  182. netConfs := make([]CANetConf, 0)
  183. ipList := make([]netutils.IPV4Addr, 0)
  184. for j := 0; j < len(hostIps); j++ {
  185. var net *SNetwork
  186. for _, n := range onPremiseNets {
  187. if n.IsAddressInRange(hostIps[j]) {
  188. // already covered by existing network
  189. net = &n
  190. break
  191. }
  192. }
  193. if net != nil {
  194. // found a network contains this IP, no need to create
  195. continue
  196. }
  197. ipList = append(ipList, hostIps[j])
  198. }
  199. if len(ipList) == 0 {
  200. // no need to create network
  201. return nil, nil
  202. }
  203. sort.Sort(sIpv4List(ipList))
  204. for i := 0; i < len(ipList); {
  205. simNetConfs := CASimpleNetConf{}
  206. var net *SNetwork
  207. for _, n := range onPremiseNets {
  208. if n.IsAddressInNet(ipList[i]) {
  209. // already covered by existing network
  210. net = &n
  211. break
  212. }
  213. }
  214. if net != nil {
  215. simNetConfs.WireId = net.WireId
  216. simNetConfs.Gateway = net.GuestGateway
  217. simNetConfs.IpMask = net.GuestIpMask
  218. } else {
  219. simNetConfs.Gateway = (ipList[i].NetAddr(ipMaskLen) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String()
  220. simNetConfs.IpMask = ipMaskLen
  221. }
  222. netRange := netutils.NewIPV4AddrRange(ipList[i].NetAddr(simNetConfs.IpMask), ipList[i].BroadcastAddr(simNetConfs.IpMask))
  223. j := i
  224. for j < len(ipList)-1 {
  225. if ipList[j]+1 == ipList[j+1] && netRange.Contains(ipList[j+1]) {
  226. j++
  227. } else {
  228. break
  229. }
  230. }
  231. simNetConfs.IpStart = ipList[i].String()
  232. simNetConfs.IpEnd = ipList[j].String()
  233. i = j + 1
  234. netConfs = append(netConfs, CANetConf{
  235. Name: fmt.Sprintf("%s-esxi-host-network", accountName),
  236. Description: fmt.Sprintf("Auto created network for cloudaccount %q", accountName),
  237. CASimpleNetConf: simNetConfs,
  238. })
  239. }
  240. return netConfs, nil
  241. }
  242. func (account *SCloudaccount) createNetworks(ctx context.Context, accountName string, zoneId string, netConfs []CANetConf) error {
  243. var err error
  244. var defWireId string
  245. for i := range netConfs {
  246. var wireId = netConfs[i].WireId
  247. if len(wireId) == 0 {
  248. if len(defWireId) == 0 {
  249. // need to create one
  250. name := fmt.Sprintf("%s-wire", accountName)
  251. desc := fmt.Sprintf("Auto created wire for cloudaccount %q", accountName)
  252. wireId, err = account.createWire(ctx, api.DEFAULT_VPC_ID, zoneId, name, desc)
  253. if err != nil {
  254. return errors.Wrapf(err, "can't create wire %s", name)
  255. }
  256. defWireId = wireId
  257. }
  258. netConfs[i].WireId = defWireId
  259. wireId = defWireId
  260. }
  261. err := account.createNetwork(ctx, wireId, api.NETWORK_TYPE_BAREMETAL, netConfs[i])
  262. if err != nil {
  263. return errors.Wrapf(err, "can't create network %s", jsonutils.Marshal(netConfs[i]))
  264. }
  265. }
  266. return nil
  267. }
  268. // NETWORK_TYPE_GUEST = "guest"
  269. // NETWORK_TYPE_BAREMETAL = "baremetal"
  270. func (account *SCloudaccount) createNetwork(ctx context.Context, wireId string, networkType api.TNetworkType, net CANetConf) error {
  271. network := &SNetwork{}
  272. if hint, err := NetworkManager.NewIfnameHint(net.Name); err != nil {
  273. log.Errorf("can't NewIfnameHint form hint %s", net.Name)
  274. } else {
  275. network.IfnameHint = hint
  276. }
  277. network.GuestIpStart = net.IpStart
  278. network.GuestIpEnd = net.IpEnd
  279. network.GuestIpMask = net.IpMask
  280. network.GuestGateway = net.Gateway
  281. network.VlanId = int(net.VlanID)
  282. network.WireId = wireId
  283. network.ServerType = networkType
  284. network.IsPublic = true
  285. network.Status = api.NETWORK_STATUS_AVAILABLE
  286. network.PublicScope = string(rbacscope.ScopeDomain)
  287. network.ProjectId = account.ProjectId
  288. network.DomainId = account.DomainId
  289. network.Description = net.Description
  290. lockman.LockClass(ctx, NetworkManager, network.ProjectId)
  291. defer lockman.ReleaseClass(ctx, NetworkManager, network.ProjectId)
  292. ownerId := network.GetOwnerId()
  293. nName, err := db.GenerateName(ctx, NetworkManager, ownerId, net.Name)
  294. if err != nil {
  295. return errors.Wrap(err, "GenerateName")
  296. }
  297. network.Name = nName
  298. network.SetModelManager(NetworkManager, network)
  299. // TODO: Prevent IP conflict
  300. log.Infof("create network %s succussfully", network.Id)
  301. err = NetworkManager.TableSpec().Insert(ctx, network)
  302. if err != nil {
  303. return errors.Wrap(err, "Insert")
  304. }
  305. return nil
  306. }
  307. func (account *SCloudaccount) createWire(ctx context.Context, vpcId, zoneId, wireName, desc string) (string, error) {
  308. wire := &SWire{
  309. Bandwidth: 10000,
  310. Mtu: 1500,
  311. }
  312. wire.VpcId = vpcId
  313. wire.ZoneId = zoneId
  314. wire.IsEmulated = false
  315. wire.DomainId = account.GetOwnerId().GetDomainId()
  316. wire.Description = desc
  317. wire.Status = api.WIRE_STATUS_AVAILABLE
  318. wire.SetModelManager(WireManager, wire)
  319. lockman.LockClass(ctx, WireManager, wire.DomainId)
  320. defer lockman.ReleaseClass(ctx, WireManager, wire.DomainId)
  321. ownerId := wire.GetOwnerId()
  322. wName, err := db.GenerateName(ctx, WireManager, ownerId, wireName)
  323. if err != nil {
  324. return "", errors.Wrap(err, "GenerateName")
  325. }
  326. wire.Name = wName
  327. err = WireManager.TableSpec().Insert(ctx, wire)
  328. if err != nil {
  329. return "", errors.Wrap(err, "Insert")
  330. }
  331. log.Infof("create wire %s succussfully", wire.GetId())
  332. return wire.GetId(), nil
  333. }