diff --git a/scripts/kvm/async_job/apply_second_ips.sh b/scripts/kvm/async_job/apply_second_ips.sh index f0a9a40a..b0c85b77 100755 --- a/scripts/kvm/async_job/apply_second_ips.sh +++ b/scripts/kvm/async_job/apply_second_ips.sh @@ -3,40 +3,50 @@ cd `dirname $0` source ../../cloudrc -[ $# -lt 4 ] && echo "$0 " && exit -1 +[ $# -lt 4 ] && echo "$0 [primary_ip] [gateway]" && exit -1 ID=$1 vm_ID=inst-$ID mac=$2 os_code=$3 update_meta=$4 +primary_ip=$5 +gateway=${6%/*} more_addresses=$(cat) -naddrs=$(jq length <<< $more_addresses) +naddrs=$(jq length <<< "$more_addresses") -i=0 -while [ $i -lt $naddrs ]; do - read -d'\n' -r address < <(jq -r ".[$i]" <<<$more_addresses) - read -d'\n' -r ip netmask < <(ipcalc -nb $address | awk '/Address/ {print $2} /Netmask/ {print $2}') - second_addrs_json="$second_addrs_json,{ - \"type\": \"ipv4\", - \"ip_address\": \"$ip\", - \"netmask\": \"$netmask\", - \"link\": \"eth0\", - \"id\": \"network0\" - }" - let i=$i+1 -done +if [ "$update_meta" != true ]; then + log_debug "no need to update any ip addresses" + exit 0 +fi if [ "$os_code" = "windows" ]; then wait_qemu_ping $ID 10 + if [ -n "$primary_ip" ]; then + read -d'\n' -r ip netmask < <(ipcalc -nb $primary_ip | awk '/Address/ {print $2} /Netmask/ {print $2}') + virsh qemu-agent-command "$vm_ID" '{"execute":"guest-exec","arguments":{"path":"C:\\Windows\\System32\\netsh.exe","arg":["interface","ipv4","set","address","name=eth0","addr='"$ip"'","mask='"$netmask"'"],"capture-output":true}}' + fi i=0 while [ $i -lt $naddrs ]; do - read -d'\n' -r address < <(jq -r ".[$i]" <<<$more_addresses) + read -d'\n' -r address < <(jq -r ".[$i]" <<< "$more_addresses") read -d'\n' -r ip netmask < <(ipcalc -nb $address | awk '/Address/ {print $2} /Netmask/ {print $2}') virsh qemu-agent-command "$vm_ID" '{"execute":"guest-exec","arguments":{"path":"C:\\Windows\\System32\\netsh.exe","arg":["interface","ipv4","add","address","name=eth0","addr='"$ip"'","mask='"$netmask"'"],"capture-output":true}}' let i=$i+1 done -elif [ "$os_code" = "linux" -a "$update_meta" = "true" ]; then +elif [ "$os_code" = "linux" ]; then + i=0 + while [ $i -lt $naddrs ]; do + read -d'\n' -r address < <(jq -r ".[$i]" <<< "$more_addresses") + read -d'\n' -r ip netmask < <(ipcalc -nb $address | awk '/Address/ {print $2} /Netmask/ {print $2}') + second_addrs_json="$second_addrs_json,{ + \"type\": \"ipv4\", + \"ip_address\": \"$ip\", + \"netmask\": \"$netmask\", + \"link\": \"eth0\", + \"id\": \"network0\" + }" + let i=$i+1 + done tmp_mnt=/tmp/mnt-$vm_ID working_dir=/tmp/$vm_ID latest_dir=$working_dir/openstack/latest @@ -44,6 +54,10 @@ elif [ "$os_code" = "linux" -a "$update_meta" = "true" ]; then mount ${cache_dir}/meta/${vm_ID}.iso $tmp_mnt cp -r $tmp_mnt/* $working_dir net_json=$(cat $latest_dir/network_data.json) + if [ -n "$primary_ip" ]; then + read -d'\n' -r ip netmask < <(ipcalc -nb $primary_ip | awk '/Address/ {print $2} /Netmask/ {print $2}') + net_json=$(jq --arg ip "$ip" --arg netmask "$netmask" --arg gateway "$gateway" '.networks[] |= (select(.id == "network0" and .routes[0].network == "0.0.0.0") | .ip_address = $ip | .netmask = $netmask | .routes[0].gateway = $gateway)' <<< "$net_json") + fi networks="[$(jq -r '.networks[] | select(.id == "network0" and .routes[0].network == "0.0.0.0")' <<<$net_json)$second_addrs_json]" echo "$net_json" | jq --argjson new_networks "$networks" '.networks |= (map(select(.id != "network0")) + $new_networks)' >$latest_dir/network_data.json umount $tmp_mnt diff --git a/scripts/kvm/attach_vm_nic.sh b/scripts/kvm/attach_vm_nic.sh index 82a49ed0..35c42841 100755 --- a/scripts/kvm/attach_vm_nic.sh +++ b/scripts/kvm/attach_vm_nic.sh @@ -35,7 +35,7 @@ async_exec ./send_spoof_arp.py "$vm_br" "${ip%/*}" "$mac" ./set_subnet_gw.sh "$router" "$vlan" "$gateway" "$ext_vlan" ./set_host.sh "$router" "$vlan" "$mac" "$vm_name" "$ip" more_addresses=$(jq -r .more_addresses <<< $vlan_info) -if [ -n "$more_addresses" ]; then - ./apply_second_ips.sh "$ID" "$mac" "$os_code" "$update_meta" <<<$more_addresses +if [ -n "$more_addresses" -o "$update_meta" = true ]; then + ./apply_second_ips.sh "$ID" "$mac" "$os_code" "$update_meta" "$ip" "$gateway" <<<$more_addresses fi echo "|:-COMMAND-:| $(basename $0) '$ID' '$mac' '$SCI_CLIENT_ID'" diff --git a/scripts/kvm/sync_nic_info.sh b/scripts/kvm/sync_nic_info.sh index 61e39a53..01106bf6 100755 --- a/scripts/kvm/sync_nic_info.sh +++ b/scripts/kvm/sync_nic_info.sh @@ -3,7 +3,7 @@ cd `dirname $0` source ../cloudrc -[ $# -lt 3 ] && echo "$0 " && exit -1 +[ $# -lt 3 ] && echo "$0 [update_meta]" && exit -1 ID=$1 vm_name=$2 diff --git a/web/docs/alarm_rules_manager/alarm_v1_docs.go b/web/docs/alarm_rules_manager/alarm_v1_docs.go index 782b68ee..daf3e71b 100644 --- a/web/docs/alarm_rules_manager/alarm_v1_docs.go +++ b/web/docs/alarm_rules_manager/alarm_v1_docs.go @@ -6032,11 +6032,14 @@ const docTemplatealarm_v1 = `{ "minimum": 1 }, "disk_bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "disk_iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 }, "flavor": { @@ -6283,6 +6286,9 @@ const docTemplatealarm_v1 = `{ "maximum": 20000, "minimum": 0 }, + "primary_address": { + "$ref": "#/definitions/common.BaseReference" + }, "public_addresses": { "type": "array", "items": { @@ -7789,7 +7795,9 @@ const docTemplatealarm_v1 = `{ "minimum": 0 }, "bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "count": { @@ -7803,6 +7811,7 @@ const docTemplatealarm_v1 = `{ }, "iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 }, "name": { @@ -7820,11 +7829,14 @@ const docTemplatealarm_v1 = `{ "type": "object", "properties": { "bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 } } diff --git a/web/docs/alarm_rules_manager/alarm_v1_swagger.json b/web/docs/alarm_rules_manager/alarm_v1_swagger.json index afd9cefb..b3d4f86e 100644 --- a/web/docs/alarm_rules_manager/alarm_v1_swagger.json +++ b/web/docs/alarm_rules_manager/alarm_v1_swagger.json @@ -6026,11 +6026,14 @@ "minimum": 1 }, "disk_bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "disk_iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 }, "flavor": { @@ -6277,6 +6280,9 @@ "maximum": 20000, "minimum": 0 }, + "primary_address": { + "$ref": "#/definitions/common.BaseReference" + }, "public_addresses": { "type": "array", "items": { @@ -7783,7 +7789,9 @@ "minimum": 0 }, "bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "count": { @@ -7797,6 +7805,7 @@ }, "iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 }, "name": { @@ -7814,11 +7823,14 @@ "type": "object", "properties": { "bps_limit": { + "description": "in MB/s", "type": "integer", + "maximum": 102400, "minimum": 0 }, "iops_limit": { "type": "integer", + "maximum": 10000000, "minimum": 0 } } diff --git a/web/docs/alarm_rules_manager/alarm_v1_swagger.yaml b/web/docs/alarm_rules_manager/alarm_v1_swagger.yaml index 678b482b..531158c5 100644 --- a/web/docs/alarm_rules_manager/alarm_v1_swagger.yaml +++ b/web/docs/alarm_rules_manager/alarm_v1_swagger.yaml @@ -718,9 +718,12 @@ definitions: minimum: 1 type: integer disk_bps_limit: + description: in MB/s + maximum: 102400 minimum: 0 type: integer disk_iops_limit: + maximum: 10000000 minimum: 0 type: integer flavor: @@ -900,6 +903,8 @@ definitions: maximum: 20000 minimum: 0 type: integer + primary_address: + $ref: '#/definitions/common.BaseReference' public_addresses: items: $ref: '#/definitions/common.BaseReference' @@ -1912,6 +1917,8 @@ definitions: minimum: 0 type: integer bps_limit: + description: in MB/s + maximum: 102400 minimum: 0 type: integer count: @@ -1922,6 +1929,7 @@ definitions: minimum: 0 type: integer iops_limit: + maximum: 10000000 minimum: 0 type: integer name: @@ -1937,9 +1945,12 @@ definitions: apis.VolumeQosPayload: properties: bps_limit: + description: in MB/s + maximum: 102400 minimum: 0 type: integer iops_limit: + maximum: 10000000 minimum: 0 type: integer type: object diff --git a/web/docs/routes/v1_docs.go b/web/docs/routes/v1_docs.go index 8920b918..a1560355 100644 --- a/web/docs/routes/v1_docs.go +++ b/web/docs/routes/v1_docs.go @@ -6225,6 +6225,9 @@ const docTemplatev1 = `{ "maximum": 20000, "minimum": 0 }, + "primary_address": { + "$ref": "#/definitions/common.BaseReference" + }, "public_addresses": { "type": "array", "items": { diff --git a/web/docs/routes/v1_swagger.json b/web/docs/routes/v1_swagger.json index e23d8d76..be276a20 100644 --- a/web/docs/routes/v1_swagger.json +++ b/web/docs/routes/v1_swagger.json @@ -6218,6 +6218,9 @@ "maximum": 20000, "minimum": 0 }, + "primary_address": { + "$ref": "#/definitions/common.BaseReference" + }, "public_addresses": { "type": "array", "items": { diff --git a/web/docs/routes/v1_swagger.yaml b/web/docs/routes/v1_swagger.yaml index 13ce4fd8..47414cd3 100644 --- a/web/docs/routes/v1_swagger.yaml +++ b/web/docs/routes/v1_swagger.yaml @@ -903,6 +903,8 @@ definitions: maximum: 20000 minimum: 0 type: integer + primary_address: + $ref: '#/definitions/common.BaseReference' public_addresses: items: $ref: '#/definitions/common.BaseReference' diff --git a/web/src/apis/interface.go b/web/src/apis/interface.go index 3420b1f2..f90c03da 100644 --- a/web/src/apis/interface.go +++ b/web/src/apis/interface.go @@ -70,6 +70,7 @@ type InterfacePatchPayload struct { Name string `json:"name" binding:"omitempty,min=2,max=32"` Inbound *int32 `json:"inbound" binding:"omitempty,min=0,max=20000"` Outbound *int32 `json:"outbound" binding:"omitempty,min=0,max=20000"` + PrimaryAddress *BaseReference `json:"primary_address,omitempty"` PublicAddresses []*BaseReference `json:"public_addresses,omitempty"` Subnets []*BaseReference `json:"subnets" binding:"omitempty,gte=1,lte=32"` Count *int `json:"count" binding:"omitempty,gte=1,lte=512"` @@ -309,6 +310,8 @@ func (v *InterfaceAPI) Patch(c *gin.Context) { var floatingIp *model.FloatingIp floatingIp, err = floatingIpAdmin.GetFloatingIpByUUID(ctx, pubAddr.ID) if err != nil { + logger.Errorf("Failed to get public ip") + ErrorResponse(c, http.StatusBadRequest, "Failed to get public ip", err) return } publicIps = append(publicIps, floatingIp) @@ -346,6 +349,29 @@ func (v *InterfaceAPI) Patch(c *gin.Context) { } siteSubnets = append(siteSubnets, siteSubnet) } + if payload.PrimaryAddress != nil { + if !iface.PrimaryIf { + logger.Errorf("It is not allowed to update ip of secondary interface") + ErrorResponse(c, http.StatusBadRequest, "It is not allowed to update ip of secondary interface", err) + return + } + var primaryFip *model.FloatingIp + primaryFip, err = floatingIpAdmin.GetFloatingIpByUUID(ctx, payload.PrimaryAddress.ID) + if err != nil { + logger.Errorf("Failed to get primary public ip") + ErrorResponse(c, http.StatusBadRequest, "Failed to get primary public ip", err) + return + } + if primaryFip.ID != iface.FloatingIp { + for i, pubAddr := range publicIps { + if primaryFip.ID == pubAddr.ID { + publicIps = append(publicIps[:i], publicIps[i+1:]...) + break + } + } + publicIps = append([]*model.FloatingIp{primaryFip}, publicIps...) + } + } err = interfaceAdmin.Update(ctx, instance, iface, ifaceName, inbound, outbound, allowSpoofing, secgroups, ifaceSubnets, siteSubnets, count, publicIps) if err != nil { logger.Errorf("Patch instance failed, %+v", err) diff --git a/web/src/common/interface.go b/web/src/common/interface.go index 496dd64d..1d0eb825 100644 --- a/web/src/common/interface.go +++ b/web/src/common/interface.go @@ -177,7 +177,7 @@ func DeallocateAddress(ctx context.Context, ifaces []*model.Interface) (err erro return } -func genMacaddr() (mac string, err error) { +func GenerateMacaddr() (mac string, err error) { buf := make([]byte, 4) _, err = rand.Read(buf) if err != nil { @@ -188,11 +188,13 @@ func genMacaddr() (mac string, err error) { return mac, nil } -func DerivePublicInterface(ctx context.Context, instance *model.Instance, iface *model.Interface, floatingIps []*model.FloatingIp) (primaryIface *model.Interface, primarySubnet *model.Subnet, err error) { +func DerivePublicInterface(ctx context.Context, instance *model.Instance, iface *model.Interface, floatingIps []*model.FloatingIp, primaryMac string) (primaryIface *model.Interface, primarySubnet *model.Subnet, err error) { ctx, db := GetContextDB(ctx) primaryIface = iface + updatePrimary := false if primaryIface == nil { primaryIface = floatingIps[0].Interface + updatePrimary = true } primarySubnet = primaryIface.Address.Subnet for _, address := range primaryIface.SecondAddresses { @@ -227,7 +229,7 @@ func DerivePublicInterface(ctx context.Context, instance *model.Instance, iface } primaryIface.SecondAddresses = nil for i, fip := range floatingIps { - if fip.InstanceID > 0 { + if !updatePrimary && fip.InstanceID > 0 { continue } fip.Instance = instance @@ -236,6 +238,9 @@ func DerivePublicInterface(ctx context.Context, instance *model.Instance, iface primaryIface.Instance = instance.ID primaryIface.Name = "eth0" primaryIface.PrimaryIf = true + if primaryMac != "" { + primaryIface.MacAddr = primaryMac + } err = db.Model(primaryIface).Updates(primaryIface).Error if err != nil { logger.Errorf("Failed to update interface, %v", err) @@ -288,7 +293,7 @@ func CreateInterface(ctx context.Context, subnet *model.Subnet, ID, owner int64, primary = true } if mac == "" { - mac, err = genMacaddr() + mac, err = GenerateMacaddr() if err != nil { logger.Error("Failed to generate random Mac address, %v", err) return diff --git a/web/src/routes/instance.go b/web/src/routes/instance.go index 0ebfc43b..c52760db 100644 --- a/web/src/routes/instance.go +++ b/web/src/routes/instance.go @@ -741,7 +741,7 @@ func (a *InstanceAdmin) createInterface(ctx context.Context, ifaceInfo *Interfac memberShip := GetMemberShip(ctx) if len(ifaceInfo.PublicIps) > 0 { - iface, ifaceSubnet, err = DerivePublicInterface(ctx, instance, nil, ifaceInfo.PublicIps) + iface, ifaceSubnet, err = DerivePublicInterface(ctx, instance, nil, ifaceInfo.PublicIps, "") if err != nil { logger.Error("Failed to derive primary interface", err) return diff --git a/web/src/routes/interface.go b/web/src/routes/interface.go index 46c9681a..7bf7dc32 100644 --- a/web/src/routes/interface.go +++ b/web/src/routes/interface.go @@ -165,10 +165,12 @@ func (a *InterfaceAdmin) checkAddresses(ctx context.Context, iface *model.Interf } for i, pubIp := range publicIps { if vlan != pubIp.Interface.Address.Subnet.Vlan { + changed = true return } if i == 0 { if pubIp.FipAddress != iface.Address.Address { + changed = true logger.Errorf("pubIp.FipAddress: %s, iface.Address.Address: %s, %d", pubIp.FipAddress, iface.Address.Address, i) return } @@ -176,6 +178,7 @@ func (a *InterfaceAdmin) checkAddresses(ctx context.Context, iface *model.Interf if (i - 1) < secondIpsLength { secondAddr := iface.SecondAddresses[i-1].Address if pubIp.FipAddress != secondAddr { + changed = true logger.Errorf("pubIp.FipAddress: %s, iface.Address.Address: %s, %d", pubIp.FipAddress, secondAddr, i) return } @@ -188,6 +191,7 @@ func (a *InterfaceAdmin) checkAddresses(ctx context.Context, iface *model.Interf } for _, subnet := range ifaceSubnets { if vlan != subnet.Vlan { + changed = true return } } @@ -197,6 +201,7 @@ func (a *InterfaceAdmin) checkAddresses(ctx context.Context, iface *model.Interf } for _, site := range siteSubnets { if vlan != site.Vlan { + changed = true return } found := false @@ -247,7 +252,7 @@ func (a *InterfaceAdmin) allocateSecondAddresses(ctx context.Context, instance * return } -func (a *InterfaceAdmin) changeAddresses(ctx context.Context, instance *model.Instance, iface *model.Interface, ifaceSubnets, siteSubnets []*model.Subnet, secondAddrsCount int, publicIps []*model.FloatingIp) (err error) { +func (a *InterfaceAdmin) changeAddresses(ctx context.Context, instance *model.Instance, iface *model.Interface, ifaceSubnets, siteSubnets []*model.Subnet, secondAddrsCount int, publicIps []*model.FloatingIp, secgroups []*model.SecurityGroup) (iface2 *model.Interface, err error) { ctx, db := GetContextDB(ctx) for _, site := range iface.SiteSubnets { err = db.Model(site).Updates(map[string]interface{}{"interface": 0}).Error @@ -258,22 +263,53 @@ func (a *InterfaceAdmin) changeAddresses(ctx context.Context, instance *model.In } } iface.SiteSubnets = nil - for _, site := range siteSubnets { - err = db.Model(site).Updates(map[string]interface{}{"interface": iface.ID}).Error - if err != nil { - logger.Error("Failed to update interface", err) - err = NewCLError(ErrSiteSubnetUpdateFailed, "Failed to update interface", err) - return - } - iface.SiteSubnets = append(iface.SiteSubnets, site) - } if len(publicIps) > 0 { - iface, _, err = DerivePublicInterface(ctx, instance, iface, publicIps) + primaryMac := iface.MacAddr + if iface.FloatingIp != publicIps[0].ID { + var floatingIp *model.FloatingIp + floatingIp, err = floatingIpAdmin.Get(ctx, iface.FloatingIp) + if err != nil { + logger.Errorf("Failed to get floating ip, %v", err) + return + } + err = floatingIpAdmin.Detach(ctx, floatingIp) + if err != nil { + logger.Errorf("Failed to detach floating ip, %v", err) + return + } + if err = db.Model(iface).Association("Security_Groups").Replace([]*model.SecurityGroup{}).Error; err != nil { + logger.Debug("Failed to save interface", err) + return + } + mac := "" + mac, err = GenerateMacaddr() + if err != nil { + logger.Error("Failed to generate random Mac address, %v", err) + return + } + err = db.Model(iface).Update(map[string]interface{}{"instance": 0, "primary_if": false, "name": "fip", "inbound": 0, "outbound": 0, "allow_spoofing": false, "mac_addr": mac}).Error + if err != nil { + logger.Error("Failed to Update addresses, %v", err) + return + } + iface = nil + } + iface2, _, err = DerivePublicInterface(ctx, instance, iface, publicIps, primaryMac) if err != nil { logger.Error("Failed to derive primary interface", err) return } + if iface2 != iface { + iface = iface2 + if len(secgroups) > 0 { + if err = db.Model(iface).Association("Security_Groups").Replace(secgroups).Error; err != nil { + logger.Debug("Failed to save interface", err) + return + } + iface.SecurityGroups = secgroups + } + } } else { cnt := secondAddrsCount - len(iface.SecondAddresses) if cnt > 0 { @@ -292,6 +328,15 @@ func (a *InterfaceAdmin) changeAddresses(ctx context.Context, instance *model.In } } } + for _, site := range siteSubnets { + err = db.Model(site).Updates(map[string]interface{}{"interface": iface.ID}).Error + if err != nil { + logger.Error("Failed to update interface", err) + err = NewCLError(ErrSiteSubnetUpdateFailed, "Failed to update interface", err) + return + } + iface.SiteSubnets = append(iface.SiteSubnets, site) + } iface.SecondAddresses = nil err = db.Preload("Subnet").Where("second_interface = ?", iface.ID).Find(&iface.SecondAddresses).Error if err != nil { @@ -299,6 +344,7 @@ func (a *InterfaceAdmin) changeAddresses(ctx context.Context, instance *model.In err = NewCLError(ErrSQLSyntaxError, "Failed to query second addresses of interface", err) return } + iface2 = iface return } @@ -513,7 +559,7 @@ func (a *InterfaceAdmin) Update(ctx context.Context, instance *model.Instance, i return } // 1. Get old addresses 2. Change addresses 3. Remote execute - err = a.changeAddresses(ctx, instance, iface, ifaceSubnets, siteSubnets, secondAddrsCount, publicIps) + iface, err = a.changeAddresses(ctx, instance, iface, ifaceSubnets, siteSubnets, secondAddrsCount, publicIps, secgroups) if err != nil { logger.Errorf("Failed to get instance networks, %v", err) return @@ -531,7 +577,7 @@ func (a *InterfaceAdmin) Update(ctx context.Context, instance *model.Instance, i } } - if needRemoteUpdate { + if needRemoteUpdate || changed { err = ApplyInterface(ctx, instance, iface, changed) if err != nil { logger.Error("Update vm nic command execution failed", err) @@ -982,6 +1028,23 @@ func (v *InterfaceView) Patch(c *macaron.Context, store session.Store) { siteSubnets = append(siteSubnets, siteSubnet) } } + PrimaryFloating := c.QueryInt64("primary_floating") + if PrimaryFloating > 0 && PrimaryFloating != iface.FloatingIp { + primaryFip, err := floatingIpAdmin.Get(ctx, int64(PrimaryFloating)) + if err != nil { + logger.Error("Get primary public ip failed", err) + c.Data["ErrorMsg"] = err.Error() + c.HTML(http.StatusBadRequest, "error") + return + } + for i, pubAddr := range publicAddresses { + if PrimaryFloating == pubAddr.ID { + publicAddresses = append(publicAddresses[:i], publicAddresses[i+1:]...) + break + } + } + publicAddresses = append([]*model.FloatingIp{primaryFip}, publicAddresses...) + } err = interfaceAdmin.Update(ctx, instance, iface, name, int32(inbound), int32(outbound), allowSpoofing, secgroups, ifaceSubnets, siteSubnets, ipCount, publicAddresses) if err != nil { logger.Debug("Failed to update interface", err) diff --git a/web/templates/interfaces_patch.tmpl b/web/templates/interfaces_patch.tmpl index aaef8656..3669bbc5 100644 --- a/web/templates/interfaces_patch.tmpl +++ b/web/templates/interfaces_patch.tmpl @@ -13,7 +13,25 @@
+ {{ if eq .Interface.FloatingIp 0 }} + {{ else }} + + {{ end }}