From c3d76d0cbee30ccafe6599f9b8a97ae928a84f38 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Fri, 15 Nov 2019 12:36:26 +0800 Subject: [PATCH] fix: network-delete may fail immidiately after deleting vm --- cmd/climc/shell/networks.go | 14 +++- cmd/climc/shell/vpcs.go | 9 +++ pkg/apis/compute/network_const.go | 2 + pkg/apis/compute/vpcs_const.go | 2 + pkg/compute/models/networks.go | 25 +++++++ pkg/compute/models/vpcs.go | 24 +++++++ pkg/compute/tasks/network_syncstatus_task.go | 73 ++++++++++++++++++++ pkg/compute/tasks/vpc_syncstatus_task.go | 73 ++++++++++++++++++++ pkg/multicloud/aliyun/vswitch.go | 15 +++- 9 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 pkg/compute/tasks/network_syncstatus_task.go create mode 100644 pkg/compute/tasks/vpc_syncstatus_task.go diff --git a/cmd/climc/shell/networks.go b/cmd/climc/shell/networks.go index 678e259247..e384fd14e8 100644 --- a/cmd/climc/shell/networks.go +++ b/cmd/climc/shell/networks.go @@ -418,7 +418,7 @@ func init() { }) type NetworkAddressOptions struct { - NETWORK string `help:"if of network to query"` + NETWORK string `help:"id or name of network to query"` } R(&NetworkAddressOptions{}, "network-addresses", "Query used addresses of network", func(s *mcclient.ClientSession, args *NetworkAddressOptions) error { result, err := modules.Networks.GetSpecific(s, args.NETWORK, "addresses", nil) @@ -433,4 +433,16 @@ func init() { printList(&listResult, nil) return nil }) + + type NetworkSyncOptions struct { + NETWORK string `help:"id or name of network to sync"` + } + R(&NetworkSyncOptions{}, "network-sync", "Sync network status", func(s *mcclient.ClientSession, args *NetworkSyncOptions) error { + result, err := modules.Networks.PerformAction(s, args.NETWORK, "sync", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/cmd/climc/shell/vpcs.go b/cmd/climc/shell/vpcs.go index 519bc57c5a..e3ecb54f63 100644 --- a/cmd/climc/shell/vpcs.go +++ b/cmd/climc/shell/vpcs.go @@ -158,4 +158,13 @@ func init() { printObject(result) return nil }) + + R(&VpcUpdateStatusOptions{}, "vpc-sync", "Synchronize the status of a vpc", func(s *mcclient.ClientSession, args *VpcUpdateStatusOptions) error { + result, err := modules.Vpcs.PerformAction(s, args.ID, "sync", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/pkg/apis/compute/network_const.go b/pkg/apis/compute/network_const.go index f5acfab3c9..6ee24fa77a 100644 --- a/pkg/apis/compute/network_const.go +++ b/pkg/apis/compute/network_const.go @@ -40,6 +40,8 @@ const ( NETWORK_STATUS_DELETING = "deleting" NETWORK_STATUS_DELETED = "deleted" NETWORK_STATUS_DELETE_FAILED = "delete_failed" + NETWORK_STATUS_START_SYNC = "start_sync" + NETWORK_STATUS_SYNCING = "sync" ) var ( diff --git a/pkg/apis/compute/vpcs_const.go b/pkg/apis/compute/vpcs_const.go index 2aed69e02c..690c575ab7 100644 --- a/pkg/apis/compute/vpcs_const.go +++ b/pkg/apis/compute/vpcs_const.go @@ -24,6 +24,8 @@ const ( VPC_STATUS_DELETE_FAILED = "delete_failed" VPC_STATUS_DELETED = "deleted" VPC_STATUS_UNKNOWN = "unknown" + VPC_STATUS_START_SYNC = "start_sync" + VPC_STATUS_SYNCING = "sync" MAX_VPC_PER_REGION = 3 diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 252e4a814b..2ef96d8878 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -2418,3 +2418,28 @@ func (network *SNetwork) GetDetailsAddresses(ctx context.Context, userCred mccli result.Add(jsonutils.Marshal(netAddrs), "addresses") return result, nil } + +func (net *SNetwork) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return net.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, net, "sync") +} + +func (net *SNetwork) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + vpc := net.GetVpc() + if vpc != nil && vpc.IsManaged() { + err := net.StartNetworkSyncstatusTask(ctx, userCred, "") + return nil, err + } else { + return nil, httperrors.NewUnsupportOperationError("on-premise network cannot sync status") + } +} + +func (net *SNetwork) StartNetworkSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "NetworkSyncstatusTask", net, userCred, nil, parentTaskId, "", nil) + if err != nil { + log.Errorf("create NetworkSyncstatusTask fail %s", err) + return err + } + net.SetStatus(userCred, api.NETWORK_STATUS_START_SYNC, "synchronize") + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 9f24cd0d57..14afe8fdd8 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -763,3 +763,27 @@ func (self *SVpc) SyncRemoteWires(ctx context.Context, userCred mcclient.TokenCr } return nil } + +func (vpc *SVpc) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, vpc, "sync") +} + +func (vpc *SVpc) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if vpc.IsManaged() { + err := vpc.StartVpcSyncstatusTask(ctx, userCred, "") + return nil, err + } else { + return nil, httperrors.NewUnsupportOperationError("on-premise vpc cannot sync status") + } +} + +func (vpc *SVpc) StartVpcSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "VpcSyncstatusTask", vpc, userCred, nil, parentTaskId, "", nil) + if err != nil { + log.Errorf("create NetworkSyncstatusTask fail %s", err) + return err + } + vpc.SetStatus(userCred, api.VPC_STATUS_START_SYNC, "synchronize") + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/tasks/network_syncstatus_task.go b/pkg/compute/tasks/network_syncstatus_task.go new file mode 100644 index 0000000000..dbd5382a49 --- /dev/null +++ b/pkg/compute/tasks/network_syncstatus_task.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type NetworkSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(NetworkSyncstatusTask{}) +} + +func (self *NetworkSyncstatusTask) taskFail(ctx context.Context, net *models.SNetwork, msg string) { + net.SetStatus(self.UserCred, api.NETWORK_STATUS_UNKNOWN, msg) + db.OpsLog.LogEvent(net, db.ACT_SYNC_STATUS, msg, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, net, logclient.ACT_SYNC_STATUS, msg, self.UserCred, false) + self.SetStageFailed(ctx, msg) +} + +func (self *NetworkSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + net := obj.(*models.SNetwork) + + net.SetStatus(self.UserCred, api.NETWORK_STATUS_SYNCING, "synchronize") + + extNet, err := net.GetINetwork() + if err != nil { + msg := fmt.Sprintf("fail to find ICloudNetwork for network %s", err) + self.taskFail(ctx, net, msg) + return + } + + err = extNet.Refresh() + if err != nil { + msg := fmt.Sprintf("fail to refresh ICloudNetwork status %s", err) + self.taskFail(ctx, net, msg) + return + } + + err = net.SyncWithCloudNetwork(ctx, self.UserCred, extNet, nil) + if err != nil { + msg := fmt.Sprintf("fail to sync network status %s", err) + self.taskFail(ctx, net, msg) + return + } + + logclient.AddActionLogWithStartable(self, net, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/vpc_syncstatus_task.go b/pkg/compute/tasks/vpc_syncstatus_task.go new file mode 100644 index 0000000000..4586074fc8 --- /dev/null +++ b/pkg/compute/tasks/vpc_syncstatus_task.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type VpcSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(VpcSyncstatusTask{}) +} + +func (self *VpcSyncstatusTask) taskFail(ctx context.Context, vpc *models.SVpc, msg string) { + vpc.SetStatus(self.UserCred, api.VPC_STATUS_UNKNOWN, msg) + db.OpsLog.LogEvent(vpc, db.ACT_SYNC_STATUS, msg, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, vpc, logclient.ACT_SYNC_STATUS, msg, self.UserCred, false) + self.SetStageFailed(ctx, msg) +} + +func (self *VpcSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + vpc := obj.(*models.SVpc) + + vpc.SetStatus(self.UserCred, api.VPC_STATUS_SYNCING, "synchronize") + + extVpc, err := vpc.GetIVpc() + if err != nil { + msg := fmt.Sprintf("fail to find ICloudVpc for vpc %s", err) + self.taskFail(ctx, vpc, msg) + return + } + + err = extVpc.Refresh() + if err != nil { + msg := fmt.Sprintf("fail to refresh ICloudVpc status %s", err) + self.taskFail(ctx, vpc, msg) + return + } + + err = vpc.SyncWithCloudVpc(ctx, self.UserCred, extVpc) + if err != nil { + msg := fmt.Sprintf("fail to sync vpc status %s", err) + self.taskFail(ctx, vpc, msg) + return + } + + logclient.AddActionLogWithStartable(self, vpc, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/multicloud/aliyun/vswitch.go b/pkg/multicloud/aliyun/vswitch.go index 82733f5582..546bda11e5 100644 --- a/pkg/multicloud/aliyun/vswitch.go +++ b/pkg/multicloud/aliyun/vswitch.go @@ -186,7 +186,20 @@ func (self *SVSwitch) Delete() error { log.Errorf("fail to dissociateWithSNAT") return err } - return self.wire.zone.region.DeleteVSwitch(self.VSwitchId) + err = cloudprovider.Wait(10*time.Second, 60*time.Second, func() (bool, error) { + err := self.wire.zone.region.DeleteVSwitch(self.VSwitchId) + if err != nil { + // delete network immediately after deleting vm on it + // \"Code\":\"DependencyViolation\",\"Message\":\"Specified object has dependent resources.\"} + if isError(err, "DependencyViolation") { + return false, nil + } + return false, err + } else { + return true, nil + } + }) + return err } func (self *SVSwitch) GetAllocTimeoutSeconds() int {