Skip to content

Commit 267c302

Browse files
committed
populate NetworkAdapters in VM list, add syncInterfaces with dirty-tracking and correct network resolution
1 parent 31dbf9d commit 267c302

2 files changed

Lines changed: 123 additions & 12 deletions

File tree

src/main/groovy/com/morpheusdata/scvmm/ScvmmApiService.groovy

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -575,14 +575,32 @@ if(\$cloud) {
575575
}
576576
577577
\$VNAs = \$VM | Get-SCVirtualNetworkAdapter
578-
foreach (\$VNA in \$VNAs) {
579-
foreach (\$ip in \$VNA.IPv4Addresses) {
580-
if([string]::IsNullOrEmpty(\$data.IpAddress)) {
581-
\$data.IpAddress = \$ip
582-
\$data.InternalIp = \$ip
583-
}
584-
}
585-
}
578+
foreach ($VNA in $VNAs) {
579+
# If slotId = 0 set data.ipAddress and data.internalIp from IPv4 or IPv6
580+
if ($VNA.SlotId -eq 0) {
581+
if ($VNA.IPv4Addresses.Count -gt 0) {
582+
$data.IpAddress = $VNA.IPv4Addresses[0]
583+
$data.InternalIp = $VNA.IPv4Addresses[0]
584+
} elseif ($VNA.IPv6Addresses.Count -gt 0) {
585+
$data.IpAddress = $VNA.IPv6Addresses[0]
586+
$data.InternalIp = $VNA.IPv6Addresses[0]
587+
}
588+
}
589+
$nic = New-Object PSObject -property @{
590+
ID = $VNA.ID
591+
Name = $VNA.Name
592+
IPv4Addresses = @($VNA.IPv4Addresses)
593+
IPv4AddressType = $VNA.IPv4AddressType.ToString()
594+
IPv6Addresses = @($VNA.IPv6Addresses)
595+
IPv6AddressType = $VNA.IPv6AddressType.ToString()
596+
MacAddress = $VNA.MacAddress
597+
VirtualNetworkId = $VNA.VMNetwork.ID
598+
VLanID = $VNA.VLanID
599+
SlotId = $VNA.SlotId
600+
Enabled = $VNA.Enabled
601+
}
602+
$data.NetworkAdapters += $nic
603+
}
586604
587605
\$report +=\$data
588606
}

src/main/groovy/com/morpheusdata/scvmm/sync/VirtualMachineSync.groovy

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ class VirtualMachineSync {
6666
))
6767
}
6868
def serverType = context.async.cloud.findComputeServerTypeByCode("scvmmUnmanaged").blockingGet()
69+
def systemNetworks = context.services.cloud.network.list(
70+
new DataQuery().withFilter('category', '=~', "scvmm.network.${cloud.id}.%")
71+
)?.collectEntries { [(it.externalId): it] } ?: [:]
6972

7073
def existingVms = context.async.computeServer.listIdentityProjections(new DataQuery()
7174
.withFilter('zone.id', cloud.id)
@@ -83,10 +86,10 @@ class VirtualMachineSync {
8386
}
8487
}.onAdd { itemsToAdd ->
8588
if (createNew) {
86-
addMissingVirtualMachines(itemsToAdd, availablePlans, fallbackPlan, availablePlanPermissions, hosts, consoleEnabled, serverType)
89+
addMissingVirtualMachines(itemsToAdd, availablePlans, fallbackPlan, availablePlanPermissions, hosts, consoleEnabled, serverType, systemNetworks)
8790
}
8891
}.onUpdate { List<SyncTask.UpdateItem<ComputeServer, Map>> updateItems ->
89-
updateMatchedVirtualMachines(updateItems, availablePlans, fallbackPlan, hosts, consoleEnabled, serverType)
92+
updateMatchedVirtualMachines(updateItems, availablePlans, fallbackPlan, hosts, consoleEnabled, serverType, systemNetworks, systemNetworks)
9093
}.onDelete { List<ComputeServerIdentityProjection> removeItems ->
9194
removeMissingVirtualMachines(removeItems)
9295
}.observe().blockingSubscribe()
@@ -96,7 +99,8 @@ class VirtualMachineSync {
9699
}
97100
}
98101

99-
def addMissingVirtualMachines(List addList, Collection<ServicePlan> availablePlans, ServicePlan fallbackPlan, Collection<ResourcePermission> availablePlanPermissions, List hosts, Boolean consoleEnabled, ComputeServerType defaultServerType) {
102+
def addMissingVirtualMachines(List addList, Collection<ServicePlan> availablePlans, ServicePlan fallbackPlan, Collection<ResourcePermission> availablePlanPermissions, List hosts, Boolean consoleEnabled, ComputeServerType defaultServerType, Map systemNetworks) {
103+
log.debug("addMissingVirtualMachines: ${cloud} ${addList.size()}")
100104
try {
101105
for (cloudItem in addList) {
102106
log.debug "Adding new virtual machine: ${cloudItem.Name}"
@@ -140,6 +144,7 @@ class VirtualMachineSync {
140144
log.error "error adding new virtual machine: ${add}"
141145
} else {
142146
syncVolumes(savedServer, cloudItem.Disks)
147+
syncInterfaces(savedServer, cloudItem.NetworkAdapters, systemNetworks)
143148
}
144149
}
145150
} catch (ex) {
@@ -148,7 +153,7 @@ class VirtualMachineSync {
148153
}
149154

150155
protected updateMatchedVirtualMachines(List<SyncTask.UpdateItem<ComputeServer, Map>> updateList, availablePlans, fallbackPlan,
151-
List<ComputeServer> hosts, consoleEnabled, ComputeServerType defaultServerType) {
156+
List<ComputeServer> hosts, consoleEnabled, ComputeServerType defaultServerType, Map systemNetworks, Map<String, Network> existingSystemNetworks) {
152157
log.debug("VirtualMachineSync >> updateMatchedVirtualMachines() called")
153158
try {
154159
def matchedServers = context.services.computeServer.list(new DataQuery().withFilter('id', 'in', updateList.collect { up -> up.existingItem.id })
@@ -288,6 +293,11 @@ class VirtualMachineSync {
288293
syncVolumes(currentServer, masterItem.Disks)
289294
}
290295
}
296+
if (masterItem.NetworkAdapters) {
297+
if (syncInterfaces(currentServer, masterItem.NetworkAdapters, systemNetworks)) {
298+
save = true
299+
}
300+
}
291301
log.debug ("updateMatchedVirtualMachines: save: ${save}")
292302
if (save) {
293303
saves << currentServer
@@ -609,4 +619,87 @@ class VirtualMachineSync {
609619
return "data-${index}"
610620
}
611621
}
622+
623+
private boolean syncInterfaces(ComputeServer server, List networkAdapters, Map systemNetworks) {
624+
def changed = false
625+
try {
626+
def masterItems = networkAdapters?.findAll { it } ?: []
627+
def existingInterfaces = server.interfaces?.findAll { it } ?: []
628+
boolean isPrimaryAssigned = existingInterfaces.any { it.primaryInterface }
629+
630+
def masterById = masterItems.collectEntries { [(it.ID): it] }
631+
def existingById = existingInterfaces.findAll { it.externalId }.collectEntries { [(it.externalId): it] }
632+
633+
// remove NICs no longer reported by SCVMM
634+
existingInterfaces.findAll { !masterById.containsKey(it.externalId) }.each { iface ->
635+
context.async.computeServer.computeServerInterface.remove([iface]).blockingGet()
636+
changed = true
637+
}
638+
639+
masterItems.each { masterItem ->
640+
def existing = existingById[masterItem.ID]
641+
def network = resolveNetworkForAdapter(masterItem, systemNetworks, existing?.network)
642+
def isPrimary = getIsPrimary(existing, masterItem, isPrimaryAssigned)
643+
def dhcp = (masterItem.IPv4AddressType == 'Dynamic' || masterItem.IPv6AddressType == 'Dynamic')
644+
def allIps = ((masterItem.IPv4Addresses ?: []).findAll { it }) + ((masterItem.IPv6Addresses ?: []).findAll { it })
645+
646+
if (existing) {
647+
def save = false
648+
if (existing.macAddress != masterItem.MacAddress) { existing.macAddress = masterItem.MacAddress; save = true }
649+
if (existing.vlanId != masterItem.VLanID?.toString()) { existing.vlanId = masterItem.VLanID?.toString(); save = true }
650+
if (existing.network?.id != network?.id) { existing.network = network; save = true }
651+
if (existing.dhcp != dhcp) { existing.dhcp = dhcp; save = true }
652+
if (existing.primaryInterface != isPrimary) { existing.primaryInterface = isPrimary; save = true }
653+
def existingIps = existing.addresses?.collect { it.address } as Set ?: [] as Set
654+
if (existingIps != (allIps as Set)) {
655+
existing.addresses = allIps.collect { ip ->
656+
def type = (masterItem.IPv4Addresses ?: []).contains(ip) ? NetAddress.AddressType.IPV4 : NetAddress.AddressType.IPV6
657+
new NetAddress(type: type, address: ip)
658+
}
659+
save = true
660+
}
661+
if (save) {
662+
context.async.computeServer.computeServerInterface.save([existing]).blockingGet()
663+
changed = true
664+
}
665+
} else {
666+
def iface = new ComputeServerInterface(
667+
externalId: masterItem.ID,
668+
name: server.sourceImage?.interfaceName ?: 'eth0',
669+
macAddress: masterItem.MacAddress,
670+
vlanId: masterItem.VLanID?.toString(),
671+
network: network,
672+
primaryInterface: isPrimary,
673+
dhcp: dhcp
674+
)
675+
allIps.each { ip ->
676+
def type = (masterItem.IPv4Addresses ?: []).contains(ip) ? NetAddress.AddressType.IPV4 : NetAddress.AddressType.IPV6
677+
iface.addresses += new NetAddress(type: type, address: ip)
678+
}
679+
context.async.computeServer.computeServerInterface.create([iface], server).blockingGet()
680+
if (isPrimary) isPrimaryAssigned = true
681+
changed = true
682+
}
683+
}
684+
} catch (e) {
685+
log.error("syncInterfaces error: ${e}", e)
686+
}
687+
return changed
688+
}
689+
690+
private Network resolveNetworkForAdapter(Map masterItem, Map systemNetworks, Network existingNetwork = null) {
691+
def vlanSuffix = masterItem.VLanID ? '-' + masterItem.VLanID : ''
692+
def masterItemNetID = (masterItem.VirtualNetworkId ?: '') + vlanSuffix
693+
def alternateId = masterItem.ID + vlanSuffix
694+
def network = existingNetwork ?: systemNetworks[masterItemNetID]
695+
if (network?.externalId != masterItemNetID && systemNetworks?.get(alternateId)) {
696+
network = systemNetworks[alternateId]
697+
}
698+
return network
699+
}
700+
701+
private boolean getIsPrimary(ComputeServerInterface iface, Map masterItem, boolean isPrimaryAssigned) {
702+
return (iface?.primaryInterface == true) || (masterItem.SlotId == 0 && !isPrimaryAssigned)
703+
}
704+
612705
}

0 commit comments

Comments
 (0)