From 0343daa1fa1bc6f0a9705cbaf9a423d3f7f51d1e Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Fri, 19 Mar 2021 16:30:32 +0800 Subject: [PATCH] feat: add nas sync --- .../shell/compute/access_group_caches.go | 26 + cmd/climc/shell/compute/access_group_rules.go | 29 + cmd/climc/shell/compute/access_groups.go | 30 + cmd/climc/shell/compute/filesystem.go | 31 + cmd/climc/shell/compute/mount_targets.go | 31 + cmd/climc/shell/compute/nas_sku.go | 29 + pkg/apis/compute/access_group_caches.go | 35 + pkg/apis/compute/access_group_rules.go | 48 ++ pkg/apis/compute/access_groups.go | 54 ++ pkg/apis/compute/filesystem.go | 86 +++ pkg/apis/compute/mount_targets.go | 76 +++ pkg/apis/compute/nas_skus.go | 43 ++ pkg/apis/compute/network.go | 5 + pkg/cloudprovider/access_group.go | 156 +++++ pkg/cloudprovider/consts.go | 1 + pkg/cloudprovider/mount_target.go | 23 + pkg/cloudprovider/nas.go | 31 + pkg/cloudprovider/resources.go | 59 ++ pkg/compute/models/access_group_caches.go | 628 ++++++++++++++++++ pkg/compute/models/access_group_resource.go | 161 +++++ pkg/compute/models/access_group_rules.go | 375 +++++++++++ pkg/compute/models/access_groups.go | 335 ++++++++++ pkg/compute/models/capabilities.go | 4 + pkg/compute/models/cloudaccounts.go | 1 + pkg/compute/models/cloudproviders.go | 2 + pkg/compute/models/cloudsync.go | 60 ++ pkg/compute/models/filesystem.go | 523 +++++++++++++++ pkg/compute/models/initdb.go | 2 + pkg/compute/models/mount_targets.go | 518 +++++++++++++++ pkg/compute/models/nas_skus.go | 339 ++++++++++ pkg/compute/models/nat_skus.go | 5 - pkg/compute/models/purge.go | 48 ++ pkg/compute/models/regiondrivers.go | 6 + pkg/compute/models/skuresource.go | 22 +- pkg/compute/models/skus_tools.go | 38 ++ pkg/compute/policy/defaults.go | 12 + pkg/compute/regiondrivers/aliyun.go | 53 ++ pkg/compute/regiondrivers/base.go | 8 + pkg/compute/service/handlers.go | 7 + pkg/compute/service/service.go | 1 + .../tasks/access_group_cache_delete_task.go | 68 ++ pkg/compute/tasks/access_group_delete_task.go | 80 +++ .../tasks/access_group_sync_rules_task.go | 62 ++ .../tasks/access_group_syncstatus_task.go | 59 ++ .../tasks/cloud_account_sync_skus_task.go | 3 + pkg/compute/tasks/filesystem_create_task.go | 100 +++ pkg/compute/tasks/filesystem_delete_task.go | 75 +++ .../tasks/filesystem_syncstatus_task.go | 61 ++ pkg/compute/tasks/mount_target_create_task.go | 142 ++++ pkg/compute/tasks/mount_target_delete_task.go | 88 +++ .../tasks/mount_target_syncstatus_task.go | 73 ++ .../modules/mod_access_group_caches.go | 35 + .../modules/mod_access_group_rules.go | 35 + pkg/mcclient/modules/mod_access_groups.go | 35 + pkg/mcclient/modules/mod_filesystem.go | 35 + pkg/mcclient/modules/mod_mount_targets.go | 35 + pkg/mcclient/modules/mod_nas_skus.go | 33 + pkg/mcclient/options/base.go | 4 + pkg/mcclient/options/cloudaccounts.go | 2 +- .../options/compute/access_group_caches.go | 41 ++ .../options/compute/access_group_rules.go | 54 ++ pkg/mcclient/options/compute/access_groups.go | 41 ++ pkg/mcclient/options/compute/filesystem.go | 56 ++ pkg/mcclient/options/compute/mount_targets.go | 53 ++ pkg/mcclient/options/compute/nas_skus.go | 41 ++ pkg/multicloud/aliyun/access_groups.go | 324 +++++++++ pkg/multicloud/aliyun/aliyun.go | 20 +- pkg/multicloud/aliyun/filesystem.go | 350 ++++++++++ pkg/multicloud/aliyun/mount_target.go | 159 +++++ pkg/multicloud/aliyun/region.go | 9 + pkg/multicloud/aliyun/securitygroup.go | 8 + pkg/multicloud/aliyun/shell/access_groups.go | 87 +++ pkg/multicloud/aliyun/shell/filesystem.go | 54 ++ pkg/multicloud/aliyun/shell/mount_target.go | 56 ++ pkg/multicloud/nas_base.go | 20 + pkg/multicloud/region_base.go | 24 + 76 files changed, 6351 insertions(+), 12 deletions(-) create mode 100644 cmd/climc/shell/compute/access_group_caches.go create mode 100644 cmd/climc/shell/compute/access_group_rules.go create mode 100644 cmd/climc/shell/compute/access_groups.go create mode 100644 cmd/climc/shell/compute/filesystem.go create mode 100644 cmd/climc/shell/compute/mount_targets.go create mode 100644 cmd/climc/shell/compute/nas_sku.go create mode 100644 pkg/apis/compute/access_group_caches.go create mode 100644 pkg/apis/compute/access_group_rules.go create mode 100644 pkg/apis/compute/access_groups.go create mode 100644 pkg/apis/compute/filesystem.go create mode 100644 pkg/apis/compute/mount_targets.go create mode 100644 pkg/apis/compute/nas_skus.go create mode 100644 pkg/cloudprovider/access_group.go create mode 100644 pkg/cloudprovider/mount_target.go create mode 100644 pkg/cloudprovider/nas.go create mode 100644 pkg/compute/models/access_group_caches.go create mode 100644 pkg/compute/models/access_group_resource.go create mode 100644 pkg/compute/models/access_group_rules.go create mode 100644 pkg/compute/models/access_groups.go create mode 100644 pkg/compute/models/filesystem.go create mode 100644 pkg/compute/models/mount_targets.go create mode 100644 pkg/compute/models/nas_skus.go create mode 100644 pkg/compute/tasks/access_group_cache_delete_task.go create mode 100644 pkg/compute/tasks/access_group_delete_task.go create mode 100644 pkg/compute/tasks/access_group_sync_rules_task.go create mode 100644 pkg/compute/tasks/access_group_syncstatus_task.go create mode 100644 pkg/compute/tasks/filesystem_create_task.go create mode 100644 pkg/compute/tasks/filesystem_delete_task.go create mode 100644 pkg/compute/tasks/filesystem_syncstatus_task.go create mode 100644 pkg/compute/tasks/mount_target_create_task.go create mode 100644 pkg/compute/tasks/mount_target_delete_task.go create mode 100644 pkg/compute/tasks/mount_target_syncstatus_task.go create mode 100644 pkg/mcclient/modules/mod_access_group_caches.go create mode 100644 pkg/mcclient/modules/mod_access_group_rules.go create mode 100644 pkg/mcclient/modules/mod_access_groups.go create mode 100644 pkg/mcclient/modules/mod_filesystem.go create mode 100644 pkg/mcclient/modules/mod_mount_targets.go create mode 100644 pkg/mcclient/modules/mod_nas_skus.go create mode 100644 pkg/mcclient/options/compute/access_group_caches.go create mode 100644 pkg/mcclient/options/compute/access_group_rules.go create mode 100644 pkg/mcclient/options/compute/access_groups.go create mode 100644 pkg/mcclient/options/compute/filesystem.go create mode 100644 pkg/mcclient/options/compute/mount_targets.go create mode 100644 pkg/mcclient/options/compute/nas_skus.go create mode 100644 pkg/multicloud/aliyun/access_groups.go create mode 100644 pkg/multicloud/aliyun/filesystem.go create mode 100644 pkg/multicloud/aliyun/mount_target.go create mode 100644 pkg/multicloud/aliyun/shell/access_groups.go create mode 100644 pkg/multicloud/aliyun/shell/filesystem.go create mode 100644 pkg/multicloud/aliyun/shell/mount_target.go create mode 100644 pkg/multicloud/nas_base.go diff --git a/cmd/climc/shell/compute/access_group_caches.go b/cmd/climc/shell/compute/access_group_caches.go new file mode 100644 index 0000000000..6e8e38f48f --- /dev/null +++ b/cmd/climc/shell/compute/access_group_caches.go @@ -0,0 +1,26 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.AccessGroupCaches).WithKeyword("access-group-cache") + cmd.List(&compute.AccessGroupCacheListOptions{}) +} diff --git a/cmd/climc/shell/compute/access_group_rules.go b/cmd/climc/shell/compute/access_group_rules.go new file mode 100644 index 0000000000..f6dcebe8c9 --- /dev/null +++ b/cmd/climc/shell/compute/access_group_rules.go @@ -0,0 +1,29 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.AccessGroupRules).WithKeyword("access-group-rule") + cmd.List(&compute.AccessGroupRuleListOptions{}) + cmd.Delete(&options.BaseIdOptions{}) + cmd.Create(&compute.AccessRuleCreateOptions{}) +} diff --git a/cmd/climc/shell/compute/access_groups.go b/cmd/climc/shell/compute/access_groups.go new file mode 100644 index 0000000000..ab4184cee0 --- /dev/null +++ b/cmd/climc/shell/compute/access_groups.go @@ -0,0 +1,30 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.AccessGroups).WithKeyword("access-group") + cmd.List(&compute.AccessGroupListOptions{}) + cmd.Delete(&options.BaseIdOptions{}) + cmd.Create(&options.BaseCreateOptions{}) + cmd.Perform("syncstatus", &options.BaseIdOptions{}) +} diff --git a/cmd/climc/shell/compute/filesystem.go b/cmd/climc/shell/compute/filesystem.go new file mode 100644 index 0000000000..87daef2adc --- /dev/null +++ b/cmd/climc/shell/compute/filesystem.go @@ -0,0 +1,31 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.FileSystems).WithKeyword("file-system") + cmd.List(&compute.FileSystemListOptions{}) + cmd.Show(&options.BaseIdOptions{}) + cmd.Delete(&options.BaseIdOptions{}) + cmd.Create(&compute.FileSystemCreateOptions{}) + cmd.Perform("syncstatus", &compute.FileSystemIdOption{}) +} diff --git a/cmd/climc/shell/compute/mount_targets.go b/cmd/climc/shell/compute/mount_targets.go new file mode 100644 index 0000000000..cbfce8ebd8 --- /dev/null +++ b/cmd/climc/shell/compute/mount_targets.go @@ -0,0 +1,31 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.MountTargets).WithKeyword("mount-target") + cmd.Show(&options.BaseIdOptions{}) + cmd.Delete(&options.BaseIdOptions{}) + cmd.List(&compute.MountTargetListOptions{}) + cmd.Create(&compute.MountTargetCreateOptions{}) + cmd.Perform("syncstatus", &options.BaseIdOptions{}) +} diff --git a/cmd/climc/shell/compute/nas_sku.go b/cmd/climc/shell/compute/nas_sku.go new file mode 100644 index 0000000000..0edb86392a --- /dev/null +++ b/cmd/climc/shell/compute/nas_sku.go @@ -0,0 +1,29 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.NasSkus).WithKeyword("nas-sku") + cmd.List(&compute.NasSkuListOption{}) + cmd.Show(&compute.NasSkuIdOption{}) + cmd.PerformClass("sync-skus", &options.SkuSyncOptions{}) +} diff --git a/pkg/apis/compute/access_group_caches.go b/pkg/apis/compute/access_group_caches.go new file mode 100644 index 0000000000..86fa890dce --- /dev/null +++ b/pkg/apis/compute/access_group_caches.go @@ -0,0 +1,35 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +type AccessGroupCacheListInput struct { + apis.StatusStandaloneResourceListInput + apis.ExternalizedResourceBaseListInput + + ManagedResourceListInput + RegionalFilterListInput + + AccessGroupFilterListInput +} + +type AccessGroupCacheDetails struct { + apis.StatusStandaloneResourceDetails + apis.DomainizedResourceInfo + ManagedResourceInfo + CloudregionResourceInfo + AccessGroupResourceInfo +} diff --git a/pkg/apis/compute/access_group_rules.go b/pkg/apis/compute/access_group_rules.go new file mode 100644 index 0000000000..70f0a05c89 --- /dev/null +++ b/pkg/apis/compute/access_group_rules.go @@ -0,0 +1,48 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +type AccessGroupRuleListInput struct { + apis.ResourceBaseListInput + + AccessGroupFilterListInput +} + +type AccessGroupRuleDetails struct { + apis.ResourceBaseDetails + + AccessGroupResourceInfo +} + +type AccessGroupRuleCreateInput struct { + apis.ResourceBaseCreateInput + + Priority int + Source string + RWAccessType string + UserAccessType string + Description string + AccessGroupId string +} + +type AccessGroupRuleUpdateInput struct { + Priority *int + Source string + RWAccessType string + UserAccessType string + Description string +} diff --git a/pkg/apis/compute/access_groups.go b/pkg/apis/compute/access_groups.go new file mode 100644 index 0000000000..9c3cdce5be --- /dev/null +++ b/pkg/apis/compute/access_groups.go @@ -0,0 +1,54 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + DEFAULT_ACCESS_GROUP = "default" + + ACCESS_GROUP_STATUS_AVAILABLE = "available" + ACCESS_GROUP_STATUS_DELETING = "deleting" + ACCESS_GROUP_STATUS_DELETE_FAILED = "delete_failed" + ACCESS_GROUP_STATUS_CREATING = "creating" + ACCESS_GROUP_STATUS_SYNC_RULES = "sync_rules" + ACCESS_GROUP_STATUS_SYNC_RULES_FAILED = "sync_rules_failed" + ACCESS_GROUP_STATUS_UNKNOWN = "unknown" +) + +type AccessGroupListInput struct { + apis.StatusInfrasResourceBaseListInput +} + +type AccessGroupDetails struct { + apis.StatusInfrasResourceBaseDetails +} + +type AccessGroupResourceInfo struct { + // 权限组名称 + AccessGroup string `json:"access_group"` +} + +type AccessGroupFilterListInput struct { + // 权限组Id + AccessGroupId string `json:"access_group_id"` + + // 以权限组排序 + OrderByAccessGroup string `json:"order_by_access_group"` +} + +type AccessGroupCreateInput struct { + apis.StatusInfrasResourceBaseCreateInput +} diff --git a/pkg/apis/compute/filesystem.go b/pkg/apis/compute/filesystem.go new file mode 100644 index 0000000000..092b5babae --- /dev/null +++ b/pkg/apis/compute/filesystem.go @@ -0,0 +1,86 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + // 可用 + NAS_STATUS_AVAILABLE = "available" + // 不可用 + NAS_STATUS_UNAVAILABLE = "unavailable" + // 扩容中 + NAS_STATUS_EXTENDING = "extending" + // 创建中 + NAS_STATUS_CREATING = "creating" + // 创建失败 + NAS_STATUS_CREATE_FAILED = "create_failed" + // 未知 + NAS_STATUS_UNKNOWN = "unknown" + // 删除中 + NAS_STATUS_DELETING = "deleting" + NAS_STATUS_DELETE_FAILED = "delete_failed" +) + +type FileSystemListInput struct { + apis.StatusInfrasResourceBaseListInput + apis.ExternalizedResourceBaseListInput + ManagedResourceListInput + + RegionalFilterListInput +} + +type FileSystemCreateInput struct { + apis.StatusInfrasResourceBaseCreateInput + // 协议类型 + // enum: NFS, SMB, CPFS + Protocol string `json:"protocol"` + + // 文件系统类型 + // enmu: extreme, standard, cpfs + FileSystemType string `json:"file_system_type"` + + // 容量大小 + Capacity int64 `json:"capacity"` + + // IP子网Id + NetworkId string `json:"network_id"` + + // 存储类型 + // enmu: performance, capacity, standard, advance, advance_100, advance_200 + StorageType string `json:"storage_type"` + + // 可用区Id, 若不指定IP子网,此参数必填 + ZoneId string `json:"zone_id"` + + //swagger:ignore + CloudregionId string `json:"cloudregion_id"` + + // 订阅Id, 若传入network_id此参数可忽略 + ManagerId string `json:"manager_id"` +} + +type FileSystemSyncstatusInput struct { +} + +type FileSystemDetails struct { + apis.StatusInfrasResourceBaseDetails + ManagedResourceInfo + CloudregionResourceInfo + + Vpc string + Network string + Zone string +} diff --git a/pkg/apis/compute/mount_targets.go b/pkg/apis/compute/mount_targets.go new file mode 100644 index 0000000000..a53d1df4b2 --- /dev/null +++ b/pkg/apis/compute/mount_targets.go @@ -0,0 +1,76 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + MOUNT_TARGET_STATUS_AVAILABLE = "available" + MOUNT_TARGET_STATUS_UNAVAILABLE = "unavailable" + MOUNT_TARGET_STATUS_CREATING = "creating" + MOUNT_TARGET_STATUS_CREATE_FAILED = "create_failed" + MOUNT_TARGET_STATUS_DELETING = "deleting" + MOUNT_TARGET_STATUS_DELETE_FAILED = "delete_failed" + MOUNT_TARGET_STATUS_UNKNOWN = "unknown" +) + +type MountTargetListInput struct { + apis.StatusStandaloneResourceListInput + apis.ExternalizedResourceBaseListInput + + AccessGroupFilterListInput + VpcFilterListInput + NetworkFilterListInput + + // 文件系统Id + FileSystemId string `json:"file_system_id"` +} + +type MountTargetDetails struct { + apis.StatusStandaloneResourceDetails + AccessGroupResourceInfo + VpcResourceInfo + NetworkResourceInfo + + FileSystem string +} + +type MountTargetCreateInput struct { + apis.StatusStandaloneResourceCreateInput + + // 网络类型 + // enmu: vpc, classic + // default: vpc + NetworkType string `json:"network_type"` + + // 文件系统Id + // required: true + FileSystemId string `json:"file_system_id"` + + // Ip子网名称或Id, network_type == vpc时有效 + // required: true + NetworkId string `json:"network_id"` + // swagger:ignore + Network string `json:"network" yunion-deprecated-by:"network_id"` + + // swagger:ignore + VpcId string `json:"vpc_id"` + + // 权限组Id + AccessGroupId string `json:"access_group_id"` +} + +type MountTargetSyncstatusInput struct { +} diff --git a/pkg/apis/compute/nas_skus.go b/pkg/apis/compute/nas_skus.go new file mode 100644 index 0000000000..7afd543fe2 --- /dev/null +++ b/pkg/apis/compute/nas_skus.go @@ -0,0 +1,43 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + NAS_SKU_AVAILABLE = "available" + NAS_SKU_SOLDOUT = "soldout" +) + +type NasSkuListInput struct { + apis.EnabledStatusStandaloneResourceListInput + apis.ExternalizedResourceBaseListInput + + RegionalFilterListInput + + PostpaidStatus string `json:"postpaid_stauts"` + PrepaidStatus string `json:"prepaid_status"` + + Providers []string `json:"providers"` + // swagger:ignore + // Deprecated + Provider []string `json:"provider" yunion-deprecated-by:"providers"` +} + +type NasSkuDetails struct { + apis.EnabledStatusStandaloneResourceDetails + + CloudregionResourceInfo +} diff --git a/pkg/apis/compute/network.go b/pkg/apis/compute/network.go index ff2b0c579e..4a341e7c30 100644 --- a/pkg/apis/compute/network.go +++ b/pkg/apis/compute/network.go @@ -18,6 +18,11 @@ import ( "yunion.io/x/onecloud/pkg/apis" ) +const ( + NETWORK_TYPE_VPC = "vpc" + NETWORK_TYPE_CLASSIC = "classic" +) + type WireResourceInput struct { // 二层网络(ID或Name)的资源 WireId string `json:"wire_id"` diff --git a/pkg/cloudprovider/access_group.go b/pkg/cloudprovider/access_group.go new file mode 100644 index 0000000000..faba20bcd8 --- /dev/null +++ b/pkg/cloudprovider/access_group.go @@ -0,0 +1,156 @@ +// 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 cloudprovider + +import ( + "fmt" + "sort" + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" +) + +type SAccessGroup struct { + Name string + NetworkType string + FileSystemType string + Desc string +} + +type TRWAccessType string +type TUserAccessType string + +const ( + RWAccessTypeRW = TRWAccessType("RW") + RWAccessTypeR = TRWAccessType("R") + + UserAccessTypeNoRootSquash = TUserAccessType("no_root_squash") + UserAccessTypeRootSquash = TUserAccessType("root_squash") + UserAccessTypeAllSquash = TUserAccessType("all_squash") +) + +func GetAccessGroupRuleInfo(group ICloudAccessGroup) (AccessGroupRuleInfo, error) { + ret := AccessGroupRuleInfo{ + MinPriority: group.GetMinPriority(), + MaxPriority: group.GetMaxPriority(), + SupportedUserAccessType: group.GetSupporedUserAccessTypes(), + } + var err error + ret.Rules, err = group.GetRules() + if err != nil { + return ret, errors.Wrapf(err, "GetRules") + } + return ret, nil +} + +type AccessGroupRule struct { + Id string + ExternalId string + Priority int + RWAccessType TRWAccessType + UserAccessType TUserAccessType + Source string +} + +func (self AccessGroupRule) String() string { + return fmt.Sprintf("%s-%s-%s", self.RWAccessType, self.UserAccessType, self.Source) +} + +type AccessGroupRuleSet []AccessGroupRule + +func (srs AccessGroupRuleSet) Len() int { + return len(srs) +} + +func (srs AccessGroupRuleSet) Swap(i, j int) { + srs[i], srs[j] = srs[j], srs[i] +} + +func (srs AccessGroupRuleSet) Less(i, j int) bool { + return srs[i].Priority < srs[j].Priority || (srs[i].Priority == srs[j].Priority && srs[i].String() < srs[j].String()) +} + +type AccessGroupRuleInfo struct { + MaxPriority int + MinPriority int + SupportedUserAccessType []TUserAccessType + Rules AccessGroupRuleSet +} + +func (self *AccessGroupRuleInfo) Sort() { + if self.MinPriority < self.MaxPriority { + sort.Sort(self.Rules) + return + } + sort.Sort(sort.Reverse(self.Rules)) +} + +func CompareAccessGroupRules(src, dest AccessGroupRuleInfo, debug bool) (common, added, removed AccessGroupRuleSet) { + src.Sort() + dest.Sort() + var addPriority = func(init int, min, max int) int { + inc := 1 + if max < min { + max, min, inc = min, max, -1 + } + if init >= max || init <= min { + return init + } + return init + inc + } + i, j, priority := 0, 0, (dest.MinPriority-1+dest.MaxPriority)/2 + for i < len(src.Rules) || j < len(dest.Rules) { + if i < len(src.Rules) && j < len(dest.Rules) { + destRuleStr := dest.Rules[j].String() + srcRuleStr := src.Rules[i].String() + if debug { + log.Debugf("compare src %s priority(%d) %s -> dest %s priority(%d) %s\n", + src.Rules[i].ExternalId, src.Rules[i].Priority, src.Rules[i].String(), + dest.Rules[j].ExternalId, dest.Rules[j].Priority, dest.Rules[j].String()) + } + cmp := strings.Compare(destRuleStr, srcRuleStr) + if cmp == 0 { + dest.Rules[j].Id = src.Rules[i].Id + common = append(common, dest.Rules[j]) + priority = dest.Rules[j].Priority + i++ + j++ + } else if cmp < 0 { + removed = append(removed, dest.Rules[j]) + j++ + } else { + if isIn, _ := utils.InArray(src.Rules[i].UserAccessType, dest.SupportedUserAccessType); isIn { + priority = addPriority(priority, dest.MinPriority, dest.MaxPriority) + src.Rules[i].Priority = priority + added = append(added, src.Rules[i]) + } + i++ + } + } else if i >= len(src.Rules) { + removed = append(removed, dest.Rules[j]) + j++ + } else if j >= len(dest.Rules) { + if isIn, _ := utils.InArray(src.Rules[i].UserAccessType, dest.SupportedUserAccessType); isIn { + priority = addPriority(priority, dest.MinPriority, dest.MaxPriority) + src.Rules[i].Priority = priority + added = append(added, src.Rules[i]) + } + i++ + } + } + return common, added, removed +} diff --git a/pkg/cloudprovider/consts.go b/pkg/cloudprovider/consts.go index 07251ad7ec..eaf97850ed 100644 --- a/pkg/cloudprovider/consts.go +++ b/pkg/cloudprovider/consts.go @@ -55,6 +55,7 @@ const ( CLOUD_CAPABILITY_INTERVPCNETWORK = "intervpcnetwork" CLOUD_CAPABILITY_SAML_AUTH = "saml_auth" // 是否支持SAML 2.0 CLOUD_CAPABILITY_NAT = "nat" // NAT网关 + CLOUD_CAPABILITY_NAS = "nas" // NAS ) const ( diff --git a/pkg/cloudprovider/mount_target.go b/pkg/cloudprovider/mount_target.go new file mode 100644 index 0000000000..9c6007b4df --- /dev/null +++ b/pkg/cloudprovider/mount_target.go @@ -0,0 +1,23 @@ +// 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 cloudprovider + +type SMountTargetCreateOptions struct { + AccessGroupId string + NetworkType string + VpcId string + NetworkId string + FileSystemId string +} diff --git a/pkg/cloudprovider/nas.go b/pkg/cloudprovider/nas.go new file mode 100644 index 0000000000..f03e685690 --- /dev/null +++ b/pkg/cloudprovider/nas.go @@ -0,0 +1,31 @@ +// 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 cloudprovider + +import "yunion.io/x/onecloud/pkg/util/billing" + +type FileSystemCraeteOptions struct { + Name string + Desc string + VpcId string + NetworkId string + Capacity int64 + StorageType string + Protocol string + FileSystemType string + ZoneId string + + BillingCycle *billing.SBillingCycle +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 41cd68b4d5..5f38ededf2 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -153,6 +153,15 @@ type ICloudRegion interface { GetCapabilities() []string GetICloudQuotas() ([]ICloudQuota, error) + + GetICloudFileSystems() ([]ICloudFileSystem, error) + GetICloudFileSystemById(id string) (ICloudFileSystem, error) + + CreateICloudFileSystem(opts *FileSystemCraeteOptions) (ICloudFileSystem, error) + + GetICloudAccessGroups() ([]ICloudAccessGroup, error) + CreateICloudAccessGroup(opts *SAccessGroup) (ICloudAccessGroup, error) + GetICloudAccessGroupById(id string) (ICloudAccessGroup, error) } type ICloudZone interface { @@ -1217,3 +1226,53 @@ type ICloudInterVpcNetworkRoute interface { GetEnabled() bool GetCidr() string } + +type ICloudFileSystem interface { + ICloudResource + IBillingResource + + GetFileSystemType() string + GetStorageType() string + GetProtocol() string + GetCapacityGb() int64 + GetUsedCapacityGb() int64 + GetMountTargetCountLimit() int + + GetZoneId() string + + GetMountTargets() ([]ICloudMountTarget, error) + CreateMountTarget(opts *SMountTargetCreateOptions) (ICloudMountTarget, error) + + Delete() error +} + +type ICloudMountTarget interface { + GetGlobalId() string + GetName() string + GetAccessGroupId() string + GetDomainName() string + GetNetworkType() string + GetVpcId() string + GetNetworkId() string + GetStatus() string + + Delete() error +} + +type ICloudAccessGroup interface { + GetGlobalId() string + GetName() string + GetDesc() string + IsDefault() bool + GetMaxPriority() int + GetMinPriority() int + GetSupporedUserAccessTypes() []TUserAccessType + GetNetworkType() string + GetFileSystemType() string + GetMountTargetCount() int + + GetRules() ([]AccessGroupRule, error) + SyncRules(common, added, removed AccessGroupRuleSet) error + + Delete() error +} diff --git a/pkg/compute/models/access_group_caches.go b/pkg/compute/models/access_group_caches.go new file mode 100644 index 0000000000..e2c75a136b --- /dev/null +++ b/pkg/compute/models/access_group_caches.go @@ -0,0 +1,628 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SAccessGroupCacheManager struct { + db.SStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SManagedResourceBaseManager + SCloudregionResourceBaseManager + SAccessGroupResourceBaseManager +} + +var AccessGroupCacheManager *SAccessGroupCacheManager + +func init() { + AccessGroupCacheManager = &SAccessGroupCacheManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SAccessGroupCache{}, + "access_group_caches_tbl", + "access_group_cache", + "access_group_caches", + ), + } + AccessGroupCacheManager.SetVirtualObject(AccessGroupCacheManager) +} + +type SAccessGroupCache struct { + db.SStatusStandaloneResourceBase + db.SExternalizedResourceBase + SCloudregionResourceBase + SManagedResourceBase + SAccessGroupResourceBase + + // 已关联的挂载点数量 + MountTargetCount int `nullable:"false" list:"user" json:"mount_target_count"` + + FileSystemType string `width:"16" charset:"ascii" nullable:"false" index:"true" list:"user"` + NetworkType string `width:"8" charset:"ascii" nullable:"false" index:"true" list:"user" default:"vpc"` +} + +func (manager *SAccessGroupCacheManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (manager *SAccessGroupCacheManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsDomainAllowList(userCred, manager) +} + +func (self *SAccessGroupCache) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + group, err := self.GetAccessGroup() + if err != nil { + return false + } + return group.AllowUpdateItem(ctx, userCred) +} + +func (manager *SAccessGroupCacheManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupCacheListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SStatusStandaloneResourceBaseManager.ListItemFilter") + } + q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = manager.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SManagedResourceBaseManager.ListItemFilter") + } + q, err = manager.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SCloudregionResourceBaseManager.ListItemFilter") + } + q, err = manager.SAccessGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager *SAccessGroupCacheManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupCacheListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SAccessGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SAccessGroupResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (manager *SAccessGroupCacheManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SAccessGroupResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SAccessGroupCacheManager) FilterByOwner(q *sqlchemy.SQuery, userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + if userCred != nil { + sq := AccessGroupManager.Query("id") + if len(userCred.GetProjectDomainId()) > 0 { + sq = sq.Equals("domain_id", userCred.GetProjectDomainId()) + return q.In("access_group_id", sq) + } + } + return q +} + +func (manager *SAccessGroupCacheManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.AccessGroupCacheDetails { + rows := make([]api.AccessGroupCacheDetails, len(objs)) + + stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + manRows := manager.SManagedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + groupRows := manager.SAccessGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + for i := range rows { + rows[i] = api.AccessGroupCacheDetails{ + StatusStandaloneResourceDetails: stdRows[i], + ManagedResourceInfo: manRows[i], + CloudregionResourceInfo: regRows[i], + AccessGroupResourceInfo: groupRows[i], + } + } + + return rows +} + +func (manager *SAccessGroupCacheManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys") + } + + if keys.ContainsAny(manager.SManagedResourceBaseManager.GetExportKeys()...) { + q, err = manager.SManagedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemExportKeys") + } + } + + if keys.ContainsAny(manager.SCloudregionResourceBaseManager.GetExportKeys()...) { + q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys") + } + } + + if keys.ContainsAny(manager.SAccessGroupResourceBaseManager.GetExportKeys()...) { + q, err = manager.SAccessGroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SAccessGroupResourceBaseManager.ListItemExportKey") + } + } + + return q, nil +} + +func (self *SCloudregion) GetAccessGroupCaches() ([]SAccessGroupCache, error) { + caches := []SAccessGroupCache{} + q := AccessGroupCacheManager.Query().Equals("cloudregion_id", self.Id) + err := db.FetchModelObjects(AccessGroupCacheManager, q, &caches) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return caches, nil +} + +func (self *SAccessGroupCache) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SAccessGroupCache) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SCloudregion) SyncAccessGroups(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, iAccessGroups []cloudprovider.ICloudAccessGroup) compare.SyncResult { + lockman.LockRawObject(ctx, self.Id, "access_groups") + defer lockman.ReleaseRawObject(ctx, self.Id, "access_groups") + + result := compare.SyncResult{} + + dbCaches, err := self.GetAccessGroupCaches() + if err != nil { + result.Error(errors.Wrapf(err, "self.GetAccessGroupCaches")) + return result + } + + removed := make([]SAccessGroupCache, 0) + commondb := make([]SAccessGroupCache, 0) + commonext := make([]cloudprovider.ICloudAccessGroup, 0) + added := make([]cloudprovider.ICloudAccessGroup, 0) + err = compare.CompareSets(dbCaches, iAccessGroups, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrapf(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].syncWithAccessGroup(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + for i := 0; i < len(added); i += 1 { + err := provider.newFromCloudAccessGroup(ctx, userCred, self, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result +} + +func (self *SCloudprovider) GetDomainAccessGroups() ([]SAccessGroup, error) { + groups := []SAccessGroup{} + q := AccessGroupManager.Query().Equals("domain_id", self.DomainId).NotEquals("id", api.DEFAULT_ACCESS_GROUP) + err := db.FetchModelObjects(AccessGroupManager, q, &groups) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return groups, nil +} + +func (self *SAccessGroupCache) init(iAccessGroup cloudprovider.ICloudAccessGroup) { + self.Name = iAccessGroup.GetName() + self.NetworkType = iAccessGroup.GetNetworkType() + self.FileSystemType = iAccessGroup.GetFileSystemType() + self.MountTargetCount = iAccessGroup.GetMountTargetCount() + self.ExternalId = iAccessGroup.GetGlobalId() + self.Description = iAccessGroup.GetDesc() + self.Status = api.ACCESS_GROUP_STATUS_AVAILABLE +} + +func (self *SCloudprovider) newFromCloudAccessGroup(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, iAccessGroup cloudprovider.ICloudAccessGroup) error { + if iAccessGroup.IsDefault() { + cache := &SAccessGroupCache{} + cache.SetModelManager(AccessGroupCacheManager, cache) + cache.CloudregionId = region.Id + cache.ManagerId = self.Id + cache.AccessGroupId = api.DEFAULT_ACCESS_GROUP + cache.init(iAccessGroup) + return AccessGroupCacheManager.TableSpec().Insert(ctx, cache) + } + groups, err := self.GetDomainAccessGroups() + if err != nil { + return errors.Wrapf(err, "GetDomainAccessGroups") + } + src, err := cloudprovider.GetAccessGroupRuleInfo(iAccessGroup) + if err != nil { + return errors.Wrapf(err, "GetAccessGroupRuleInfo") + } + for i := range groups { + dest, err := groups[i].GetAccessGroupRuleInfo() + if err != nil { + continue + } + _, added, removed := cloudprovider.CompareAccessGroupRules(src, dest, false) + if len(added)+len(removed) == 0 { + cache := &SAccessGroupCache{} + cache.SetModelManager(AccessGroupCacheManager, cache) + cache.Name = iAccessGroup.GetName() + cache.AccessGroupId = groups[i].Id + cache.ManagerId = self.Id + cache.CloudregionId = region.Id + cache.init(iAccessGroup) + return AccessGroupCacheManager.TableSpec().Insert(ctx, cache) + } + } + group := &SAccessGroup{} + group.SetModelManager(AccessGroupManager, group) + group.Name, _ = db.GenerateName(AccessGroupManager, self.GetOwnerId(), iAccessGroup.GetName()) + group.Status = api.ACCESS_GROUP_STATUS_AVAILABLE + group.DomainId = self.DomainId + err = AccessGroupManager.TableSpec().Insert(ctx, group) + if err != nil { + return errors.Wrapf(err, "AccessGroupManager.Insert") + } + err = group.SyncRules(ctx, userCred, src) + return nil +} + +func (self *SAccessGroup) GetAccessGroupCaches() ([]SAccessGroupCache, error) { + q := AccessGroupCacheManager.Query().Equals("access_group_id", self.Id) + caches := []SAccessGroupCache{} + err := db.FetchModelObjects(AccessGroupCacheManager, q, &caches) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return caches, nil +} + +func (self *SAccessGroup) removeRules(ctx context.Context, ruleIds []string) error { + rules := []SAccessGroupRule{} + q := AccessGroupRuleManager.Query().Equals("access_group_id", self.Id).In("id", ruleIds) + err := db.FetchModelObjects(AccessGroupRuleManager, q, &rules) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + for i := range rules { + rules[i].Delete(ctx, nil) + } + return nil +} + +func (self *SAccessGroup) newAccessGroupRule(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.AccessGroupRule) error { + rule := &SAccessGroupRule{} + rule.SetModelManager(AccessGroupRuleManager, rule) + rule.AccessGroupId = self.Id + rule.Source = ext.Source + rule.RWAccessType = string(ext.RWAccessType) + rule.UserAccessType = string(ext.UserAccessType) + rule.Priority = ext.Priority + return AccessGroupRuleManager.TableSpec().Insert(ctx, rule) +} + +func (self *SAccessGroup) SyncRules(ctx context.Context, userCred mcclient.TokenCredential, src cloudprovider.AccessGroupRuleInfo) error { + dest, err := self.GetAccessGroupRuleInfo() + if err != nil { + return errors.Wrapf(err, "GetAccessGroupRuleInfo") + } + _, added, removed := cloudprovider.CompareAccessGroupRules(src, dest, false) + removeIds := []string{} + for i := range removed { + if len(removed[i].ExternalId) > 0 { + removeIds = append(removeIds, removed[i].ExternalId) + } + } + self.removeRules(ctx, removeIds) + for i := range added { + self.newAccessGroupRule(ctx, userCred, added[i]) + } + return nil +} + +func (self *SAccessGroupCache) syncAccessGroupBaseInfo(ctx context.Context, userCred mcclient.TokenCredential, iAccessGroup cloudprovider.ICloudAccessGroup) error { + _, err := db.Update(self, func() error { + self.init(iAccessGroup) + return nil + }) + return errors.Wrapf(err, "db.Update") +} + +func (self *SAccessGroupCache) syncWithAccessGroup(ctx context.Context, userCred mcclient.TokenCredential, iAccessGroup cloudprovider.ICloudAccessGroup) error { + err := self.syncAccessGroupBaseInfo(ctx, userCred, iAccessGroup) + if err != nil { + return errors.Wrapf(err, "syncAccessGroupBaseInfo") + } + if self.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return nil + } + group, err := self.GetAccessGroup() + if err != nil { + return errors.Wrapf(err, "GetAccessGroup") + } + caches, err := group.GetAccessGroupCaches() + if err != nil { + return errors.Wrapf(err, "GetAccessGroupCaches") + } + if len(caches) > 1 { + return nil + } + src, err := cloudprovider.GetAccessGroupRuleInfo(iAccessGroup) + if err != nil { + return errors.Wrapf(err, "cloudprovider.GetAccessGroupRuleInfo") + } + return group.SyncRules(ctx, userCred, src) +} + +func (self *SAccessGroupCache) GetRegion() (*SCloudregion, error) { + region, err := CloudregionManager.FetchById(self.CloudregionId) + if err != nil { + return nil, errors.Wrapf(err, "CloudregionManager.FetchById(%s)", self.CloudregionId) + } + return region.(*SCloudregion), nil +} + +func (self *SAccessGroupCache) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := self.GetDriver() + if err != nil { + return nil, errors.Wrapf(err, "self.GetDriver") + } + region, err := self.GetRegion() + if err != nil { + return nil, errors.Wrapf(err, "self.GetRegion") + } + return provider.GetIRegionById(region.ExternalId) +} + +func (self *SAccessGroupCache) GetICloudAccessGroup() (cloudprovider.ICloudAccessGroup, error) { + if len(self.ExternalId) == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty external id") + } + iRegion, err := self.GetIRegion() + if err != nil { + return nil, errors.Wrapf(err, "self.GetIRegion") + } + iAccessGroup, err := iRegion.GetICloudAccessGroupById(self.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "iRegion.GetICloudAccessGroupById(%s)", self.ExternalId) + } + return iAccessGroup, nil +} + +func (self *SAccessGroupCache) SyncRules() error { + group, err := self.GetAccessGroup() + if err != nil { + return errors.Wrapf(err, "GetAccessGroup") + } + src, err := group.GetAccessGroupRuleInfo() + if err != nil { + return errors.Wrapf(err, "GetAccessGroupRuleInfo") + } + + iAccessGroup, err := self.GetICloudAccessGroup() + if err != nil { + return errors.Wrapf(err, "GetICloudAccessGroup") + } + + dest, err := cloudprovider.GetAccessGroupRuleInfo(iAccessGroup) + if err != nil { + return errors.Wrapf(err, "GetAccessGroupRuleInfo") + } + + common, added, removed := cloudprovider.CompareAccessGroupRules(src, dest, false) + return iAccessGroup.SyncRules(common, added, removed) +} + +func (self *SAccessGroupCache) CreateIAccessGroup() error { + iRegion, err := self.GetIRegion() + if err != nil { + return errors.Wrapf(err, "self.GetIRegion") + } + opts := &cloudprovider.SAccessGroup{ + Name: self.Name, + NetworkType: self.NetworkType, + FileSystemType: self.FileSystemType, + Desc: self.Description, + } + iAccessGroup, err := iRegion.CreateICloudAccessGroup(opts) + if err != nil { + return errors.Wrapf(err, "iRegion.CreateIAccessGroup") + } + _, err = db.Update(self, func() error { + self.ExternalId = iAccessGroup.GetGlobalId() + self.Status = api.ACCESS_GROUP_STATUS_AVAILABLE + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update") + } + return self.SyncRules() +} + +type SAccessGroupCacheRegisterInput struct { + Name string + Desc string + ManagerId string + CloudregionId string + FileSystemType string + NetworkType string + AccessGroupId string +} + +func (manager *SAccessGroupCacheManager) Register(ctx context.Context, opts *SAccessGroupCacheRegisterInput) (*SAccessGroupCache, error) { + lockman.LockClass(ctx, manager, "") + defer lockman.ReleaseClass(ctx, manager, "") + + cache := &SAccessGroupCache{} + cache.SetModelManager(manager, cache) + cache.Name = opts.Name + cache.Description = opts.Desc + cache.Status = api.ACCESS_GROUP_STATUS_CREATING + cache.ManagerId = opts.ManagerId + cache.CloudregionId = opts.CloudregionId + cache.FileSystemType = opts.FileSystemType + cache.NetworkType = opts.NetworkType + cache.AccessGroupId = opts.AccessGroupId + err := manager.TableSpec().Insert(ctx, cache) + if err != nil { + return nil, errors.Wrapf(err, "Insert") + } + return cache, cache.CreateIAccessGroup() +} + +func (self *SAccessGroupCache) ValidateDeleteCondition(ctx context.Context) error { + if self.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return httperrors.NewProtectedResourceError("not allow to delete default access group") + } + if self.MountTargetCount > 0 && self.Status != api.ACCESS_GROUP_STATUS_UNKNOWN { + return httperrors.NewNotEmptyError("access group not empty, please delete mount target first") + } + return self.SStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SAccessGroupCache) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + + return self.StartDeleteTask(ctx, userCred, "") +} + +func (self *SAccessGroupCache) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "AccessGroupCacheDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_DELETE_FAILED, err.Error()) + return nil + } + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_DELETING, "") + return nil +} + +func (self *SAccessGroupCache) SyncStatus(ctx context.Context, userCred mcclient.TokenCredential) error { + iAccessGroup, err := self.GetICloudAccessGroup() + if err != nil { + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_CREATING, err.Error()) + return err + } + return self.syncAccessGroupBaseInfo(ctx, userCred, iAccessGroup) +} + +func (self *SAccessGroupCache) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步权限组缓存状态 +func (self *SAccessGroupCache) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.MountTargetSyncstatusInput) (jsonutils.JSONObject, error) { + return nil, self.SyncStatus(ctx, userCred) +} diff --git a/pkg/compute/models/access_group_resource.go b/pkg/compute/models/access_group_resource.go new file mode 100644 index 0000000000..e6284bff38 --- /dev/null +++ b/pkg/compute/models/access_group_resource.go @@ -0,0 +1,161 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SAccessGroupResourceBase struct { + // 权限组Id + AccessGroupId string `width:"36" charset:"ascii" nullable:"false" create:"required" index:"true" list:"user"` +} + +type SAccessGroupResourceBaseManager struct{} + +func (self *SAccessGroupResourceBase) GetAccessGroup() (*SAccessGroup, error) { + group, err := AccessGroupManager.FetchById(self.AccessGroupId) + if err != nil { + return nil, errors.Wrapf(err, "AccessGroupManager.FetchById(%s)", self.AccessGroupId) + } + return group.(*SAccessGroup), nil +} + +func (manager *SAccessGroupResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.AccessGroupResourceInfo { + rows := make([]api.AccessGroupResourceInfo, len(objs)) + groupIds := make([]string, len(objs)) + for i := range objs { + var base *SAccessGroupResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SAccessGroupResourceBase in object %s", objs[i]) + continue + } + groupIds[i] = base.AccessGroupId + } + groupNames, err := db.FetchIdNameMap2(AccessGroupManager, groupIds) + if err != nil { + return rows + } + for i := range rows { + rows[i].AccessGroup, _ = groupNames[groupIds[i]] + } + return rows +} + +func (manager *SAccessGroupResourceBaseManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupFilterListInput, +) (*sqlchemy.SQuery, error) { + if len(query.AccessGroupId) > 0 { + _, err := validators.ValidateModel(userCred, AccessGroupManager, &query.AccessGroupId) + if err != nil { + return nil, err + } + q = q.Equals("access_group_id", query.AccessGroupId) + } + return q, nil +} + +func (manager *SAccessGroupResourceBaseManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupFilterListInput, +) (*sqlchemy.SQuery, error) { + if !db.NeedOrderQuery(manager.GetOrderByFields(query)) { + return q, nil + } + orderQ := AccessGroupManager.Query("id") + orderSubQ := orderQ.SubQuery() + orderQ, orders, fields := manager.GetOrderBySubQuery(orderQ, orderSubQ, orderQ.Field("id"), userCred, query, nil, nil) + q = q.LeftJoin(orderSubQ, sqlchemy.Equals(q.Field("access_group_id"), orderSubQ.Field("id"))) + q = db.OrderByFields(q, orders, fields) + return q, nil +} + +func (manager *SAccessGroupResourceBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + if field == "access_group" { + groupQuery := AccessGroupManager.Query("name", "id").Distinct().SubQuery() + q.AppendField(groupQuery.Field("name", field)) + q = q.Join(groupQuery, sqlchemy.Equals(q.Field("access_group_id"), groupQuery.Field("id"))) + q.GroupBy(groupQuery.Field("name")) + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SAccessGroupResourceBaseManager) GetOrderBySubQuery( + q *sqlchemy.SQuery, + subq *sqlchemy.SSubQuery, + joinField sqlchemy.IQueryField, + userCred mcclient.TokenCredential, + query api.AccessGroupFilterListInput, + orders []string, + fields []sqlchemy.IQueryField, +) (*sqlchemy.SQuery, []string, []sqlchemy.IQueryField) { + if !db.NeedOrderQuery(manager.GetOrderByFields(query)) { + return q, orders, fields + } + groupQ := AccessGroupManager.Query().SubQuery() + q = q.LeftJoin(groupQ, sqlchemy.Equals(joinField, groupQ.Field("id"))) + q = q.AppendField(groupQ.Field("name").Label("access_group")) + orders = append(orders, query.OrderByAccessGroup) + fields = append(fields, subq.Field("access_group")) + return q, orders, fields +} + +func (manager *SAccessGroupResourceBaseManager) GetOrderByFields(query api.AccessGroupFilterListInput) []string { + return []string{query.OrderByAccessGroup} +} + +func (manager *SAccessGroupResourceBaseManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + if keys.ContainsAny(manager.GetExportKeys()...) { + subq := AccessGroupManager.Query("id", "name").SubQuery() + q = q.LeftJoin(subq, sqlchemy.Equals(q.Field("access_group_id"), subq.Field("id"))) + q = q.AppendField(subq.Field("name", "access_group")) + } + return q, nil +} + +func (manager *SAccessGroupResourceBaseManager) GetExportKeys() []string { + return []string{"access_group"} +} diff --git a/pkg/compute/models/access_group_rules.go b/pkg/compute/models/access_group_rules.go new file mode 100644 index 0000000000..ae84699844 --- /dev/null +++ b/pkg/compute/models/access_group_rules.go @@ -0,0 +1,375 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SAccessGroupRuleManager struct { + db.SResourceBaseManager + SAccessGroupResourceBaseManager +} + +var AccessGroupRuleManager *SAccessGroupRuleManager + +func init() { + AccessGroupRuleManager = &SAccessGroupRuleManager{ + SResourceBaseManager: db.NewResourceBaseManager( + SAccessGroupRule{}, + "access_group_rules_tbl", + "access_group_rule", + "access_group_rules", + ), + } + AccessGroupRuleManager.SetVirtualObject(AccessGroupRuleManager) +} + +type SAccessGroupRule struct { + db.SResourceBase + SAccessGroupResourceBase + + Id string `width:"128" charset:"ascii" primary:"true" list:"user"` + Priority int `default:"1" list:"user" update:"user" list:"user"` + Source string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"` + RWAccessType string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"` + UserAccessType string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"` + Description string `width:"256" charset:"utf8" list:"user" update:"user" create:"optional"` +} + +func (self *SAccessGroupRule) BeforeInsert() { + if len(self.Id) == 0 { + self.Id = stringutils.UUID4() + } +} + +func (self *SAccessGroupRule) GetId() string { + return self.Id +} + +func (manager *SAccessGroupRuleManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject { + groupId, _ := data.GetString("access_group_id") + return jsonutils.Marshal(map[string]string{"access_group_id": groupId}) +} + +func (manager *SAccessGroupRuleManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery { + groupId, _ := values.GetString("access_group_id") + if len(groupId) > 0 { + q = q.Equals("access_group_id", groupId) + } + return q +} + +func (manager *SAccessGroupRuleManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { + groupId, _ := data.GetString("access_group_id") + if len(groupId) > 0 { + accessGroup, err := db.FetchById(AccessGroupManager, groupId) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchById(%s)", groupId) + } + return accessGroup.(*SAccessGroup).GetOwnerId(), nil + } + return db.FetchDomainInfo(ctx, data) +} + +func (manager *SAccessGroupRuleManager) FilterByOwner(q *sqlchemy.SQuery, userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + sq := AccessGroupManager.Query("id") + sq = db.SharableManagerFilterByOwner(AccessGroupManager, sq, userCred, scope) + return q.In("access_group_id", sq.SubQuery()) +} + +func (manager *SAccessGroupRuleManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowCreate(userCred, manager) +} + +func (manager *SAccessGroupRuleManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsDomainAllowList(userCred, manager) +} + +func (self *SAccessGroupRule) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + if self.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return false + } + group, err := self.GetAccessGroup() + if err != nil { + return false + } + return group.AllowUpdateItem(ctx, userCred) +} + +func (self *SAccessGroupRule) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + group, err := self.GetAccessGroup() + if err != nil { + return false + } + return group.AllowDeleteItem(ctx, userCred, query, data) +} + +func (manager *SAccessGroupRuleManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery { + return q.Equals("id", idStr) +} + +// 权限组规则列表 +func (manager *SAccessGroupRuleManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupRuleListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SResourceBaseManager.ListItemFilter") + } + q, err = manager.SAccessGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager *SAccessGroupRuleManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.AccessGroupRuleDetails { + rows := make([]api.AccessGroupRuleDetails, len(objs)) + bRows := manager.SResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + gRows := manager.SAccessGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + for i := range rows { + rows[i] = api.AccessGroupRuleDetails{ + ResourceBaseDetails: bRows[i], + AccessGroupResourceInfo: gRows[i], + } + } + + return rows +} + +func (manager *SAccessGroupRuleManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupRuleListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SResourceBaseManager.OrderByExtraFields") + } + + q, err = manager.SAccessGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SAccessGroupRuleManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SAccessGroupResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (self *SAccessGroupRule) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +// 创建权限组规则 +func (manager *SAccessGroupRuleManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.AccessGroupRuleCreateInput) (api.AccessGroupRuleCreateInput, error) { + if len(input.AccessGroupId) == 0 { + return input, httperrors.NewMissingParameterError("access_group_id") + } + if input.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return input, httperrors.NewNotSupportedError("can not add rule for default access group") + } + _ag, err := validators.ValidateModel(userCred, AccessGroupManager, &input.AccessGroupId) + if err != nil { + return input, err + } + ag := _ag.(*SAccessGroup) + if !ag.IsOwner(userCred) && !userCred.HasSystemAdminPrivilege() { + return input, httperrors.NewForbiddenError("not enough privilege") + } + if ag.Status != api.ACCESS_GROUP_STATUS_AVAILABLE { + return input, httperrors.NewInvalidStatusError("access group %s status is not available", ag.Name) + } + if len(input.Source) == 0 { + return input, httperrors.NewMissingParameterError("source") + } + if !regutils.MatchCIDR(input.Source) && !regutils.MatchIP4Addr(input.Source) { + return input, httperrors.NewInputParameterError("invalid source %s", input.Source) + } + if input.Priority < 1 || input.Priority > 100 { + return input, httperrors.NewOutOfRangeError("Invalid priority %d, must be in range or 1 ~ 100", input.Priority) + } + if len(input.RWAccessType) == 0 { + return input, httperrors.NewMissingParameterError("rw_access_type") + } + if isIn, _ := utils.InArray(cloudprovider.TRWAccessType(input.RWAccessType), []cloudprovider.TRWAccessType{ + cloudprovider.RWAccessTypeR, + cloudprovider.RWAccessTypeRW, + }); !isIn { + return input, httperrors.NewInputParameterError("invalid rw_access_type %s", input.RWAccessType) + } + if len(input.UserAccessType) == 0 { + return input, httperrors.NewMissingParameterError("user_access_type") + } + if isIn, _ := utils.InArray(cloudprovider.TUserAccessType(input.UserAccessType), []cloudprovider.TUserAccessType{ + cloudprovider.UserAccessTypeAllSquash, + cloudprovider.UserAccessTypeRootSquash, + cloudprovider.UserAccessTypeNoRootSquash, + }); !isIn { + return input, httperrors.NewInputParameterError("invalid user_access_type %s", input.UserAccessType) + } + + input.ResourceBaseCreateInput, err = manager.SResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.ResourceBaseCreateInput) + if err != nil { + return input, err + } + + return input, nil +} + +func (self *SAccessGroupRule) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + + log.Debugf("POST Create %s", data) + + group, err := self.GetAccessGroup() + if err == nil { + logclient.AddSimpleActionLog(group, logclient.ACT_ALLOCATE, data, userCred, true) + group.DoSync(ctx, userCred) + } +} + +func (self *SAccessGroupRule) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.AccessGroupRuleUpdateInput) (api.AccessGroupRuleUpdateInput, error) { + if input.Priority != nil && *input.Priority < 1 || *input.Priority > 100 { + return input, httperrors.NewOutOfRangeError("Invalid priority %d, must be in range or 1 ~ 100", input.Priority) + } + if len(input.Source) > 0 && !regutils.MatchCIDR(input.Source) { + return input, httperrors.NewInputParameterError("invalid source %s", input.Source) + } + if isIn, _ := utils.InArray(cloudprovider.TRWAccessType(input.RWAccessType), []cloudprovider.TRWAccessType{ + cloudprovider.RWAccessTypeR, + cloudprovider.RWAccessTypeRW, + }); !isIn && len(input.RWAccessType) > 0 { + return input, httperrors.NewInputParameterError("invalid rw_access_type %s", input.RWAccessType) + } + if isIn, _ := utils.InArray(cloudprovider.TUserAccessType(input.UserAccessType), []cloudprovider.TUserAccessType{ + cloudprovider.UserAccessTypeAllSquash, + cloudprovider.UserAccessTypeRootSquash, + cloudprovider.UserAccessTypeNoRootSquash, + }); !isIn && len(input.UserAccessType) > 0 { + return input, httperrors.NewInputParameterError("invalid user_access_type %s", input.UserAccessType) + } + return input, nil +} + +func (self *SAccessGroupRule) GetOwnerId() mcclient.IIdentityProvider { + group, err := self.GetAccessGroup() + if err != nil { + return nil + } + return group.GetOwnerId() +} + +func (manager *SAccessGroupRuleManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SAccessGroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.ListItemExportKeys") + } + + return q, nil +} + +func (self *SAccessGroupRule) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { + self.SResourceBase.PreDelete(ctx, userCred) + + group, err := self.GetAccessGroup() + if err == nil { + logclient.AddSimpleActionLog(group, logclient.ACT_DELETE, jsonutils.Marshal(self), userCred, true) + group.DoSync(ctx, userCred) + } +} + +func (self *SAccessGroupRule) ValidateDeleteCondition(ctx context.Context) error { + if self.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return httperrors.NewProtectedResourceError("not allow to delete default access group rule") + } + return self.SResourceBase.ValidateDeleteCondition(ctx) +} + +func (manager *SAccessGroupRuleManager) InitializeData() error { + q := manager.Query().Equals("access_group_id", api.DEFAULT_ACCESS_GROUP) + rules := []SAccessGroupRule{} + err := db.FetchModelObjects(manager, q, &rules) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + if len(rules) == 0 { + rule := &SAccessGroupRule{} + rule.SetModelManager(manager, rule) + rule.AccessGroupId = api.DEFAULT_ACCESS_GROUP + rule.Source = "0.0.0.0/0" + rule.RWAccessType = string(cloudprovider.RWAccessTypeRW) + rule.UserAccessType = string(cloudprovider.UserAccessTypeNoRootSquash) + err = manager.TableSpec().Insert(context.TODO(), rule) + return errors.Wrapf(err, "Insert") + } + return nil +} diff --git a/pkg/compute/models/access_groups.go b/pkg/compute/models/access_groups.go new file mode 100644 index 0000000000..1684b62039 --- /dev/null +++ b/pkg/compute/models/access_groups.go @@ -0,0 +1,335 @@ +// 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 models + +import ( + "context" + "database/sql" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SAccessGroupManager struct { + db.SStatusInfrasResourceBaseManager +} + +var AccessGroupManager *SAccessGroupManager + +func init() { + AccessGroupManager = &SAccessGroupManager{ + SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager( + SAccessGroup{}, + "access_groups_tbl", + "access_group", + "access_groups", + ), + } + AccessGroupManager.SetVirtualObject(AccessGroupManager) +} + +type SAccessGroup struct { + db.SStatusInfrasResourceBase + + IsDirty bool `nullable:"false" default:"false"` +} + +func (manager *SAccessGroupManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SStatusInfrasResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager SAccessGroupManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.AccessGroupDetails { + rows := make([]api.AccessGroupDetails, len(objs)) + stdRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.AccessGroupDetails{ + StatusInfrasResourceBaseDetails: stdRows[i], + } + } + return rows +} + +func (manager *SAccessGroupManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SStatusInfrasResourceBaseManager.ListItemExportKeys") + } + return q, nil +} + +func (manager *SAccessGroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, nil +} + +func (manager *SAccessGroupManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.AccessGroupListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusInfrasResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusInfrasResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (self *SAccessGroup) GetChangeOwnerCandidateDomainIds() []string { + return []string{} +} + +func (self *SAccessGroup) GetEmptyAccessGroupRuleInfo() cloudprovider.AccessGroupRuleInfo { + return cloudprovider.AccessGroupRuleInfo{ + MinPriority: 1, + MaxPriority: 100, + Rules: cloudprovider.AccessGroupRuleSet{}, + SupportedUserAccessType: []cloudprovider.TUserAccessType{ + cloudprovider.UserAccessTypeRootSquash, + cloudprovider.UserAccessTypeNoRootSquash, + cloudprovider.UserAccessTypeAllSquash, + }, + } +} + +func (self *SAccessGroup) GetAccessGroupRuleInfo() (cloudprovider.AccessGroupRuleInfo, error) { + ret := self.GetEmptyAccessGroupRuleInfo() + rules, err := self.GetAccessGroupRules() + if err != nil { + return ret, errors.Wrapf(err, "GetAccessGroupRules") + } + for _, rule := range rules { + userType := cloudprovider.TUserAccessType(rule.UserAccessType) + if isIn, _ := utils.InArray(userType, ret.SupportedUserAccessType); !isIn { + ret.SupportedUserAccessType = append(ret.SupportedUserAccessType, userType) + } + ret.Rules = append(ret.Rules, cloudprovider.AccessGroupRule{ + ExternalId: rule.Id, + Source: rule.Source, + RWAccessType: cloudprovider.TRWAccessType(rule.RWAccessType), + UserAccessType: userType, + Priority: rule.Priority, + }) + } + return ret, nil +} + +func (self *SAccessGroup) GetAccessGroupRules() ([]SAccessGroupRule, error) { + rules := []SAccessGroupRule{} + q := AccessGroupRuleManager.Query().Equals("access_group_id", self.Id) + err := db.FetchModelObjects(AccessGroupRuleManager, q, &rules) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return rules, nil +} + +func (manager *SAccessGroupManager) InitializeData() error { + _, err := manager.FetchById(api.DEFAULT_ACCESS_GROUP) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return errors.Wrapf(err, "manager.FetchById(%s)", api.DEFAULT_ACCESS_GROUP) + } + log.Infof("Init default access group") + accessGroup := &SAccessGroup{} + accessGroup.SetModelManager(manager, accessGroup) + accessGroup.Id = api.DEFAULT_ACCESS_GROUP + accessGroup.Name = "Default" + accessGroup.Status = api.ACCESS_GROUP_STATUS_AVAILABLE + accessGroup.DomainId = auth.AdminCredential().GetProjectDomainId() + accessGroup.IsPublic = true + accessGroup.Deleted = false + err = manager.TableSpec().InsertOrUpdate(context.TODO(), accessGroup) + if err != nil { + return errors.Wrapf(err, "InsertOrUpdate default access group") + } + } + return nil +} + +func (manager *SAccessGroupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.AccessGroupCreateInput) (api.AccessGroupCreateInput, error) { + var err error + input.StatusInfrasResourceBaseCreateInput, err = manager.SStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusInfrasResourceBaseCreateInput) + if err != nil { + return input, err + } + input.Status = api.ACCESS_GROUP_STATUS_AVAILABLE + return input, nil +} + +func (self *SAccessGroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + + return self.StartDeleteTask(ctx, userCred, "") +} + +func (self *SAccessGroup) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "AccessGroupDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_DELETE_FAILED, err.Error()) + return nil + } + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_DELETING, "") + return nil +} + +func (self *SAccessGroup) GetMountTargets() ([]SMountTarget, error) { + mts := []SMountTarget{} + q := MountTargetManager.Query().Equals("access_group_id", self.Id) + err := db.FetchModelObjects(MountTargetManager, q, &mts) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return mts, nil +} + +func (self *SAccessGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SAccessGroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SAccessGroup) ValidateDeleteCondition(ctx context.Context) error { + if self.Id == api.DEFAULT_ACCESS_GROUP { + return httperrors.NewProtectedResourceError("not allow to delete default access group") + } + mts, err := self.GetMountTargets() + if err != nil { + return errors.Wrapf(err, "GetMountTargets") + } + if len(mts) > 0 { + return httperrors.NewNotEmptyError("access group not empty, please delete mount target first") + } + return self.SStatusInfrasResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SAccessGroup) DoSync(ctx context.Context, userCred mcclient.TokenCredential) { + if _, err := db.Update(self, func() error { + self.IsDirty = true + return nil + }); err != nil { + log.Errorf("Update Security Group error: %s", err.Error()) + } + time.AfterFunc(10*time.Second, func() { + AccessGroupManager.DelaySync(context.Background(), userCred, self.Id) + }) +} + +func (manager *SAccessGroupManager) DelaySync(ctx context.Context, userCred mcclient.TokenCredential, idStr string) error { + _group, err := manager.FetchById(idStr) + if err != nil { + return errors.Wrapf(err, "FetchById(%s)", idStr) + } + group := _group.(*SAccessGroup) + needSync := false + + func() { + lockman.LockObject(ctx, group) + defer lockman.ReleaseObject(ctx, group) + + if group.IsDirty { + if _, err := db.Update(group, func() error { + group.IsDirty = false + return nil + }); err != nil { + log.Errorf("Update Access Group error: %s", err.Error()) + } + needSync = true + } + }() + + if needSync { + return group.StartSyncRulesTask(ctx, userCred, "") + } + return nil +} + +func (self *SAccessGroup) StartSyncRulesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "AccessGroupSyncRulesTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_SYNC_RULES_FAILED, err.Error()) + return err + } + self.SetStatus(userCred, api.ACCESS_GROUP_STATUS_SYNC_RULES, "") + return nil +} + +func (self *SAccessGroup) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步权限组状态 +func (self *SAccessGroup) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.MountTargetSyncstatusInput) (jsonutils.JSONObject, error) { + return nil, self.StartSyncstatus(ctx, userCred, "") +} + +func (self *SAccessGroup) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + return StartResourceSyncStatusTask(ctx, userCred, self, "AccessGroupSyncstatusTask", parentTaskId) +} diff --git a/pkg/compute/models/capabilities.go b/pkg/compute/models/capabilities.go index e44a842d1d..9588d780c1 100644 --- a/pkg/compute/models/capabilities.go +++ b/pkg/compute/models/capabilities.go @@ -53,6 +53,8 @@ type SCapabilities struct { DisabledSamlAuthBrands []string `json:",allowempty"` NatBrands []string `json:",allowempty"` DisabledNatBrands []string `json:",allowempty"` + NasBrands []string `json:",allowempty"` + DisabledNasBrands []string `json:",allowempty"` PublicIpBrands []string `json:",allowempty"` NetworkManageBrands []string `json:",allowempty"` DisabledNetworkManageBrands []string `json:",allowempty"` @@ -289,6 +291,7 @@ func getBrands(region *SCloudregion, zone *SZone, domainId string, capa *SCapabi capa.LoadbalancerEngineBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_LOADBALANCER) capa.SamlAuthBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH) capa.NatBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_NAT) + capa.NasBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.True, cloudprovider.CLOUD_CAPABILITY_NAS) if utils.IsInStringArray(api.HYPERVISOR_KVM, capa.Hypervisors) || utils.IsInStringArray(api.HYPERVISOR_BAREMETAL, capa.Hypervisors) { capa.Brands = append(capa.Brands, api.ONECLOUD_BRAND_ONECLOUD) @@ -309,6 +312,7 @@ func getBrands(region *SCloudregion, zone *SZone, domainId string, capa *SCapabi capa.DisabledCloudIdBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_CLOUDID) capa.DisabledSamlAuthBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH) capa.DisabledNatBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_NAT) + capa.DisabledNasBrands, _ = CloudaccountManager.getBrandsOfCapability(region, zone, domainId, tristate.False, cloudprovider.CLOUD_CAPABILITY_NAS) return } diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index a570b6cc35..f2601fd7ef 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -2400,6 +2400,7 @@ func (account *SCloudaccount) PerformSyncSkus(ctx context.Context, userCred mccl ElasticcacheSkuManager.Keyword(), DBInstanceSkuManager.Keyword(), NatSkuManager.Keyword(), + NasSkuManager.Keyword(), }) { return nil, httperrors.NewInputParameterError("invalid resource %s", input.Resource) } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 78c521c108..eeabe71727 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -1397,6 +1397,8 @@ func (self *SCloudprovider) RealDelete(ctx context.Context, userCred mcclient.To DBInstanceManager, DBInstanceBackupManager, ElasticcacheManager, + AccessGroupCacheManager, + FileSystemManager, VpcManager, ElasticipManager, NetworkInterfaceManager, diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index d83bc58afa..b4edff51bf 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -256,6 +256,63 @@ func syncRegionVPCs(ctx context.Context, userCred mcclient.TokenCredential, sync } } +func syncRegionAccessGroups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) { + accessGroups, err := remoteRegion.GetICloudAccessGroups() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported { + return + } + log.Errorf("GetICloudFileSystems for region %s error: %v", localRegion.Name, err) + return + } + + result := localRegion.SyncAccessGroups(ctx, userCred, provider, accessGroups) + syncResults.Add(AccessGroupCacheManager, result) + log.Infof("Sync Access Group Caches for region %s result: %s", localRegion.Name, result.Result()) +} + +func syncRegionFileSystems(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) { + filesystems, err := remoteRegion.GetICloudFileSystems() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported { + return + } + log.Errorf("GetICloudFileSystems for region %s error: %v", localRegion.Name, err) + return + } + + localFSs, removeFSs, result := localRegion.SyncFileSystems(ctx, userCred, provider, filesystems) + syncResults.Add(FileSystemManager, result) + log.Infof("Sync FileSystem for region %s result: %s", localRegion.Name, result.Result()) + + for j := 0; j < len(localFSs); j += 1 { + func() { + // lock file system + lockman.LockObject(ctx, &localFSs[j]) + defer lockman.ReleaseObject(ctx, &localFSs[j]) + + if localFSs[j].Deleted { + return + } + + syncFileSystemMountTargets(ctx, userCred, &localFSs[j], removeFSs[j]) + }() + } +} + +func syncFileSystemMountTargets(ctx context.Context, userCred mcclient.TokenCredential, localFs *SFileSystem, remoteFs cloudprovider.ICloudFileSystem) { + mountTargets, err := remoteFs.GetMountTargets() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported { + return + } + log.Errorf("GetMountTargets for %s error: %v", localFs.Name, err) + return + } + result := localFs.SyncMountTargets(ctx, userCred, mountTargets) + log.Infof("SyncMountTargets for FileSystem %s result: %s", localFs.Name, result.Result()) +} + func syncVpcPeerConnections(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localVpc *SVpc, remoteVpc cloudprovider.ICloudVpc, syncRange *SSyncRange) { peerConnections, err := remoteVpc.GetICloudVpcPeeringConnections() if err != nil { @@ -1182,6 +1239,9 @@ func syncPublicCloudProviderInfo( syncRegionSnapshots(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) } + syncRegionAccessGroups(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) + syncRegionFileSystems(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) + if cloudprovider.IsSupportLoadbalancer(driver) { syncRegionLoadbalancerAcls(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) syncRegionLoadbalancerCertificates(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange) diff --git a/pkg/compute/models/filesystem.go b/pkg/compute/models/filesystem.go new file mode 100644 index 0000000000..901b4db9f8 --- /dev/null +++ b/pkg/compute/models/filesystem.go @@ -0,0 +1,523 @@ +// 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 models + +import ( + "context" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SFileSystemManager struct { + db.SStatusInfrasResourceBaseManager + db.SExternalizedResourceBaseManager + SManagedResourceBaseManager + SCloudregionResourceBaseManager + SZoneResourceBaseManager + + SDeletePreventableResourceBaseManager +} + +var FileSystemManager *SFileSystemManager + +func init() { + FileSystemManager = &SFileSystemManager{ + SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager( + SFileSystem{}, + "file_systems_tbl", + "file_system", + "file_systems", + ), + } + FileSystemManager.SetVirtualObject(FileSystemManager) +} + +type SFileSystem struct { + db.SStatusInfrasResourceBase + db.SExternalizedResourceBase + SManagedResourceBase + SBillingResourceBase + SCloudregionResourceBase + SZoneResourceBase + + SDeletePreventableResourceBase + + // 文件系统类型 + // enmu: extreme, standard, cpfs + FileSystemType string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"required"` + + // 存储类型 + // enmu: performance, capacity, standard, advance, advance_100, advance_200 + StorageType string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"required"` + // 协议类型 + // enum: NFS, SMB, cpfs + Protocol string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"required"` + // 容量, 单位Gb + Capacity int64 `nullable:"false" list:"user" create:"optional"` + // 已使用容量, 单位Gb + UsedCapacity int64 `nullable:"false" list:"user"` + + // 最多支持挂载点数量, -1代表无限制 + MountTargetCountLimit int `nullable:"false" list:"user" default:"-1"` +} + +func (manager *SFileSystemManager) GetContextManagers() [][]db.IModelManager { + return [][]db.IModelManager{ + {CloudregionManager}, + } +} + +func (self *SFileSystem) GetCloudproviderId() string { + return self.ManagerId +} + +func (manager *SFileSystemManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.FileSystemListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SStatusInfrasResourceBaseManager.ListItemFilter") + } + q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = manager.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SManagedResourceBaseManager.ListItemFilter") + } + q, err = manager.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SCloudregionResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (man *SFileSystemManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.FileSystemCreateInput) (api.FileSystemCreateInput, error) { + var err error + if len(input.NetworkId) > 0 { + net, err := validators.ValidateModel(userCred, NetworkManager, &input.NetworkId) + if err != nil { + return input, err + } + network := net.(*SNetwork) + zone := network.GetZone() + input.ManagerId = network.GetVpc().ManagerId + input.ZoneId = zone.Id + input.CloudregionId = zone.CloudregionId + } else if len(input.ZoneId) > 0 { + _zone, err := validators.ValidateModel(userCred, ZoneManager, &input.ZoneId) + if err != nil { + return input, err + } + zone := _zone.(*SZone) + input.CloudregionId = zone.CloudregionId + } else { + return input, httperrors.NewMissingParameterError("zone_id") + } + if len(input.ManagerId) == 0 { + return input, httperrors.NewMissingParameterError("manager_id") + } + input.StatusInfrasResourceBaseCreateInput, err = man.SStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusInfrasResourceBaseCreateInput) + if err != nil { + return input, err + } + return input, nil +} + +func (self *SFileSystem) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + self.StartCreateTask(ctx, userCred, jsonutils.GetAnyString(data, []string{"network_id"}), "") +} + +func (self *SFileSystem) StartCreateTask(ctx context.Context, userCred mcclient.TokenCredential, networkId string, parentTaskId string) error { + var err = func() error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(networkId), "network_id") + task, err := taskman.TaskManager.NewTask(ctx, "FileSystemCreateTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.NAS_STATUS_CREATE_FAILED, err.Error()) + return err + } + self.SetStatus(userCred, api.NAS_STATUS_CREATING, "") + return nil +} + +func (manager SFileSystemManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.FileSystemDetails { + rows := make([]api.FileSystemDetails, len(objs)) + stdRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + regionRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + mRows := manager.SManagedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + zoneIds := make([]string, len(objs)) + for i := range rows { + rows[i] = api.FileSystemDetails{ + StatusInfrasResourceBaseDetails: stdRows[i], + CloudregionResourceInfo: regionRows[i], + ManagedResourceInfo: mRows[i], + } + nas := objs[i].(*SFileSystem) + zoneIds[i] = nas.ZoneId + } + + zoneMaps, err := db.FetchIdNameMap2(ZoneManager, zoneIds) + if err != nil { + return rows + } + for i := range rows { + rows[i].Zone, _ = zoneMaps[zoneIds[i]] + } + return rows +} + +func (manager *SFileSystemManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SStatusInfrasResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys") + } + return q, nil +} + +func (manager *SFileSystemManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusInfrasResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SFileSystemManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.FileSystemListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusInfrasResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusInfrasResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SManagedResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (self *SCloudregion) GetFileSystems() ([]SFileSystem, error) { + ret := []SFileSystem{} + q := FileSystemManager.Query().Equals("cloudregion_id", self.Id) + err := db.FetchModelObjects(FileSystemManager, q, &ret) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return ret, nil +} + +func (self *SCloudregion) SyncFileSystems(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, filesystems []cloudprovider.ICloudFileSystem) ([]SFileSystem, []cloudprovider.ICloudFileSystem, compare.SyncResult) { + lockman.LockRawObject(ctx, self.Id, "filesystems") + defer lockman.ReleaseRawObject(ctx, self.Id, "filesystems") + + result := compare.SyncResult{} + + localFSs := []SFileSystem{} + remoteFSs := []cloudprovider.ICloudFileSystem{} + + dbFSs, err := self.GetFileSystems() + if err != nil { + result.Error(errors.Wrapf(err, "self.GetFileSystems")) + return localFSs, remoteFSs, result + } + + removed := make([]SFileSystem, 0) + commondb := make([]SFileSystem, 0) + commonext := make([]cloudprovider.ICloudFileSystem, 0) + added := make([]cloudprovider.ICloudFileSystem, 0) + err = compare.CompareSets(dbFSs, filesystems, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrapf(err, "compare.CompareSets")) + return localFSs, remoteFSs, result + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].syncRemove(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithCloudFileSystem(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + syncMetadata(ctx, userCred, &commondb[i], commonext[i]) + localFSs = append(localFSs, commondb[i]) + remoteFSs = append(remoteFSs, commonext[i]) + result.Update() + } + for i := 0; i < len(added); i += 1 { + newFs, err := self.newFromCloudFileSystem(ctx, userCred, provider, added[i]) + if err != nil { + result.AddError(err) + continue + } + syncMetadata(ctx, userCred, newFs, added[i]) + localFSs = append(localFSs, *newFs) + remoteFSs = append(remoteFSs, added[i]) + result.Add() + } + + return localFSs, remoteFSs, result +} + +func (self *SFileSystem) syncRemove(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + self.DeletePreventionOff(self, userCred) + + err := self.ValidateDeleteCondition(ctx) + if err != nil { // cannot delete + return self.SetStatus(userCred, api.NAS_STATUS_UNKNOWN, "sync to delete") + } + + return self.RealDelete(ctx, userCred) +} + +func (self *SFileSystem) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + + return self.StartDeleteTask(ctx, userCred, "") +} + +func (self *SFileSystem) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "FileSystemDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.NAS_STATUS_DELETE_FAILED, err.Error()) + return nil + } + self.SetStatus(userCred, api.NAS_STATUS_DELETING, "") + return nil +} + +func (self *SFileSystem) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SFileSystem) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + mts, err := self.GetMountTargets() + if err != nil { + return errors.Wrapf(err, "GetMountTargets") + } + for i := range mts { + err = mts[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "mount target %s real delete", mts[i].DomainName) + } + } + return self.SInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SFileSystem) ValidateDeleteCondition(ctx context.Context) error { + if self.DisableDelete.IsTrue() { + return httperrors.NewInvalidStatusError("FileSystem is locked, cannot delete") + } + return self.SStatusInfrasResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SFileSystem) SyncAllWithCloudFileSystem(ctx context.Context, userCred mcclient.TokenCredential, fs cloudprovider.ICloudFileSystem) error { + syncFileSystemMountTargets(ctx, userCred, self, fs) + return self.SyncWithCloudFileSystem(ctx, userCred, fs) +} + +func (self *SFileSystem) SyncWithCloudFileSystem(ctx context.Context, userCred mcclient.TokenCredential, fs cloudprovider.ICloudFileSystem) error { + _, err := db.Update(self, func() error { + self.Status = fs.GetStatus() + self.StorageType = fs.GetStorageType() + self.Protocol = fs.GetProtocol() + self.Capacity = fs.GetCapacityGb() + self.UsedCapacity = fs.GetUsedCapacityGb() + self.FileSystemType = fs.GetFileSystemType() + self.MountTargetCountLimit = fs.GetMountTargetCountLimit() + if zoneId := fs.GetZoneId(); len(zoneId) > 0 { + region, err := self.GetRegion() + if err != nil { + return errors.Wrapf(err, "self.GetRegion") + } + self.ZoneId, _ = region.getZoneIdBySuffix(zoneId) + } + return nil + }) + return errors.Wrapf(err, "db.Update") +} + +func (self *SCloudregion) getZoneIdBySuffix(zoneId string) (string, error) { + zones, err := self.GetZones() + if err != nil { + return "", errors.Wrapf(err, "region.GetZones") + } + for _, zone := range zones { + if strings.HasSuffix(zone.ExternalId, zoneId) { + return zone.Id, nil + } + } + return "", errors.Wrapf(cloudprovider.ErrNotFound, zoneId) +} + +func (self *SCloudregion) newFromCloudFileSystem(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, fs cloudprovider.ICloudFileSystem) (*SFileSystem, error) { + nas := SFileSystem{} + nas.SetModelManager(FileSystemManager, &nas) + var err error + nas.Name, err = db.GenerateName(FileSystemManager, userCred, fs.GetName()) + if err != nil { + return nil, errors.Wrapf(err, "db.GenerateName") + } + nas.ExternalId = fs.GetGlobalId() + nas.CloudregionId = self.Id + nas.ManagerId = provider.Id + nas.Status = fs.GetStatus() + nas.CreatedAt = fs.GetCreatedAt() + nas.StorageType = fs.GetStorageType() + nas.Protocol = fs.GetProtocol() + nas.Capacity = fs.GetCapacityGb() + nas.UsedCapacity = fs.GetCapacityGb() + nas.FileSystemType = fs.GetFileSystemType() + nas.MountTargetCountLimit = fs.GetMountTargetCountLimit() + if zoneId := fs.GetZoneId(); len(zoneId) > 0 { + nas.ZoneId, _ = self.getZoneIdBySuffix(zoneId) + } + return &nas, FileSystemManager.TableSpec().Insert(ctx, &nas) +} + +func (self *SFileSystem) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "syncstatus") +} + +// 同步NAS状态 +func (self *SFileSystem) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.FileSystemSyncstatusInput) (jsonutils.JSONObject, error) { + var openTask = true + count, err := taskman.TaskManager.QueryTasksOfObject(self, time.Now().Add(-3*time.Minute), &openTask).CountWithError() + if err != nil { + return nil, err + } + if count > 0 { + return nil, httperrors.NewBadRequestError("Nas has %d task active, can't sync status", count) + } + + return nil, self.StartSyncstatus(ctx, userCred, "") +} + +func (self *SFileSystem) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + return StartResourceSyncStatusTask(ctx, userCred, self, "FileSystemSyncstatusTask", parentTaskId) +} + +func (self *SFileSystem) GetRegion() (*SCloudregion, error) { + region, err := CloudregionManager.FetchById(self.CloudregionId) + if err != nil { + return nil, errors.Wrap(err, "CloudregionManager.FetchById") + } + return region.(*SCloudregion), nil +} + +func (self *SFileSystem) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := self.GetDriver() + if err != nil { + return nil, errors.Wrapf(err, "self.GetDriver") + } + region, err := self.GetRegion() + if err != nil { + return nil, errors.Wrapf(err, "self.GetRegion") + } + iRegion, err := provider.GetIRegionById(region.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "provider.GetIRegionById") + } + return iRegion, nil +} + +func (self *SFileSystem) GetICloudFileSystem() (cloudprovider.ICloudFileSystem, error) { + if len(self.ExternalId) == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty externalId") + } + iRegion, err := self.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "self.GetIRegion") + } + return iRegion.GetICloudFileSystemById(self.ExternalId) +} diff --git a/pkg/compute/models/initdb.go b/pkg/compute/models/initdb.go index 4841a32538..11e9b15b32 100644 --- a/pkg/compute/models/initdb.go +++ b/pkg/compute/models/initdb.go @@ -69,6 +69,8 @@ func InitDB() error { DBInstanceDatabaseManager, SnapshotPolicyDiskManager, + AccessGroupManager, + AccessGroupRuleManager, } { err := manager.InitializeData() if err != nil { diff --git a/pkg/compute/models/mount_targets.go b/pkg/compute/models/mount_targets.go new file mode 100644 index 0000000000..38f9d97496 --- /dev/null +++ b/pkg/compute/models/mount_targets.go @@ -0,0 +1,518 @@ +// 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 models + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SMountTargetManager struct { + db.SStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SVpcResourceBaseManager + SNetworkResourceBaseManager + SAccessGroupResourceBaseManager +} + +var MountTargetManager *SMountTargetManager + +func init() { + MountTargetManager = &SMountTargetManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SMountTarget{}, + "mount_targets_tbl", + "mount_target", + "mount_targets", + ), + } + MountTargetManager.SetVirtualObject(MountTargetManager) +} + +type SMountTarget struct { + db.SStatusStandaloneResourceBase + db.SExternalizedResourceBase + SVpcResourceBase + SNetworkResourceBase + SAccessGroupResourceBase + + NetworkType string `width:"8" charset:"ascii" nullable:"false" create:"required" index:"true" list:"user" default:"vpc"` + DomainName string `charset:"utf8" nullable:"true" create:"optional" list:"user"` + FileSystemId string `width:"36" charset:"ascii" nullable:"false" create:"required" index:"true" list:"user"` +} + +func (manager *SMountTargetManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowCreate(userCred, manager) +} + +func (manager *SMountTargetManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsDomainAllowList(userCred, manager) +} + +func (self *SMountTarget) GetFileSystem() (*SFileSystem, error) { + fs, err := FileSystemManager.FetchById(self.FileSystemId) + if err != nil { + return nil, errors.Wrapf(err, "FileSystemManager.FetchById(%s)", self.FileSystemId) + } + return fs.(*SFileSystem), nil +} + +func (self *SMountTarget) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + fs, err := self.GetFileSystem() + if err != nil { + return false + } + return fs.AllowUpdateItem(ctx, userCred) +} + +func (manager *SMountTargetManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.MountTargetCreateInput) (api.MountTargetCreateInput, error) { + if len(input.FileSystemId) == 0 { + return input, httperrors.NewMissingParameterError("file_system_id") + } + _fs, err := validators.ValidateModel(userCred, FileSystemManager, &input.FileSystemId) + if err != nil { + return input, err + } + fs := _fs.(*SFileSystem) + if fs.MountTargetCountLimit > -1 { + mts, err := fs.GetMountTargets() + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrapf(err, "fs.GetMountTargets")) + } + if len(mts) > fs.MountTargetCountLimit { + return input, httperrors.NewOutOfLimitError("Mount target reached the upper limit") + } + } + if len(input.NetworkType) == 0 { + input.NetworkType = api.NETWORK_TYPE_VPC + } + if !utils.IsInStringArray(input.NetworkType, []string{api.NETWORK_TYPE_VPC, api.NETWORK_TYPE_CLASSIC}) { + return input, httperrors.NewInputParameterError("invalid network type %s", input.NetworkType) + } + if input.NetworkType == api.NETWORK_TYPE_VPC { + if len(input.NetworkId) == 0 { + return input, httperrors.NewMissingParameterError("network_id") + } + _network, err := validators.ValidateModel(userCred, NetworkManager, &input.NetworkId) + if err != nil { + return input, err + } + network := _network.(*SNetwork) + vpc := network.GetVpc() + if vpc == nil { + return input, httperrors.NewGeneralError(fmt.Errorf("failed to found vpc for network %s", input.NetworkId)) + } + if vpc.ManagerId != fs.ManagerId { + return input, httperrors.NewConflictError("network and filesystem do not belong to the same account") + } + if vpc.CloudregionId != fs.CloudregionId { + return input, httperrors.NewConflictError("network and filesystem are not in the same region") + } + input.VpcId = vpc.Id + } + if len(input.AccessGroupId) == 0 { + return input, httperrors.NewMissingParameterError("access_group_id") + } + _, err = validators.ValidateModel(userCred, AccessGroupManager, &input.AccessGroupId) + if err != nil { + return input, err + } + input.StatusStandaloneResourceCreateInput, err = manager.SStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusStandaloneResourceCreateInput) + if err != nil { + return input, err + } + return input, nil +} + +func (self *SMountTarget) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + if self.NetworkType == api.NETWORK_TYPE_CLASSIC { + db.Update(self, func() error { + self.VpcId = "" + self.NetworkId = "" + return nil + }) + } + self.StartCreateTask(ctx, userCred, "") +} + +func (self *SMountTarget) StartCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "MountTargetCreateTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.MOUNT_TARGET_STATUS_CREATE_FAILED, err.Error()) + return nil + } + self.SetStatus(userCred, api.MOUNT_TARGET_STATUS_CREATING, "") + return nil +} + +func (manager *SMountTargetManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.MountTargetListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SStatusStandaloneResourceBaseManager.ListItemFilter") + } + q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = manager.SAccessGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.ListItemFilter") + } + q, err = manager.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SVpcResourceBaseManager.ListItemFilter") + } + q, err = manager.SNetworkResourceBaseManager.ListItemFilter(ctx, q, userCred, query.NetworkFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SNetworkResourceBaseManager.ListItemFilter") + } + if len(query.FileSystemId) > 0 { + _, err := validators.ValidateModel(userCred, FileSystemManager, &query.FileSystemId) + if err != nil { + return nil, err + } + q = q.Equals("file_system_id", query.FileSystemId) + } + return q, nil +} + +func (manager *SMountTargetManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.MountTargetListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SAccessGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.AccessGroupFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SVpcResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SNetworkResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.NetworkFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SNetworkResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (manager *SMountTargetManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SAccessGroupResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SVpcResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SNetworkResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SMountTargetManager) FilterByOwner(q *sqlchemy.SQuery, userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + if userCred != nil { + sq := FileSystemManager.Query("id") + if len(userCred.GetProjectDomainId()) > 0 { + sq = sq.Equals("domain_id", userCred.GetProjectDomainId()) + return q.In("file_system_id", sq) + } + } + return q +} + +func (manager *SMountTargetManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.MountTargetDetails { + rows := make([]api.MountTargetDetails, len(objs)) + stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + vpcRows := manager.SVpcResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + netRows := manager.SNetworkResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SAccessGroupResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + fsIds := make([]string, len(objs)) + for i := range rows { + rows[i] = api.MountTargetDetails{ + StatusStandaloneResourceDetails: stdRows[i], + VpcResourceInfo: vpcRows[i], + NetworkResourceInfo: netRows[i], + AccessGroupResourceInfo: acRows[i], + } + mount := objs[i].(*SMountTarget) + fsIds[i] = mount.FileSystemId + } + + fsMaps, err := db.FetchIdNameMap2(FileSystemManager, fsIds) + if err != nil { + return rows + } + for i := range rows { + rows[i].FileSystem, _ = fsMaps[fsIds[i]] + } + + return rows +} + +func (manager *SMountTargetManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SVpcResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrapf(err, "SVpcResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SNetworkResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrapf(err, "SNetworkResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SAccessGroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrapf(err, "SAccessGroupResourceBaseManager.ListItemExportKeys") + } + return q, nil +} + +func (self *SMountTarget) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SMountTarget) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +// 删除挂载点 +func (self *SMountTarget) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query api.ServerDeleteInput, input api.NatgatewayDeleteInput) error { + return self.StartDeleteTask(ctx, userCred, "") +} + +func (self *SMountTarget) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "MountTargetDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.MOUNT_TARGET_STATUS_DELETE_FAILED, err.Error()) + return err + } + return self.SetStatus(userCred, api.MOUNT_TARGET_STATUS_DELETING, "") +} + +func (self *SFileSystem) GetMountTargets() ([]SMountTarget, error) { + mounts := []SMountTarget{} + q := MountTargetManager.Query().Equals("file_system_id", self.Id) + err := db.FetchModelObjects(MountTargetManager, q, &mounts) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return mounts, nil +} + +func (self *SFileSystem) SyncMountTargets(ctx context.Context, userCred mcclient.TokenCredential, extMounts []cloudprovider.ICloudMountTarget) compare.SyncResult { + lockman.LockRawObject(ctx, self.Id, MountTargetManager.KeywordPlural()) + lockman.ReleaseRawObject(ctx, self.Id, MountTargetManager.KeywordPlural()) + + result := compare.SyncResult{} + + dbMounts, err := self.GetMountTargets() + if err != nil { + result.Error(errors.Wrapf(err, "self.GetMountTargets")) + return result + } + + removed := make([]SMountTarget, 0) + commondb := make([]SMountTarget, 0) + commonext := make([]cloudprovider.ICloudMountTarget, 0) + added := make([]cloudprovider.ICloudMountTarget, 0) + err = compare.CompareSets(dbMounts, extMounts, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrapf(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithMountTarget(ctx, userCred, self.ManagerId, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + for i := 0; i < len(added); i += 1 { + err := self.newFromCloudMountTarget(ctx, userCred, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result +} + +func (self *SMountTarget) SyncWithMountTarget(ctx context.Context, userCred mcclient.TokenCredential, managerId string, m cloudprovider.ICloudMountTarget) error { + _, err := db.Update(self, func() error { + self.Status = m.GetStatus() + self.Name = m.GetName() + self.DomainName = m.GetDomainName() + self.ExternalId = m.GetGlobalId() + if groupId := m.GetAccessGroupId(); len(groupId) > 0 { + _cache, _ := db.FetchByExternalIdAndManagerId(AccessGroupCacheManager, groupId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", managerId) + }) + if _cache != nil { + cache := _cache.(*SAccessGroupCache) + self.AccessGroupId = cache.AccessGroupId + } + } + return nil + }) + return errors.Wrapf(err, "db.Update") +} + +func (self *SFileSystem) newFromCloudMountTarget(ctx context.Context, userCred mcclient.TokenCredential, m cloudprovider.ICloudMountTarget) error { + mount := &SMountTarget{} + mount.SetModelManager(MountTargetManager, mount) + mount.FileSystemId = self.Id + mount.Name = m.GetName() + mount.Status = m.GetStatus() + mount.ExternalId = m.GetGlobalId() + mount.DomainName = m.GetDomainName() + mount.NetworkType = m.GetNetworkType() + if mount.NetworkType == api.NETWORK_TYPE_VPC { + if vpcId := m.GetVpcId(); len(vpcId) > 0 { + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, vpcId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", self.ManagerId) + }) + if err != nil { + log.Errorf("failed to found vpc for mount point %s by externalId: %s", mount.Name, vpcId) + } else { + mount.VpcId = vpc.GetId() + } + } + if networkId := m.GetNetworkId(); len(networkId) > 0 { + network, err := db.FetchByExternalIdAndManagerId(NetworkManager, networkId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + wire := WireManager.Query().SubQuery() + vpc := VpcManager.Query().SubQuery() + return q.Join(wire, sqlchemy.Equals(wire.Field("id"), q.Field("wire_id"))). + Join(vpc, sqlchemy.Equals(vpc.Field("id"), wire.Field("vpc_id"))). + Filter(sqlchemy.Equals(vpc.Field("manager_id"), self.ManagerId)) + }) + if err != nil { + log.Errorf("failed to found network for mount point %s by externalId: %s", mount.Name, networkId) + } else { + mount.NetworkId = network.GetId() + } + } + } + + return MountTargetManager.TableSpec().Insert(ctx, mount) +} + +func (self *SMountTarget) GetNetwork() (*SNetwork, error) { + network, err := NetworkManager.FetchById(self.NetworkId) + if err != nil { + return nil, errors.Wrapf(err, "NetworkManager.FetchById(%s)", self.NetworkId) + } + return network.(*SNetwork), nil +} + +func (self *SMountTarget) GetVpc() (*SVpc, error) { + vpc, err := VpcManager.FetchById(self.VpcId) + if err != nil { + return nil, errors.Wrapf(err, "VpcManager.FetchById(%s)", self.VpcId) + } + return vpc.(*SVpc), nil +} + +func (self *SMountTarget) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步挂载点状态 +func (self *SMountTarget) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.MountTargetSyncstatusInput) (jsonutils.JSONObject, error) { + return nil, self.StartSyncstatus(ctx, userCred, "") +} + +func (self *SMountTarget) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + return StartResourceSyncStatusTask(ctx, userCred, self, "MountTargetSyncstatusTask", parentTaskId) +} diff --git a/pkg/compute/models/nas_skus.go b/pkg/compute/models/nas_skus.go new file mode 100644 index 0000000000..5501fd60ff --- /dev/null +++ b/pkg/compute/models/nas_skus.go @@ -0,0 +1,339 @@ +// 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 models + +import ( + "context" + "database/sql" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SNasSkuManager struct { + db.SEnabledStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SCloudregionResourceBaseManager +} + +var NasSkuManager *SNasSkuManager + +func init() { + NasSkuManager = &SNasSkuManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SNasSku{}, + "nas_skus_tbl", + "nas_sku", + "nas_skus", + ), + } + NasSkuManager.NameRequireAscii = false + NasSkuManager.SetVirtualObject(NasSkuManager) +} + +type SNasSku struct { + db.SEnabledStatusStandaloneResourceBase + db.SExternalizedResourceBase + SCloudregionResourceBase + + PrepaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"` // 预付费资源状态 available|soldout + PostpaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"` // 按需付费资源状态 available|soldout + + StorageType string `width:"32" index:"true" list:"user" create:"optional"` + DiskSizeStep int `list:"user" default:"-1" create:"optional"` //步长 + MaxDiskSizeGb int `list:"user" create:"optional"` + MinDiskSizeGb int `list:"user" create:"optional"` + + NetworkTypes string `list:"user" create:"optional"` + FileSystemType string `list:"user" create:"optional"` + Protocol string `list:"user" create:"optional"` + + Provider string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_required" update:"admin"` + ZoneIds string `charset:"utf8" nullable:"true" list:"user" update:"admin" create:"admin_optional" json:"zone_ids"` +} + +func (manager *SNasSkuManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NasSkuListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrapf(err, "SEnabledStatusStandaloneResourceBaseManager.ListItemFilter") + } + q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrapf(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = manager.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrapf(err, "SCloudregionResourceBaseManager.ListItemFilter") + } + if len(query.PostpaidStatus) > 0 { + q = q.Equals("postpaid_status", query.PostpaidStatus) + } + if len(query.PrepaidStatus) > 0 { + q = q.Equals("prepaid_status", query.PrepaidStatus) + } + if len(query.Providers) > 0 { + q = q.In("provider", query.Providers) + } + return q, nil +} + +func (manager *SNasSkuManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.NasSkuDetails { + rows := make([]api.NasSkuDetails, len(objs)) + + stdRows := manager.SEnabledStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + for i := range rows { + rows[i] = api.NasSkuDetails{ + EnabledStatusStandaloneResourceDetails: stdRows[i], + CloudregionResourceInfo: regRows[i], + } + } + + return rows +} + +func (manager *SNasSkuManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.ListItemExportKeys") + } + q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys") + } + return q, nil +} + +func (manager *SNasSkuManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SNasSkuManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NasSkuListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (self *SCloudregion) GetNasSkus() ([]SNasSku, error) { + skus := []SNasSku{} + q := NasSkuManager.Query().Equals("cloudregion_id", self.Id) + err := db.FetchModelObjects(NasSkuManager, q, &skus) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return skus, nil +} + +func (self SNasSku) GetGlobalId() string { + return self.ExternalId +} + +func (self *SCloudregion) SyncNasSkus(ctx context.Context, userCred mcclient.TokenCredential, meta *SSkuResourcesMeta) compare.SyncResult { + lockman.LockRawObject(ctx, self.Id, "nas-sku") + defer lockman.ReleaseRawObject(ctx, self.Id, "nas-sku") + + syncResult := compare.SyncResult{} + + iskus, err := meta.GetNasSkusByRegionExternalId(self.ExternalId) + if err != nil { + syncResult.Error(err) + return syncResult + } + + dbSkus, err := self.GetNasSkus() + if err != nil { + syncResult.Error(err) + return syncResult + } + + removed := make([]SNasSku, 0) + commondb := make([]SNasSku, 0) + commonext := make([]SNasSku, 0) + added := make([]SNasSku, 0) + + err = compare.CompareSets(dbSkus, iskus, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].Delete(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + continue + } + syncResult.Delete() + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].syncWithCloudSku(ctx, userCred, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + continue + } + syncResult.Update() + } + for i := 0; i < len(added); i += 1 { + err = self.newFromCloudNasSku(ctx, userCred, added[i]) + if err != nil { + syncResult.AddError(err) + } else { + syncResult.Add() + } + } + return syncResult +} + +func (self *SNasSku) syncWithCloudSku(ctx context.Context, userCred mcclient.TokenCredential, sku SNasSku) error { + _, err := db.Update(self, func() error { + jsonutils.Update(self, sku) + self.Status = api.NAS_SKU_AVAILABLE + return nil + }) + return err +} + +func (self *SCloudregion) newFromCloudNasSku(ctx context.Context, userCred mcclient.TokenCredential, isku SNasSku) error { + sku := &isku + sku.SetModelManager(NasSkuManager, sku) + sku.Id = "" //避免使用yunion meta的id,导致出现duplicate entry问题 + sku.Status = api.NAS_SKU_AVAILABLE + sku.SetEnabled(true) + sku.CloudregionId = self.Id + return NasSkuManager.TableSpec().Insert(ctx, sku) +} + +func SyncNasSkus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + err := SyncRegionNasSkus(ctx, userCred, "", isStart) + if err != nil { + log.Errorf("SyncRegionNasSkus error: %v", err) + } +} + +func SyncRegionNasSkus(ctx context.Context, userCred mcclient.TokenCredential, regionId string, isStart bool) error { + if isStart { + q := NasSkuManager.Query() + if len(regionId) > 0 { + q = q.Equals("cloudregion_id", regionId) + } + cnt, err := q.Limit(1).CountWithError() + if err != nil && err != sql.ErrNoRows { + return errors.Wrapf(err, "SyncRegionNasSkus.QueryNasSku") + } + if cnt > 0 { + log.Debugf("SyncRegionNasSkus synced skus, skip...") + return nil + } + } + + q := CloudregionManager.Query() + q = q.In("provider", CloudproviderManager.GetPublicProviderProvidersQuery()) + if len(regionId) > 0 { + q = q.Equals("id", regionId) + } + regions := []SCloudregion{} + err := db.FetchModelObjects(CloudregionManager, q, ®ions) + if err != nil { + return errors.Wrapf(err, "db.FetchModelObjects") + } + + meta, err := FetchSkuResourcesMeta() + if err != nil { + return errors.Wrapf(err, "FetchSkuResourcesMeta") + } + + for i := range regions { + if !regions[i].GetDriver().IsSupportedNas() { + log.Infof("region %s(%s) not support nas, skip sync", regions[i].Name, regions[i].Id) + continue + } + result := regions[i].SyncNasSkus(ctx, userCred, meta) + msg := result.Result() + notes := fmt.Sprintf("SyncNasSkus for region %s result: %s", regions[i].Name, msg) + log.Infof(notes) + } + return nil +} + +func (manager *SNasSkuManager) AllowSyncSkus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, manager, "sync-skus") +} + +func (manager *SNasSkuManager) PerformSyncSkus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SkuSyncInput) (jsonutils.JSONObject, error) { + return PerformActionSyncSkus(ctx, userCred, manager.Keyword(), input) +} + +func (manager *SNasSkuManager) AllowGetPropertySyncTasks(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowGetSpec(userCred, manager, "sync-tasks") +} + +func (manager *SNasSkuManager) GetPropertySyncTasks(ctx context.Context, userCred mcclient.TokenCredential, query api.SkuTaskQueryInput) (jsonutils.JSONObject, error) { + return GetPropertySkusSyncTasks(ctx, userCred, query) +} diff --git a/pkg/compute/models/nat_skus.go b/pkg/compute/models/nat_skus.go index 32faca2a85..caf78b6cee 100644 --- a/pkg/compute/models/nat_skus.go +++ b/pkg/compute/models/nat_skus.go @@ -63,11 +63,6 @@ type SNatSku struct { PrepaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"` // 预付费资源状态 available|soldout PostpaidStatus string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"admin_optional" update:"admin" default:"available"` // 按需付费资源状态 available|soldout - Cps int `nullable:"false" list:"user" create:"optional" update:"admin"` - Conns int `nullable:"false" list:"user" create:"optional" update:"admin"` - Pps int `nullable:"false" list:"user" create:"optional" update:"admin"` - Throughput int `nullable:"false" list:"user" create:"optional" update:"admin"` - Provider string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_required" update:"admin"` ZoneIds string `charset:"utf8" nullable:"true" list:"user" update:"admin" create:"admin_optional" json:"zone_ids"` } diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index 01d86428a7..67bd75cad9 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -1789,6 +1789,54 @@ func (manager *SPolicyDefinitionManager) purgeAll(ctx context.Context, userCred return nil } +func (self *SAccessGroupCache) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + return self.RealDelete(ctx, userCred) +} + +func (manager *SAccessGroupCacheManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + caches := []SAccessGroupCache{} + err := fetchByManagerId(manager, providerId, &caches) + if err != nil { + return errors.Wrapf(err, "fetchByManagerId") + } + + for i := range caches { + err := caches[i].purge(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "cache purge") + } + } + + return nil +} + +func (self *SFileSystem) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + return self.RealDelete(ctx, userCred) +} + +func (manager *SFileSystemManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + files := []SFileSystem{} + err := fetchByManagerId(manager, providerId, &files) + if err != nil { + return errors.Wrapf(err, "fetchByManagerId") + } + + for i := range files { + err := files[i].purge(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "cache purge") + } + } + + return nil +} + func (vpcPC *SVpcPeeringConnection) purge(ctx context.Context, userCred mcclient.TokenCredential) error { lockman.LockObject(ctx, vpcPC) defer lockman.ReleaseObject(ctx, vpcPC) diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 0b87400aed..a4708a5ee6 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -177,6 +177,12 @@ type IDBInstanceDriver interface { INatGatewayDriver IElasticIpDriver + INasDriver +} + +type INasDriver interface { + RequestSyncAccessGroup(ctx context.Context, userCred mcclient.TokenCredential, fs *SFileSystem, mt *SMountTarget, ag *SAccessGroup, task taskman.ITask) error + IsSupportedNas() bool } // NAT diff --git a/pkg/compute/models/skuresource.go b/pkg/compute/models/skuresource.go index 3ea3a4fde0..5292967d9a 100644 --- a/pkg/compute/models/skuresource.go +++ b/pkg/compute/models/skuresource.go @@ -1,3 +1,17 @@ +// 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 models import ( @@ -19,7 +33,13 @@ import ( ) func PerformActionSyncSkus(ctx context.Context, userCred mcclient.TokenCredential, resourceKey string, input apis.SkuSyncInput) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(resourceKey, []string{ServerSkuManager.Keyword(), ElasticcacheSkuManager.Keyword(), DBInstanceSkuManager.Keyword(), NatSkuManager.Keyword()}) { + if !utils.IsInStringArray(resourceKey, []string{ + ServerSkuManager.Keyword(), + ElasticcacheSkuManager.Keyword(), + DBInstanceSkuManager.Keyword(), + NatSkuManager.Keyword(), + NasSkuManager.Keyword(), + }) { return nil, httperrors.NewUnsupportOperationError("resource %s is not support sync skus", resourceKey) } diff --git a/pkg/compute/models/skus_tools.go b/pkg/compute/models/skus_tools.go index ff477a5fa8..36baf260e0 100644 --- a/pkg/compute/models/skus_tools.go +++ b/pkg/compute/models/skus_tools.go @@ -48,6 +48,7 @@ type SSkuResourcesMeta struct { ElasticCacheBase string `json:"elastic_cache_base"` ImageBase string `json:"image_base"` NatBase string `json:"nat_base"` + NasBase string `json:"nas_base"` } func (self *SSkuResourcesMeta) getZoneIdBySuffix(zoneMaps map[string]string, suffix string) string { @@ -161,6 +162,43 @@ func (self *SSkuResourcesMeta) GetNatSkusByRegionExternalId(regionExternalId str return result, nil } +func (self *SSkuResourcesMeta) GetNasSkusByRegionExternalId(regionExternalId string) ([]SNasSku, error) { + regionId, zoneMaps, err := self.GetRegionIdAndZoneMaps(regionExternalId) + if err != nil { + return nil, errors.Wrap(err, "GetRegionIdAndZoneMaps") + } + result := []SNasSku{} + objs, err := self.getObjsByRegion(self.NasBase, regionExternalId) + if err != nil { + return nil, errors.Wrapf(err, "getSkusByRegion") + } + for _, obj := range objs { + sku := SNasSku{} + sku.SetModelManager(NasSkuManager, &sku) + err = obj.Unmarshal(&sku) + if err != nil { + return nil, errors.Wrapf(err, "obj.Unmarshal") + } + if len(sku.ZoneIds) > 0 { + zoneIds := []string{} + for _, zoneExtId := range strings.Split(sku.ZoneIds, ",") { + zoneId := self.getZoneIdBySuffix(zoneMaps, zoneExtId) // Huawei rds sku zone1 maybe is cn-north-4f + if len(zoneId) == 0 { + log.Warningf("invalid nat sku %s(%s) %s zone: %s", sku.Name, sku.Id, sku.CloudregionId, zoneExtId) + continue + } + zoneIds = append(zoneIds, zoneId) + } + sku.ZoneIds = strings.Join(zoneIds, ",") + } + sku.Id = "" + sku.CloudregionId = regionId + + result = append(result, sku) + } + return result, nil +} + func (self *SSkuResourcesMeta) getCloudregion(regionExternalId string) (*SCloudregion, error) { region, err := db.FetchByExternalId(CloudregionManager, regionExternalId) if err != nil { diff --git a/pkg/compute/policy/defaults.go b/pkg/compute/policy/defaults.go index 572e909b31..c3e05a8495 100644 --- a/pkg/compute/policy/defaults.go +++ b/pkg/compute/policy/defaults.go @@ -300,6 +300,18 @@ var ( Action: PolicyActionGet, Result: rbacutils.Allow, }, + { + Service: api.SERVICE_TYPE, + Resource: "nas_skus", + Action: PolicyActionList, + Result: rbacutils.Allow, + }, + { + Service: api.SERVICE_TYPE, + Resource: "nas_skus", + Action: PolicyActionGet, + Result: rbacutils.Allow, + }, }, }, { diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index 3876a5a7e2..b2d95372d9 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -1474,3 +1474,56 @@ func (self *SAliyunRegionDriver) ValidateCreateNatGateway(ctx context.Context, u func (self *SAliyunRegionDriver) IsSupportedNatGateway() bool { return true } + +func (self *SAliyunRegionDriver) IsSupportedNas() bool { + return true +} + +func (self *SAliyunRegionDriver) RequestSyncAccessGroup(ctx context.Context, userCred mcclient.TokenCredential, fs *models.SFileSystem, mt *models.SMountTarget, ag *models.SAccessGroup, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if mt.AccessGroupId == api.DEFAULT_ACCESS_GROUP { + return jsonutils.Marshal( + map[string]string{ + "access_group_id": fmt.Sprintf("DEFAULT_%s_GROUP_NAME", strings.ToUpper(mt.NetworkType)), + }), nil + } + caches, err := ag.GetAccessGroupCaches() + if err != nil { + return nil, errors.Wrapf(err, "ag.GetAccessGroupCaches") + } + for i := range caches { + if caches[i].CloudregionId == fs.CloudregionId && + caches[i].ManagerId == fs.ManagerId && + caches[i].FileSystemType == fs.FileSystemType && + caches[i].NetworkType == mt.NetworkType { + if len(caches[i].ExternalId) > 0 { + err := caches[i].SyncRules() + if err != nil { + return nil, errors.Wrapf(err, "cache.SyncRules") + } + return jsonutils.Marshal(map[string]string{"access_group_id": caches[i].ExternalId}), nil + } + err := caches[i].CreateIAccessGroup() + if err != nil { + return nil, errors.Wrapf(err, "CreateIAccessGroup") + } + return jsonutils.Marshal(map[string]string{"access_group_id": caches[i].ExternalId}), nil + } + } + opts := models.SAccessGroupCacheRegisterInput{ + Name: ag.Name, + Desc: ag.Description, + ManagerId: fs.ManagerId, + CloudregionId: fs.CloudregionId, + NetworkType: mt.NetworkType, + FileSystemType: fs.FileSystemType, + AccessGroupId: ag.Id, + } + cache, err := models.AccessGroupCacheManager.Register(ctx, &opts) + if err != nil { + return nil, errors.Wrapf(err, "Register") + } + return jsonutils.Marshal(map[string]string{"access_group_id": cache.ExternalId}), nil + }) + return nil +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index af84289fd1..8f2ad36155 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -428,6 +428,10 @@ func (self *SBaseRegionDriver) IsSupportedNatGateway() bool { return false } +func (self *SBaseRegionDriver) IsSupportedNas() bool { + return false +} + func (self *SBaseRegionDriver) OnNatEntryDeleteComplete(ctx context.Context, userCred mcclient.TokenCredential, eip *models.SElasticip) error { return nil } @@ -443,3 +447,7 @@ func (self *SBaseRegionDriver) IsSupportedNatAutoRenew() bool { func (self *SBaseRegionDriver) RequestAssociatEip(ctx context.Context, userCred mcclient.TokenCredential, eip *models.SElasticip, input api.ElasticipAssociateInput, obj db.IStatusStandaloneModel, task taskman.ITask) error { return httperrors.NewNotImplementedError("RequestAssociatEip") } + +func (self *SBaseRegionDriver) RequestSyncAccessGroup(ctx context.Context, userCred mcclient.TokenCredential, fs *models.SFileSystem, mt *models.SMountTarget, ag *models.SAccessGroup, task taskman.ITask) error { + return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestSyncAccessGroup") +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index cd629fa45d..43f27c1ed8 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -205,6 +205,13 @@ func InitHandlers(app *appsrv.Application) { models.InterVpcNetworkManager, models.NatSkuManager, + models.NasSkuManager, + + models.FileSystemManager, + models.AccessGroupManager, + models.AccessGroupRuleManager, + models.AccessGroupCacheManager, + models.MountTargetManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index cc602ce9c1..2be257dda2 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -153,6 +153,7 @@ func StartService() { cron.AddJobEveryFewDays("SyncSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncServerSkus, true) cron.AddJobEveryFewDays("SyncDBInstanceSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncDBInstanceSkus, true) cron.AddJobEveryFewDays("SyncNatSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncNatSkus, false) + cron.AddJobEveryFewDays("SyncNasSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncNasSkus, false) cron.AddJobEveryFewDays("SyncElasticCacheSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncElasticCacheSkus, true) cron.AddJobEveryFewDays("StorageSnapshotsRecycle", 1, 2, 0, 0, models.StorageManager.StorageSnapshotsRecycle, false) diff --git a/pkg/compute/tasks/access_group_cache_delete_task.go b/pkg/compute/tasks/access_group_cache_delete_task.go new file mode 100644 index 0000000000..76c1cbd90b --- /dev/null +++ b/pkg/compute/tasks/access_group_cache_delete_task.go @@ -0,0 +1,68 @@ +// 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 cachereed 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 langucachee governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type AccessGroupCacheDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(AccessGroupCacheDeleteTask{}) +} + +func (self *AccessGroupCacheDeleteTask) taskFailed(ctx context.Context, cache *models.SAccessGroupCache, err error) { + cache.SetStatus(self.UserCred, api.ACCESS_GROUP_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, cache, logclient.ACT_DELOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *AccessGroupCacheDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + cache := obj.(*models.SAccessGroupCache) + + iAccessGroup, err := cache.GetICloudAccessGroup() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + self.taskComplete(ctx, cache) + return + } + self.taskFailed(ctx, cache, errors.Wrapf(err, "GetICloudAccessGroup")) + return + } + err = iAccessGroup.Delete() + if err != nil { + self.taskFailed(ctx, cache, errors.Wrapf(err, "iAccessGroup.Delete")) + return + } + self.taskComplete(ctx, cache) +} + +func (self *AccessGroupCacheDeleteTask) taskComplete(ctx context.Context, cache *models.SAccessGroupCache) { + cache.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/access_group_delete_task.go b/pkg/compute/tasks/access_group_delete_task.go new file mode 100644 index 0000000000..aefdd0333a --- /dev/null +++ b/pkg/compute/tasks/access_group_delete_task.go @@ -0,0 +1,80 @@ +// 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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type AccessGroupDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(AccessGroupDeleteTask{}) +} + +func (self *AccessGroupDeleteTask) taskFailed(ctx context.Context, ag *models.SAccessGroup, cache *models.SAccessGroupCache, err error) { + ag.SetStatus(self.UserCred, api.ACCESS_GROUP_STATUS_DELETE_FAILED, err.Error()) + if cache != nil { + cache.SetStatus(self.UserCred, api.ACCESS_GROUP_STATUS_DELETE_FAILED, err.Error()) + } + logclient.AddActionLogWithStartable(self, ag, logclient.ACT_DELOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *AccessGroupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + ag := obj.(*models.SAccessGroup) + + caches, err := ag.GetAccessGroupCaches() + if err != nil { + self.taskFailed(ctx, ag, nil, errors.Wrapf(err, "GetAccessGroupCaches")) + return + } + for i := range caches { + caches[i].SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_DELETING, "") + iAccessGroup, err := caches[i].GetICloudAccessGroup() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + caches[i].RealDelete(ctx, self.GetUserCred()) + continue + } + self.taskFailed(ctx, ag, &caches[i], errors.Wrapf(err, "cache.GetICloudAccessGroup")) + return + } + err = iAccessGroup.Delete() + if err != nil { + self.taskFailed(ctx, ag, &caches[i], errors.Wrapf(err, "iAccessGroup.Delete")) + return + } + caches[i].RealDelete(ctx, self.GetUserCred()) + } + self.taskComplete(ctx, ag) +} + +func (self *AccessGroupDeleteTask) taskComplete(ctx context.Context, ag *models.SAccessGroup) { + ag.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/access_group_sync_rules_task.go b/pkg/compute/tasks/access_group_sync_rules_task.go new file mode 100644 index 0000000000..2ca4d9f291 --- /dev/null +++ b/pkg/compute/tasks/access_group_sync_rules_task.go @@ -0,0 +1,62 @@ +// 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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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 AccessGroupSyncRulesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(AccessGroupSyncRulesTask{}) +} + +func (self *AccessGroupSyncRulesTask) taskFail(ctx context.Context, ag *models.SAccessGroup, err error) { + ag.SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_AVAILABLE, err.Error()) + logclient.AddActionLogWithStartable(self, ag, logclient.ACT_SYNC_CONF, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *AccessGroupSyncRulesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + ag := obj.(*models.SAccessGroup) + + caches, err := ag.GetAccessGroupCaches() + if err != nil { + self.taskFail(ctx, ag, errors.Wrapf(err, "ag.GetAccessGroupCaches")) + return + } + + for i := range caches { + err := caches[i].SyncRules() + if err != nil { + caches[i].SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_SYNC_RULES_FAILED, err.Error()) + } + } + + ag.SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/access_group_syncstatus_task.go b/pkg/compute/tasks/access_group_syncstatus_task.go new file mode 100644 index 0000000000..9360c477b5 --- /dev/null +++ b/pkg/compute/tasks/access_group_syncstatus_task.go @@ -0,0 +1,59 @@ +// 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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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 AccessGroupSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(AccessGroupSyncstatusTask{}) +} + +func (self *AccessGroupSyncstatusTask) taskFail(ctx context.Context, ag *models.SAccessGroup, err error) { + ag.SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_AVAILABLE, err.Error()) + logclient.AddActionLogWithStartable(self, ag, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *AccessGroupSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + ag := obj.(*models.SAccessGroup) + + caches, err := ag.GetAccessGroupCaches() + if err != nil { + self.taskFail(ctx, ag, errors.Wrapf(err, "ag.GetAccessGroupCaches")) + return + } + + for i := range caches { + caches[i].SyncStatus(ctx, self.GetUserCred()) + } + + ag.SetStatus(self.GetUserCred(), api.ACCESS_GROUP_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/cloud_account_sync_skus_task.go b/pkg/compute/tasks/cloud_account_sync_skus_task.go index bee0c38325..7baafc8c57 100644 --- a/pkg/compute/tasks/cloud_account_sync_skus_task.go +++ b/pkg/compute/tasks/cloud_account_sync_skus_task.go @@ -105,6 +105,9 @@ func (self *CloudAccountSyncSkusTask) OnInit(ctx context.Context, obj db.IStanda case models.NatSkuManager.Keyword(): result := region.SyncNatSkus(ctx, self.GetUserCred(), meta) log.Infof("Sync %s %s skus for region %s result: %s", region.Provider, res, region.Name, result.Result()) + case models.NasSkuManager.Keyword(): + result := region.SyncNasSkus(ctx, self.GetUserCred(), meta) + log.Infof("Sync %s %s skus for region %s result: %s", region.Provider, res, region.Name, result.Result()) } if syncFunc != nil { diff --git a/pkg/compute/tasks/filesystem_create_task.go b/pkg/compute/tasks/filesystem_create_task.go new file mode 100644 index 0000000000..f5ab3412f1 --- /dev/null +++ b/pkg/compute/tasks/filesystem_create_task.go @@ -0,0 +1,100 @@ +// 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 fsreed 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 langufse governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type FileSystemCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(FileSystemCreateTask{}) +} + +func (self *FileSystemCreateTask) taskFailed(ctx context.Context, fs *models.SFileSystem, err error) { + fs.SetStatus(self.UserCred, api.NAS_STATUS_CREATE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, fs, logclient.ACT_ALLOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *FileSystemCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + fs := obj.(*models.SFileSystem) + + iRegion, err := fs.GetIRegion() + if err != nil { + self.taskFailed(ctx, fs, errors.Wrapf(err, "fs.GetIRegion")) + return + } + + opts := &cloudprovider.FileSystemCraeteOptions{ + Name: fs.Name, + Desc: fs.Description, + Capacity: fs.Capacity, + StorageType: fs.StorageType, + Protocol: fs.Protocol, + FileSystemType: fs.FileSystemType, + ZoneId: strings.TrimPrefix(fs.GetZone().ExternalId, iRegion.GetGlobalId()+"/"), + } + + netId := jsonutils.GetAnyString(self.GetParams(), []string{"network_id"}) + if len(netId) > 0 { + net, err := models.NetworkManager.FetchById(netId) + if err != nil { + self.taskFailed(ctx, fs, errors.Wrapf(err, "NetworkManager.FetchById(%s)", netId)) + return + } + network := net.(*models.SNetwork) + opts.NetworkId = network.ExternalId + opts.VpcId = network.GetVpc().ExternalId + } + + log.Infof("nas create params: %v", opts) + + iFs, err := iRegion.CreateICloudFileSystem(opts) + if err != nil { + self.taskFailed(ctx, fs, errors.Wrapf(err, "iRegion.CreaetICloudFileSystem")) + return + } + db.SetExternalId(fs, self.GetUserCred(), iFs.GetGlobalId()) + + cloudprovider.WaitStatus(iFs, api.NAS_STATUS_AVAILABLE, time.Second*5, time.Minute*3) + + self.SetStage("OnSyncstatusComplete", nil) + fs.StartSyncstatus(ctx, self.GetUserCred(), self.GetTaskId()) +} + +func (self *FileSystemCreateTask) OnSyncstatusComplete(ctx context.Context, fs *models.SFileSystem, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *FileSystemCreateTask) OnSyncstatusCompleteFailed(ctx context.Context, fs *models.SFileSystem, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data) +} diff --git a/pkg/compute/tasks/filesystem_delete_task.go b/pkg/compute/tasks/filesystem_delete_task.go new file mode 100644 index 0000000000..41d4aa3ca3 --- /dev/null +++ b/pkg/compute/tasks/filesystem_delete_task.go @@ -0,0 +1,75 @@ +// 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 fsreed 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 langufse governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type FileSystemDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(FileSystemDeleteTask{}) +} + +func (self *FileSystemDeleteTask) taskFailed(ctx context.Context, fs *models.SFileSystem, err error) { + fs.SetStatus(self.UserCred, api.NAS_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, fs, logclient.ACT_DELOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *FileSystemDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + fs := obj.(*models.SFileSystem) + + if len(fs.ExternalId) == 0 { + self.taskComplete(ctx, fs) + return + } + + iFs, err := fs.GetICloudFileSystem() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + self.taskComplete(ctx, fs) + return + } + self.taskFailed(ctx, fs, errors.Wrapf(err, "fs.GetICloudFileSystem")) + return + } + err = iFs.Delete() + if err != nil { + self.taskFailed(ctx, fs, errors.Wrapf(err, "iFs.Delete")) + return + } + cloudprovider.WaitDeleted(iFs, time.Second*10, time.Minute*5) + self.taskComplete(ctx, fs) +} + +func (self *FileSystemDeleteTask) taskComplete(ctx context.Context, fs *models.SFileSystem) { + fs.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/filesystem_syncstatus_task.go b/pkg/compute/tasks/filesystem_syncstatus_task.go new file mode 100644 index 0000000000..616d3ec842 --- /dev/null +++ b/pkg/compute/tasks/filesystem_syncstatus_task.go @@ -0,0 +1,61 @@ +// 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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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 FileSystemSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(FileSystemSyncstatusTask{}) +} + +func (self *FileSystemSyncstatusTask) taskFail(ctx context.Context, nas *models.SFileSystem, err error) { + nas.SetStatus(self.GetUserCred(), api.NAS_STATUS_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(nas, db.ACT_SYNC_STATUS, err, self.UserCred) + logclient.AddActionLogWithStartable(self, nas, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *FileSystemSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + nas := obj.(*models.SFileSystem) + + iNas, err := nas.GetICloudFileSystem() + if err != nil { + self.taskFail(ctx, nas, errors.Wrapf(err, "nas.GetICloudFileSystem")) + return + } + + err = nas.SyncAllWithCloudFileSystem(ctx, self.GetUserCred(), iNas) + if err != nil { + self.taskFail(ctx, nas, errors.Wrapf(err, "nas.SyncAllWithCloudFileSystem")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/mount_target_create_task.go b/pkg/compute/tasks/mount_target_create_task.go new file mode 100644 index 0000000000..89a0fa7c1f --- /dev/null +++ b/pkg/compute/tasks/mount_target_create_task.go @@ -0,0 +1,142 @@ +// 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" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type MountTargetCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(MountTargetCreateTask{}) +} + +func (self *MountTargetCreateTask) taskFailed(ctx context.Context, mt *models.SMountTarget, err error) { + mt.SetStatus(self.UserCred, api.MOUNT_TARGET_STATUS_CREATE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, mt, logclient.ACT_ALLOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *MountTargetCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + mt := obj.(*models.SMountTarget) + + fs, err := mt.GetFileSystem() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "GetFileSystem")) + return + } + region, err := fs.GetRegion() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "fs.GetRegion")) + return + } + + ag, err := mt.GetAccessGroup() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "mt.GetAccessGroup")) + return + } + + self.SetStage("OnSyncAccessGroupComplete", nil) + err = region.GetDriver().RequestSyncAccessGroup(ctx, self.GetUserCred(), fs, mt, ag, self) + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "RequestSyncAccessGroup")) + return + } +} + +func (self *MountTargetCreateTask) OnSyncAccessGroupCompleteFailed(ctx context.Context, mt *models.SMountTarget, data jsonutils.JSONObject) { + self.taskFailed(ctx, mt, errors.Error(data.String())) +} + +func (self *MountTargetCreateTask) OnSyncAccessGroupComplete(ctx context.Context, mt *models.SMountTarget, data jsonutils.JSONObject) { + opts := cloudprovider.SMountTargetCreateOptions{NetworkType: mt.NetworkType} + data.Unmarshal(&opts) + if len(opts.AccessGroupId) == 0 { + self.taskFailed(ctx, mt, errors.Error("empty access_group_id after sync")) + return + } + + fs, err := mt.GetFileSystem() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "GetFileSystem")) + return + } + + iFs, err := fs.GetICloudFileSystem() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "fs.GetICloudFileSystem")) + return + } + + if opts.NetworkType == api.NETWORK_TYPE_VPC { + network, err := mt.GetNetwork() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "mt.GetNetwork")) + return + } + opts.NetworkId = network.ExternalId + vpc, err := mt.GetVpc() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "mt.GetVpc")) + return + } + opts.VpcId = vpc.ExternalId + } + + opts.FileSystemId = fs.ExternalId + + iMt, err := iFs.CreateMountTarget(&opts) + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "iFs.CreateMountTarget")) + return + } + + cloudprovider.Wait(time.Second*10, time.Minute*3, func() (bool, error) { + mts, err := iFs.GetMountTargets() + if err != nil { + return false, errors.Wrapf(err, "iFs.GetMountTargets") + } + for i := range mts { + if mts[i].GetGlobalId() == iMt.GetGlobalId() { + status := mts[i].GetStatus() + log.Infof("expect mount point status %s current is %s", api.MOUNT_TARGET_STATUS_AVAILABLE, status) + if status == api.MOUNT_TARGET_STATUS_AVAILABLE { + iMt = mts[i] + return true, nil + } + } + } + return false, nil + }) + + mt.SyncWithMountTarget(ctx, self.GetUserCred(), fs.ManagerId, iMt) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/mount_target_delete_task.go b/pkg/compute/tasks/mount_target_delete_task.go new file mode 100644 index 0000000000..72eb1a55af --- /dev/null +++ b/pkg/compute/tasks/mount_target_delete_task.go @@ -0,0 +1,88 @@ +// 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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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 MountTargetDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(MountTargetDeleteTask{}) +} + +func (self *MountTargetDeleteTask) taskFailed(ctx context.Context, mt *models.SMountTarget, err error) { + mt.SetStatus(self.UserCred, api.MOUNT_TARGET_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, mt, logclient.ACT_DELOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *MountTargetDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + mt := obj.(*models.SMountTarget) + + if len(mt.ExternalId) == 0 { + self.taskComplete(ctx, nil, mt) + return + } + + fs, err := mt.GetFileSystem() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "GetFileSystem")) + return + } + iFileSystem, err := fs.GetICloudFileSystem() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "GetICloudFileSystem")) + return + } + iMountTargets, err := iFileSystem.GetMountTargets() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "GetMountTargets")) + return + } + for i := range iMountTargets { + if iMountTargets[i].GetGlobalId() == mt.ExternalId { + err = iMountTargets[i].Delete() + if err != nil { + self.taskFailed(ctx, mt, errors.Wrapf(err, "iMountTarget.Delete")) + return + } + self.taskComplete(ctx, fs, mt) + return + } + } + + self.taskComplete(ctx, fs, mt) +} + +func (self *MountTargetDeleteTask) taskComplete(ctx context.Context, fs *models.SFileSystem, mt *models.SMountTarget) { + if fs != nil { + logclient.AddActionLogWithStartable(self, fs, logclient.ACT_DELOCATE, mt, self.UserCred, true) + } + mt.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/mount_target_syncstatus_task.go b/pkg/compute/tasks/mount_target_syncstatus_task.go new file mode 100644 index 0000000000..e2bfe553e7 --- /dev/null +++ b/pkg/compute/tasks/mount_target_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" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type MountTargetSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(MountTargetSyncstatusTask{}) +} + +func (self *MountTargetSyncstatusTask) taskFail(ctx context.Context, mt *models.SMountTarget, err error) { + mt.SetStatus(self.GetUserCred(), api.MOUNT_TARGET_STATUS_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(mt, db.ACT_SYNC_STATUS, err, self.UserCred) + logclient.AddActionLogWithStartable(self, mt, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *MountTargetSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + mt := obj.(*models.SMountTarget) + + fs, err := mt.GetFileSystem() + if err != nil { + self.taskFail(ctx, mt, errors.Wrapf(err, "GetFileSystem")) + return + } + iFs, err := fs.GetICloudFileSystem() + if err != nil { + self.taskFail(ctx, mt, errors.Wrapf(err, "fs.GetICloudFileSystem")) + return + } + mts, err := iFs.GetMountTargets() + if err != nil { + self.taskFail(ctx, mt, errors.Wrapf(err, "iFs.GetMountTargets")) + return + } + for i := range mts { + if mts[i].GetGlobalId() == mt.ExternalId { + mt.SyncWithMountTarget(ctx, self.GetUserCred(), fs.ManagerId, mts[i]) + self.SetStageComplete(ctx, nil) + return + } + } + + self.taskFail(ctx, mt, errors.Wrapf(cloudprovider.ErrNotFound, mt.ExternalId)) +} diff --git a/pkg/mcclient/modules/mod_access_group_caches.go b/pkg/mcclient/modules/mod_access_group_caches.go new file mode 100644 index 0000000000..c113c2a6b0 --- /dev/null +++ b/pkg/mcclient/modules/mod_access_group_caches.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type AccessGroupCacheManager struct { + modulebase.ResourceManager +} + +var ( + AccessGroupCaches AccessGroupCacheManager +) + +func init() { + AccessGroupCaches = AccessGroupCacheManager{NewComputeManager("access_group_cache", "access_group_caches", + []string{}, + []string{})} + + registerCompute(&AccessGroupCaches) +} diff --git a/pkg/mcclient/modules/mod_access_group_rules.go b/pkg/mcclient/modules/mod_access_group_rules.go new file mode 100644 index 0000000000..56c12fcc9e --- /dev/null +++ b/pkg/mcclient/modules/mod_access_group_rules.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type AccessGroupRuleManager struct { + modulebase.ResourceManager +} + +var ( + AccessGroupRules AccessGroupRuleManager +) + +func init() { + AccessGroupRules = AccessGroupRuleManager{NewComputeManager("access_group_rule", "access_group_rules", + []string{}, + []string{})} + + registerCompute(&AccessGroupRules) +} diff --git a/pkg/mcclient/modules/mod_access_groups.go b/pkg/mcclient/modules/mod_access_groups.go new file mode 100644 index 0000000000..77575ded29 --- /dev/null +++ b/pkg/mcclient/modules/mod_access_groups.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type AccessGroupManager struct { + modulebase.ResourceManager +} + +var ( + AccessGroups AccessGroupManager +) + +func init() { + AccessGroups = AccessGroupManager{NewComputeManager("access_group", "access_groups", + []string{}, + []string{})} + + registerCompute(&AccessGroups) +} diff --git a/pkg/mcclient/modules/mod_filesystem.go b/pkg/mcclient/modules/mod_filesystem.go new file mode 100644 index 0000000000..59e535c81f --- /dev/null +++ b/pkg/mcclient/modules/mod_filesystem.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type FileSystemManager struct { + modulebase.ResourceManager +} + +var ( + FileSystems FileSystemManager +) + +func init() { + FileSystems = FileSystemManager{NewComputeManager("file_system", "file_systems", + []string{}, + []string{})} + + registerCompute(&FileSystems) +} diff --git a/pkg/mcclient/modules/mod_mount_targets.go b/pkg/mcclient/modules/mod_mount_targets.go new file mode 100644 index 0000000000..1a3e528905 --- /dev/null +++ b/pkg/mcclient/modules/mod_mount_targets.go @@ -0,0 +1,35 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type MountTargetManager struct { + modulebase.ResourceManager +} + +var ( + MountTargets MountTargetManager +) + +func init() { + MountTargets = MountTargetManager{NewComputeManager("mount_target", "mount_targets", + []string{}, + []string{})} + + registerCompute(&MountTargets) +} diff --git a/pkg/mcclient/modules/mod_nas_skus.go b/pkg/mcclient/modules/mod_nas_skus.go new file mode 100644 index 0000000000..c7e0f59774 --- /dev/null +++ b/pkg/mcclient/modules/mod_nas_skus.go @@ -0,0 +1,33 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type NasSkusManager struct { + modulebase.ResourceManager +} + +var ( + NasSkus NasSkusManager +) + +func init() { + NasSkus = NasSkusManager{NewComputeManager("nas_sku", "nas_skus", + []string{}, + []string{})} + + registerCompute(&NasSkus) +} diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index 4e697e3a57..677878cfe8 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -375,6 +375,10 @@ type BaseCreateOptions struct { Desc string `metavar:"" help:"Description" json:"description"` } +func (opts *BaseCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type EnabledStatusCreateOptions struct { BaseCreateOptions Status string diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 48713537bc..0949b8c816 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -840,7 +840,7 @@ func (opts *CloudaccountShareModeOptions) Params() (jsonutils.JSONObject, error) type CloudaccountSyncSkusOptions struct { SCloudAccountIdOptions - RESOURCE string `help:"Resource of skus" choices:"serversku|elasticcachesku|dbinstance_sku|nat_sku"` + RESOURCE string `help:"Resource of skus" choices:"serversku|elasticcachesku|dbinstance_sku|nat_sku|nas_sku"` Force bool `help:"Force sync no matter what"` Cloudprovider string `help:"provider to sync"` Region string `help:"region to sync"` diff --git a/pkg/mcclient/options/compute/access_group_caches.go b/pkg/mcclient/options/compute/access_group_caches.go new file mode 100644 index 0000000000..1ae215e09d --- /dev/null +++ b/pkg/mcclient/options/compute/access_group_caches.go @@ -0,0 +1,41 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type AccessGroupCacheListOptions struct { + options.BaseListOptions +} + +func (opts *AccessGroupCacheListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type AccessGroupCacheIdOption struct { + ID string `help:"Access group cache Id"` +} + +func (opts *AccessGroupCacheIdOption) GetId() string { + return opts.ID +} + +func (opts *AccessGroupCacheIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/compute/access_group_rules.go b/pkg/mcclient/options/compute/access_group_rules.go new file mode 100644 index 0000000000..2b1eecad3c --- /dev/null +++ b/pkg/mcclient/options/compute/access_group_rules.go @@ -0,0 +1,54 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type AccessGroupRuleListOptions struct { + options.BaseListOptions +} + +func (opts *AccessGroupRuleListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type AccessGroupRuleIdOption struct { + ID string `help:"Access group rule Id"` +} + +func (opts *AccessGroupRuleIdOption) GetId() string { + return opts.ID +} + +func (opts *AccessGroupRuleIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type AccessRuleCreateOptions struct { + Priority int + Source string + RWAccessType string `choices:"rw|r"` + UserAccessType string `choices:""` + Description string + AccessGroupId string +} + +func (opts *AccessRuleCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} diff --git a/pkg/mcclient/options/compute/access_groups.go b/pkg/mcclient/options/compute/access_groups.go new file mode 100644 index 0000000000..4db25f75d7 --- /dev/null +++ b/pkg/mcclient/options/compute/access_groups.go @@ -0,0 +1,41 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type AccessGroupListOptions struct { + options.BaseListOptions +} + +func (opts *AccessGroupListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type AccessGroupIdOption struct { + ID string `help:"Access group Id"` +} + +func (opts *AccessGroupIdOption) GetId() string { + return opts.ID +} + +func (opts *AccessGroupIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} diff --git a/pkg/mcclient/options/compute/filesystem.go b/pkg/mcclient/options/compute/filesystem.go new file mode 100644 index 0000000000..e2ac4b74e8 --- /dev/null +++ b/pkg/mcclient/options/compute/filesystem.go @@ -0,0 +1,56 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type FileSystemListOptions struct { + options.BaseListOptions +} + +func (opts *FileSystemListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type FileSystemIdOption struct { + ID string `help:"File system Id"` +} + +func (opts *FileSystemIdOption) GetId() string { + return opts.ID +} + +func (opts *FileSystemIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type FileSystemCreateOptions struct { + NAME string + Protocol string `choices:"NFS|SMB|CPFS"` + FileSystemType string + Capacity int64 `json:"capacity"` + NetworkId string `json:"network_id"` + StorageType string `json:"storage_type"` + ZoneId string `json:"zone_id"` + ManagerId string `json:"manager_id"` +} + +func (opts *FileSystemCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} diff --git a/pkg/mcclient/options/compute/mount_targets.go b/pkg/mcclient/options/compute/mount_targets.go new file mode 100644 index 0000000000..70b78379fe --- /dev/null +++ b/pkg/mcclient/options/compute/mount_targets.go @@ -0,0 +1,53 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type MountTargetListOptions struct { + options.BaseListOptions +} + +func (opts *MountTargetListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type MountTargetIdOption struct { + ID string `help:"Mount Target Id"` +} + +func (opts *MountTargetIdOption) GetId() string { + return opts.ID +} + +func (opts *MountTargetIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type MountTargetCreateOptions struct { + NAME string + NetworkType string `choices:"vpc|classic"` + FileSystemId string `json:"file_system_id"` + NetworkId string `json:"network_id"` + AccessGroupId string `json:"access_group_id"` +} + +func (opts *MountTargetCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} diff --git a/pkg/mcclient/options/compute/nas_skus.go b/pkg/mcclient/options/compute/nas_skus.go new file mode 100644 index 0000000000..1e36e33d2a --- /dev/null +++ b/pkg/mcclient/options/compute/nas_skus.go @@ -0,0 +1,41 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type NasSkuListOption struct { + options.BaseListOptions +} + +func (opts *NasSkuListOption) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type NasSkuIdOption struct { + ID string `help:"Nas Id or name"` +} + +func (opts *NasSkuIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +func (opts *NasSkuIdOption) GetId() string { + return opts.ID +} diff --git a/pkg/multicloud/aliyun/access_groups.go b/pkg/multicloud/aliyun/access_groups.go new file mode 100644 index 0000000000..957eba7e3a --- /dev/null +++ b/pkg/multicloud/aliyun/access_groups.go @@ -0,0 +1,324 @@ +// 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 aliyun + +import ( + "fmt" + "strings" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SAccessGroup struct { + region *SRegion + + RuleCount int + AccessGroupType string + Description string + AccessGroupName string + MountTargetCount int + FileSystemType string +} + +func (self *SAccessGroup) GetName() string { + return self.AccessGroupName +} + +func (self *SAccessGroup) GetGlobalId() string { + return fmt.Sprintf("%s/%s", self.GetFileSystemType(), self.AccessGroupName) +} + +func (self *SAccessGroup) GetFileSystemType() string { + return self.FileSystemType +} + +func (self *SAccessGroup) IsDefault() bool { + return self.AccessGroupName == "DEFAULT_CLASSIC_GROUP_NAME" || self.AccessGroupName == "DEFAULT_VPC_GROUP_NAME" +} + +func (self *SAccessGroup) GetDesc() string { + return self.Description +} + +func (self *SAccessGroup) GetMountTargetCount() int { + return self.MountTargetCount +} + +func (self *SAccessGroup) GetNetworkType() string { + return strings.ToLower(self.AccessGroupType) +} + +func (self *SAccessGroup) GetMaxPriority() int { + return 1 +} + +func (self *SAccessGroup) GetMinPriority() int { + return 100 +} + +func (self *SAccessGroup) Delete() error { + return self.region.DeleteAccessGroup(self.FileSystemType, self.AccessGroupName) +} + +func (self *SAccessGroup) GetSupporedUserAccessTypes() []cloudprovider.TUserAccessType { + return []cloudprovider.TUserAccessType{ + cloudprovider.UserAccessTypeAllSquash, + cloudprovider.UserAccessTypeRootSquash, + cloudprovider.UserAccessTypeNoRootSquash, + } +} + +func (self *SAccessGroup) GetRules() ([]cloudprovider.AccessGroupRule, error) { + rules := []SAccessGroupRule{} + num := 1 + for { + part, total, err := self.region.GetAccessGroupRules(self.AccessGroupName, 50, num) + if err != nil { + return nil, errors.Wrapf(err, "GetAccessGroupRules") + } + rules = append(rules, part...) + if len(rules) >= total { + break + } + } + ret := []cloudprovider.AccessGroupRule{} + for i := range rules { + rule := cloudprovider.AccessGroupRule{ + ExternalId: rules[i].AccessRuleId, + Priority: rules[i].Priority, + Source: rules[i].SourceCidrIp, + } + switch rules[i].RWAccess { + case "RDWR": + rule.RWAccessType = cloudprovider.RWAccessTypeRW + case "RDONLY": + rule.RWAccessType = cloudprovider.RWAccessTypeR + } + switch rules[i].UserAccess { + case "no_squash": + rule.UserAccessType = cloudprovider.UserAccessTypeNoRootSquash + case "root_squash": + rule.UserAccessType = cloudprovider.UserAccessTypeRootSquash + case "all_squash": + rule.UserAccessType = cloudprovider.UserAccessTypeAllSquash + } + ret = append(ret, rule) + } + return ret, nil +} + +func (self *SRegion) getAccessGroups(fsType string, pageSize, pageNum int) ([]SAccessGroup, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 50 + } + if pageNum < 1 { + pageNum = 1 + } + params := map[string]string{ + "RegionId": self.RegionId, + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + "FileSystemType": fsType, + } + resp, err := self.nasRequest("DescribeAccessGroups", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeAccessGroups") + } + + ret := struct { + TotalCount int + AccessGroups struct { + AccessGroup []SAccessGroup + } + }{} + err = resp.Unmarshal(&ret) + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + return ret.AccessGroups.AccessGroup, ret.TotalCount, nil +} + +type SAccessGroupRule struct { + RWAccess string + UserAccess string + Priority int + SourceCidrIp string + AccessRuleId string +} + +func (self *SRegion) GetAccessGroupRules(groupName string, pageSize, pageNum int) ([]SAccessGroupRule, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 50 + } + if pageNum < 1 { + pageNum = 1 + } + params := map[string]string{ + "RegionId": self.RegionId, + "AccessGroupName": groupName, + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + } + resp, err := self.nasRequest("DescribeAccessRules", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeAccessRules") + } + ret := struct { + TotalCount int + AccessRules struct { + AccessRule []SAccessGroupRule + } + }{} + err = resp.Unmarshal(&ret) + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + return ret.AccessRules.AccessRule, ret.TotalCount, nil +} + +func (self *SRegion) GetAccessGroups(fsType string) ([]SAccessGroup, error) { + accessGroups := []SAccessGroup{} + num := 1 + for { + part, total, err := self.getAccessGroups(fsType, 50, num) + if err != nil { + return nil, errors.Wrapf(err, "GetAccessGroups") + } + for i := range part { + part[i].FileSystemType = fsType + accessGroups = append(accessGroups, part[i]) + } + if len(accessGroups) >= total { + break + } + } + return accessGroups, nil +} + +func (self *SRegion) GetICloudAccessGroups() ([]cloudprovider.ICloudAccessGroup, error) { + standardAccessGroups, err := self.GetAccessGroups("standard") + if err != nil { + return nil, errors.Wrapf(err, "GetAccessGroups") + } + extremeAccessGroups, err := self.GetAccessGroups("extreme") + if err != nil { + return nil, errors.Wrapf(err, "GetAccessGroups") + } + ret := []cloudprovider.ICloudAccessGroup{} + for _, accessGroups := range [][]SAccessGroup{standardAccessGroups, extremeAccessGroups} { + for i := range accessGroups { + accessGroups[i].region = self + ret = append(ret, &accessGroups[i]) + } + } + return ret, nil +} + +func (self *SRegion) GetICloudAccessGroupById(id string) (cloudprovider.ICloudAccessGroup, error) { + groups, err := self.GetICloudAccessGroups() + if err != nil { + return nil, errors.Wrapf(err, "self.GetICloudAccessGroups") + } + for i := range groups { + if groups[i].GetGlobalId() == id { + return groups[i], nil + } + } + return nil, errors.Wrapf(err, "GetICloudAccessGroupById(%s)", id) +} + +func (self *SRegion) CreateICloudAccessGroup(opts *cloudprovider.SAccessGroup) (cloudprovider.ICloudAccessGroup, error) { + err := self.CreateAccessGroup(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateAccessGroup") + } + return self.GetICloudAccessGroupById(fmt.Sprintf("%s/%s", opts.FileSystemType, opts.Name)) +} + +func (self *SRegion) CreateAccessGroup(opts *cloudprovider.SAccessGroup) error { + params := map[string]string{ + "RegionId": self.RegionId, + "AccessGroupName": opts.Name, + "AccessGroupType": opts.NetworkType, + "FileSystemType": opts.FileSystemType, + } + _, err := self.nasRequest("CreateAccessGroup", params) + return errors.Wrapf(err, "CreateAccessGroup") +} + +func (self *SRegion) DeleteAccessGroup(fsType, name string) error { + params := map[string]string{ + "RegionId": self.RegionId, + "AccessGroupName": name, + "FileSystemType": fsType, + } + _, err := self.nasRequest("DeleteAccessGroup", params) + return errors.Wrapf(err, "DeleteAccessGroup") +} + +func (self *SRegion) DeleteAccessGroupRule(fsType, groupName, ruleId string) error { + params := map[string]string{ + "RegionId": self.RegionId, + "AccessRuleId": ruleId, + "AccessGroupName": groupName, + "FileSystemType": fsType, + } + _, err := self.nasRequest("DeleteAccessRule", params) + return errors.Wrapf(err, "DeleteAccessRule") +} + +func (self *SRegion) CreateAccessGroupRule(source, fsType, groupName string, rwType cloudprovider.TRWAccessType, userType cloudprovider.TUserAccessType, priority int) error { + params := map[string]string{ + "RegionId": self.RegionId, + "SourceCidrIp": source, + "AccessGroupName": groupName, + "Priority": fmt.Sprintf("%d", priority), + "FileSystemType": fsType, + } + switch rwType { + case cloudprovider.RWAccessTypeR: + params["RWAccessType"] = "RDONLY" + case cloudprovider.RWAccessTypeRW: + params["RWAccessType"] = "RDWR" + } + switch userType { + case cloudprovider.UserAccessTypeAllSquash: + params["UserAccessType"] = "all_squash" + case cloudprovider.UserAccessTypeRootSquash: + params["UserAccessType"] = "root_squash" + case cloudprovider.UserAccessTypeNoRootSquash: + params["UserAccessType"] = "no_squash" + } + _, err := self.nasRequest("CreateAccessRule", params) + return errors.Wrapf(err, "CreateAccessRule") +} + +func (self *SAccessGroup) SyncRules(common, added, removed cloudprovider.AccessGroupRuleSet) error { + for _, rule := range removed { + err := self.region.DeleteAccessGroupRule(self.FileSystemType, self.AccessGroupName, rule.ExternalId) + if err != nil { + return errors.Wrapf(err, "DeleteAccessGroupRule") + } + } + for _, rule := range added { + err := self.region.CreateAccessGroupRule(rule.Source, self.FileSystemType, self.AccessGroupName, rule.RWAccessType, rule.UserAccessType, rule.Priority) + if err != nil { + return errors.Wrapf(err, "CreateAccessGroupRule") + } + } + return nil +} diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index 53ff155cd5..82767eb998 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -64,6 +64,7 @@ const ( ALIYUN_CBN_API_VERSION = "2017-09-12" ALIYUN_CDN_API_VERSION = "2018-05-10" ALIYUN_IMS_API_VERSION = "2019-08-15" + ALIYUN_NAS_API_VERSION = "2017-06-26" ALIYUN_SERVICE_ECS = "ecs" ALIYUN_SERVICE_VPC = "vpc" @@ -159,13 +160,21 @@ func jsonRequest(client *sdk.Client, domain, apiVersion, apiName string, params switch code { case "InvalidAccessKeyId.NotFound": return nil, err - case "SignatureNonceUsed", - "InvalidInstance.NotSupported", - "BackendServer.configuring", - "OperationUnsupported.EipNatBWPCheck": + case "404 Not Found": + return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) + case "InvalidInstance.NotSupported", + "SignatureNonceUsed", // SignatureNonce 重复。每次请求的 SignatureNonce 在 15 分钟内不能重复。 + "BackendServer.configuring", // 负载均衡的前一个配置项正在配置中,请稍后再试。 + "Operation.Conflict", // 您当前的操作可能与其他人的操作产生了冲突,请稍后重试。 + "OperationDenied.ResourceControl", // 指定的区域处于资源控制中,请稍后再试。 + "ServiceIsStopping", // 监听正在停止,请稍后重试。 + "ProcessingSameRequest", // 正在处理相同的请求。请稍后再试。 + "ResourceInOperating", // 当前资源正在操作中,请求稍后重试。 + "InvalidFileSystemStatus.Ordering", // Message: The filesystem is ordering now, please check it later. + "OperationUnsupported.EipNatBWPCheck": // create nat snat retry = true default: - if strings.HasPrefix(code, "EntityNotExist") || strings.HasSuffix(code, "NotFound") { + if strings.HasPrefix(code, "EntityNotExist.") || strings.HasSuffix(code, ".NotFound") { return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) } return nil, err @@ -554,6 +563,7 @@ func (region *SAliyunClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, cloudprovider.CLOUD_CAPABILITY_NAT, + cloudprovider.CLOUD_CAPABILITY_NAS, } return caps } diff --git a/pkg/multicloud/aliyun/filesystem.go b/pkg/multicloud/aliyun/filesystem.go new file mode 100644 index 0000000000..1c0b0d8c41 --- /dev/null +++ b/pkg/multicloud/aliyun/filesystem.go @@ -0,0 +1,350 @@ +// 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 aliyun + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SupportedFeatures struct { + SupportedFeature []string +} + +type MountTargets struct { + MountTarget []SMountTarget +} + +type Package struct { +} + +type Packages struct { + Package []Package +} + +type SFileSystem struct { + multicloud.SNasBase + region *SRegion + + Status string + Description string + StorageType string + MountTargetCountLimit int + Ldap string + ZoneId string + // 2021-03-22T10:08:15CST + CreateTime string + MeteredIASize int + SupportedFeatures SupportedFeatures + MountTargets MountTargets + AutoSnapshotPolicyId string + MeteredSize int64 + EncryptType int + Capacity int64 + ProtocolType string + ChargeType string + Packages Packages + ExpiredTime string + FileSystemType string + FileSystemId string + RegionId string + + Tags Tags +} + +func (self *SFileSystem) GetId() string { + return self.FileSystemId +} + +func (self *SFileSystem) GetGlobalId() string { + return self.FileSystemId +} + +func (self *SFileSystem) GetName() string { + if len(self.Description) > 0 { + return self.Description + } + return self.FileSystemId +} + +func (self *SFileSystem) GetFileSystemType() string { + return self.FileSystemType +} + +func (self *SFileSystem) GetMountTargetCountLimit() int { + return self.MountTargetCountLimit +} + +func (self *SFileSystem) GetStatus() string { + switch self.Status { + case "", "Running": + return api.NAS_STATUS_AVAILABLE + case "Extending": + return api.NAS_STATUS_EXTENDING + case "Stopping", "Stopped": + return api.NAS_STATUS_UNAVAILABLE + case "Pending": + return api.NAS_STATUS_CREATING + default: + return api.NAS_STATUS_UNKNOWN + } +} + +func (self *SFileSystem) GetBillintType() string { + if self.ChargeType == "PayAsYouGo" { + return billing_api.BILLING_TYPE_POSTPAID + } + return billing_api.BILLING_TYPE_PREPAID +} + +func (self *SFileSystem) GetStorageType() string { + return strings.ToLower(self.StorageType) +} + +func (self *SFileSystem) GetProtocol() string { + return self.ProtocolType +} + +func (self *SFileSystem) GetCapacityGb() int64 { + return self.Capacity +} + +func (self *SFileSystem) GetUsedCapacityGb() int64 { + return self.MeteredSize +} + +func (self *SFileSystem) GetZoneId() string { + return self.ZoneId +} + +func (self *SFileSystem) Delete() error { + return self.region.DeleteFileSystem(self.FileSystemId) +} + +func (self *SFileSystem) GetCreatedAt() time.Time { + ret, _ := time.Parse("2006-01-02T15:04:05CST", self.CreateTime) + if !ret.IsZero() { + ret = ret.Add(time.Hour * 8) + } + return ret +} + +func (self *SFileSystem) GetExpiredAt() time.Time { + ret, _ := time.Parse("2006-01-02T15:04:05CST", self.ExpiredTime) + if !ret.IsZero() { + ret = ret.Add(time.Hour * 8) + } + return ret +} + +func (self *SFileSystem) Refresh() error { + fs, err := self.region.GetFileSystem(self.FileSystemId) + if err != nil { + return errors.Wrapf(err, "GetFileSystem(%s)", self.FileSystemId) + } + return jsonutils.Update(self, fs) +} + +func (self *SFileSystem) GetTags() (map[string]string, error) { + return self.Tags.GetTags() +} + +func (self *SRegion) GetFileSystems(id string, pageSize, pageNum int) ([]SFileSystem, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 50 + } + if pageNum < 1 { + pageNum = 1 + } + params := map[string]string{ + "RegionId": self.RegionId, + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + } + if len(id) > 0 { + params["FileSystemId"] = id + } + resp, err := self.nasRequest("DescribeFileSystems", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeFileSystems") + } + ret := struct { + TotalCount int + FileSystems struct { + FileSystem []SFileSystem + } + }{} + err = resp.Unmarshal(&ret) + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + return ret.FileSystems.FileSystem, ret.TotalCount, nil +} + +func (self *SRegion) GetICloudFileSystems() ([]cloudprovider.ICloudFileSystem, error) { + nas := []SFileSystem{} + num := 1 + for { + part, total, err := self.GetFileSystems("", 100, num) + if err != nil { + return nil, errors.Wrapf(err, "GetFileSystems") + } + nas = append(nas, part...) + if total <= len(nas) { + break + } + num++ + } + + ret := []cloudprovider.ICloudFileSystem{} + for i := range nas { + nas[i].region = self + ret = append(ret, &nas[i]) + } + return ret, nil +} + +func (self *SRegion) GetFileSystem(id string) (*SFileSystem, error) { + if len(id) == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty id") + } + nas, total, err := self.GetFileSystems(id, 1, 1) + if err != nil { + return nil, errors.Wrapf(err, "GetFileSystems") + } + if total == 1 { + nas[0].region = self + return &nas[0], nil + } + if total == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) + } + return nil, errors.Wrapf(cloudprovider.ErrDuplicateId, id) +} + +func (self *SRegion) GetICloudFileSystemById(id string) (cloudprovider.ICloudFileSystem, error) { + fs, err := self.GetFileSystem(id) + if err != nil { + return nil, errors.Wrapf(err, "self.GetFileSystem") + } + return fs, nil +} + +func (self *SRegion) CreateMountTarget(opts *cloudprovider.SMountTargetCreateOptions) (*SMountTarget, error) { + params := map[string]string{ + "RegionId": self.RegionId, + "FileSystemId": opts.FileSystemId, + "AccessGroupName": strings.TrimPrefix(strings.TrimPrefix(opts.AccessGroupId, "extreme/"), "standard/"), + "NetworkType": opts.NetworkType, + "VpcId": opts.VpcId, + "VSwitchId": opts.NetworkId, + } + resp, err := self.nasRequest("CreateMountTarget", params) + if err != nil { + return nil, errors.Wrapf(err, "CreateMountTarget") + } + ret := struct { + MountTargetDomain string + }{} + err = resp.Unmarshal(&ret) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + mts, _, err := self.GetMountTargets(opts.FileSystemId, ret.MountTargetDomain, 10, 1) + if err != nil { + return nil, errors.Wrapf(err, "self.GetMountTargets") + } + for i := range mts { + if mts[i].MountTargetDomain == ret.MountTargetDomain { + return &mts[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "afeter create with mount domain %s", ret.MountTargetDomain) +} + +func (self *SFileSystem) CreateMountTarget(opts *cloudprovider.SMountTargetCreateOptions) (cloudprovider.ICloudMountTarget, error) { + mt, err := self.region.CreateMountTarget(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateMountTarget") + } + mt.fs = self + return mt, nil +} + +func (self *SRegion) DeleteFileSystem(id string) error { + params := map[string]string{ + "RegionId": self.RegionId, + "FileSystemId": id, + } + _, err := self.nasRequest("DeleteFileSystem", params) + return errors.Wrapf(err, "DeleteFileSystem") +} + +func (self *SRegion) CreateICloudFileSystem(opts *cloudprovider.FileSystemCraeteOptions) (cloudprovider.ICloudFileSystem, error) { + fs, err := self.CreateFileSystem(opts) + if err != nil { + return nil, errors.Wrapf(err, "self.CreateFileSystem") + } + return fs, nil +} + +func (self *SRegion) CreateFileSystem(opts *cloudprovider.FileSystemCraeteOptions) (*SFileSystem, error) { + params := map[string]string{ + "RegionId": self.RegionId, + "ProtocolType": opts.Protocol, + "ZoneId": opts.ZoneId, + "EncryptType": "0", + "FileSystemType": opts.FileSystemType, + "StorageType": opts.StorageType, + "ClientToken": utils.GenRequestId(20), + "Description": opts.Name, + } + switch opts.FileSystemType { + case "standard": + params["StorageType"] = utils.Capitalize(opts.StorageType) + case "cpfs": + params["ProtocolType"] = "cpfs" + switch opts.StorageType { + case "advance_100": + params["Bandwidth"] = "100" + case "advance_200": + params["Bandwidth"] = "200" + } + case "extreme": + params["Capacity"] = fmt.Sprintf("%d", opts.Capacity) + } + if len(opts.VpcId) > 0 { + params["VpcId"] = opts.VpcId + } + if len(opts.NetworkId) > 0 { + params["VSwitchId"] = opts.NetworkId + } + resp, err := self.nasRequest("CreateFileSystem", params) + if err != nil { + return nil, errors.Wrapf(err, "CreateFileSystem") + } + fsId, _ := resp.GetString("FileSystemId") + return self.GetFileSystem(fsId) +} diff --git a/pkg/multicloud/aliyun/mount_target.go b/pkg/multicloud/aliyun/mount_target.go new file mode 100644 index 0000000000..9a1408b78d --- /dev/null +++ b/pkg/multicloud/aliyun/mount_target.go @@ -0,0 +1,159 @@ +// 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 aliyun + +import ( + "fmt" + "strings" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type ClientMasterNode struct { + EcsIp string + EcsId string + DefaultPasswd string +} + +type ClientMasterNodes struct { + ClientMasterNode []ClientMasterNode +} + +type SMountTarget struct { + fs *SFileSystem + + Status string + NetworkType string + VswId string + VpcId string + MountTargetDomain string + AccessGroup string + ClientMasterNodes ClientMasterNodes + Tags Tags +} + +func (self *SMountTarget) GetGlobalId() string { + return self.MountTargetDomain +} + +func (self *SMountTarget) GetName() string { + return self.MountTargetDomain +} + +func (self *SMountTarget) GetNetworkType() string { + return strings.ToLower(self.NetworkType) +} + +func (self *SMountTarget) GetStatus() string { + switch self.Status { + case "Active": + return api.MOUNT_TARGET_STATUS_AVAILABLE + case "Inactive": + return api.MOUNT_TARGET_STATUS_UNAVAILABLE + case "Pending": + return api.MOUNT_TARGET_STATUS_CREATING + case "Deleting": + return api.MOUNT_TARGET_STATUS_DELETING + default: + return strings.ToLower(self.Status) + } +} + +func (self *SMountTarget) GetVpcId() string { + return self.VpcId +} + +func (self *SMountTarget) GetNetworkId() string { + return self.VswId +} + +func (self *SMountTarget) GetDomainName() string { + return self.MountTargetDomain +} + +func (self *SMountTarget) GetAccessGroupId() string { + return fmt.Sprintf("%s/%s", self.fs.FileSystemType, self.AccessGroup) +} + +func (self *SMountTarget) Delete() error { + return self.fs.region.DeleteMountTarget(self.fs.FileSystemId, self.MountTargetDomain) +} + +func (self *SRegion) GetMountTargets(fsId, domainName string, pageSize, pageNum int) ([]SMountTarget, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 50 + } + if pageNum < 1 { + pageNum = 1 + } + params := map[string]string{ + "RegionId": self.RegionId, + "FileSystemId": fsId, + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + } + if len(domainName) > 0 { + params["MountTargetDomain"] = domainName + } + resp, err := self.nasRequest("DescribeMountTargets", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeMountTargets") + } + ret := struct { + TotalCount int + MountTargets struct { + MountTarget []SMountTarget + } + }{} + err = resp.Unmarshal(&ret) + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + return ret.MountTargets.MountTarget, ret.TotalCount, nil +} + +func (self *SFileSystem) GetMountTargets() ([]cloudprovider.ICloudMountTarget, error) { + targets := []SMountTarget{} + num := 1 + for { + part, total, err := self.region.GetMountTargets(self.FileSystemId, "", 50, num) + if err != nil { + return nil, errors.Wrapf(err, "GetMountTargets") + } + targets = append(targets, part...) + if len(part) >= total { + break + } + } + ret := []cloudprovider.ICloudMountTarget{} + for i := range targets { + targets[i].fs = self + ret = append(ret, &targets[i]) + } + return ret, nil +} + +func (self *SRegion) DeleteMountTarget(fsId, id string) error { + params := map[string]string{ + "RegionId": self.RegionId, + "FileSystemId": fsId, + "MountTargetDomain": id, + } + _, err := self.nasRequest("DeleteMountTarget", params) + return errors.Wrapf(err, "DeleteMountTarget") +} diff --git a/pkg/multicloud/aliyun/region.go b/pkg/multicloud/aliyun/region.go index c214a77aa3..9eb75afb4c 100644 --- a/pkg/multicloud/aliyun/region.go +++ b/pkg/multicloud/aliyun/region.go @@ -153,6 +153,15 @@ func (self *SRegion) vpcRequest(action string, params map[string]string) (jsonut return jsonRequest(client, "vpc.aliyuncs.com", ALIYUN_API_VERSION_VPC, action, params, self.client.debug) } +func (self *SRegion) nasRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + + return jsonRequest(client, "nas.aliyuncs.com", ALIYUN_NAS_API_VERSION, action, params, self.client.debug) +} + func (self *SRegion) kvsRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { client, err := self.getSdkClient() if err != nil { diff --git a/pkg/multicloud/aliyun/securitygroup.go b/pkg/multicloud/aliyun/securitygroup.go index 7eab5a1eeb..edf953c6ee 100644 --- a/pkg/multicloud/aliyun/securitygroup.go +++ b/pkg/multicloud/aliyun/securitygroup.go @@ -66,6 +66,14 @@ type Tags struct { Tag []Tag } +func (t Tags) GetTags() (map[string]string, error) { + ret := map[string]string{} + for _, tag := range t.Tag { + ret[tag.TagKey] = tag.TagValue + } + return ret, nil +} + type Tag struct { TagKey string TagValue string diff --git a/pkg/multicloud/aliyun/shell/access_groups.go b/pkg/multicloud/aliyun/shell/access_groups.go new file mode 100644 index 0000000000..b937ddacd5 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/access_groups.go @@ -0,0 +1,87 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NasAccessGroupListOptions struct { + FileSystemType string `choices:"standard|extreme" default:"standard"` + } + shellutils.R(&NasAccessGroupListOptions{}, "access-group-list", "List Nas AccessGroups", func(cli *aliyun.SRegion, args *NasAccessGroupListOptions) error { + ags, err := cli.GetAccessGroups(args.FileSystemType) + if err != nil { + return err + } + printList(ags, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&cloudprovider.SAccessGroup{}, "access-group-create", "Create Nas AccessGroup", func(cli *aliyun.SRegion, args *cloudprovider.SAccessGroup) error { + return cli.CreateAccessGroup(args) + }) + + type AccessGroupDeleteOptions struct { + FileSystemType string + NAME string + } + + shellutils.R(&AccessGroupDeleteOptions{}, "access-group-delete", "Delete AccessGroup", func(cli *aliyun.SRegion, args *AccessGroupDeleteOptions) error { + return cli.DeleteAccessGroup(args.FileSystemType, args.NAME) + }) + + type NasAccessGroupRuleListOptions struct { + GROUP string + PageSize int `help:"page size"` + PageNum int `help:"page num"` + } + + shellutils.R(&NasAccessGroupRuleListOptions{}, "access-group-rule-list", "List Nas AccessGroup Rules", func(cli *aliyun.SRegion, args *NasAccessGroupRuleListOptions) error { + rules, _, err := cli.GetAccessGroupRules(args.GROUP, args.PageSize, args.PageNum) + if err != nil { + return err + } + printList(rules, 0, 0, 0, []string{}) + return nil + }) + + type AccessRuleDeleteOptions struct { + FileSystemType string + GROUP string + RULE_ID string + } + + shellutils.R(&AccessRuleDeleteOptions{}, "access-group-rule-delete", "Delete AccessGroup Rule", func(cli *aliyun.SRegion, args *AccessRuleDeleteOptions) error { + return cli.DeleteAccessGroupRule(args.FileSystemType, args.GROUP, args.RULE_ID) + }) + + type AccessRuleCreateOptions struct { + SOURCE string + FileSystemType string + GroupName string + RwType cloudprovider.TRWAccessType + UserType cloudprovider.TUserAccessType + Priority int + } + + shellutils.R(&AccessRuleCreateOptions{}, "access-group-rule-create", "Delete AccessGroup Rule", func(cli *aliyun.SRegion, args *AccessRuleCreateOptions) error { + return cli.CreateAccessGroupRule(args.SOURCE, args.FileSystemType, args.GroupName, args.RwType, args.UserType, args.Priority) + }) + +} diff --git a/pkg/multicloud/aliyun/shell/filesystem.go b/pkg/multicloud/aliyun/shell/filesystem.go new file mode 100644 index 0000000000..9483e62afd --- /dev/null +++ b/pkg/multicloud/aliyun/shell/filesystem.go @@ -0,0 +1,54 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type FileSystemListOptions struct { + Id string `help:"FileSystem Id"` + PageSize int `help:"page size"` + PageNum int `help:"page num"` + } + shellutils.R(&FileSystemListOptions{}, "file-system-list", "List FileSystem", func(cli *aliyun.SRegion, args *FileSystemListOptions) error { + nas, _, err := cli.GetFileSystems(args.Id, args.PageSize, args.PageNum) + if err != nil { + return err + } + printList(nas, 0, 0, 0, []string{}) + return nil + }) + + type FileSystemDeleteOptions struct { + ID string `help:"File System ID"` + } + shellutils.R(&FileSystemDeleteOptions{}, "file-system-delete", "Delete filesystem", func(cli *aliyun.SRegion, args *FileSystemDeleteOptions) error { + return cli.DeleteFileSystem(args.ID) + }) + + shellutils.R(&cloudprovider.FileSystemCraeteOptions{}, "file-system-create", "Create filesystem", func(cli *aliyun.SRegion, args *cloudprovider.FileSystemCraeteOptions) error { + fs, err := cli.CreateFileSystem(args) + if err != nil { + return err + } + printObject(fs) + return nil + }) + +} diff --git a/pkg/multicloud/aliyun/shell/mount_target.go b/pkg/multicloud/aliyun/shell/mount_target.go new file mode 100644 index 0000000000..e29998e0db --- /dev/null +++ b/pkg/multicloud/aliyun/shell/mount_target.go @@ -0,0 +1,56 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MountTargetListOptions struct { + ID string `help:"Id"` + DomainName string + PageSize int `help:"page size"` + PageNum int `help:"page num"` + } + shellutils.R(&MountTargetListOptions{}, "mount-target-list", "List MountTargets", func(cli *aliyun.SRegion, args *MountTargetListOptions) error { + mounts, _, err := cli.GetMountTargets(args.ID, args.DomainName, args.PageSize, args.PageNum) + if err != nil { + return err + } + printList(mounts, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&cloudprovider.SMountTargetCreateOptions{}, "mount-target-create", "Create Nas MountTarget", func(cli *aliyun.SRegion, args *cloudprovider.SMountTargetCreateOptions) error { + mt, err := cli.CreateMountTarget(args) + if err != nil { + return err + } + printObject(mt) + return nil + }) + + type MountTargetDeleteOptions struct { + FILE_SYSTEM_ID string + MOUT_POINT_ID string + } + shellutils.R(&MountTargetDeleteOptions{}, "mount-target-delete", "Delete Nas MountTarget", func(cli *aliyun.SRegion, args *MountTargetDeleteOptions) error { + return cli.DeleteMountTarget(args.FILE_SYSTEM_ID, args.MOUT_POINT_ID) + }) + +} diff --git a/pkg/multicloud/nas_base.go b/pkg/multicloud/nas_base.go new file mode 100644 index 0000000000..1fe6a505d8 --- /dev/null +++ b/pkg/multicloud/nas_base.go @@ -0,0 +1,20 @@ +// 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 multicloud + +type SNasBase struct { + SResourceBase + SBillingBase +} diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index a919540545..836acc0b28 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -149,3 +149,27 @@ func (self *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { func (self *SRegion) CreateInternetGateway() (cloudprovider.ICloudInternetGateway, error) { return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "CreateInternetGateway") } + +func (self *SRegion) GetICloudFileSystems() ([]cloudprovider.ICloudFileSystem, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudFileSystems") +} + +func (self *SRegion) GetICloudFileSystemById(id string) (cloudprovider.ICloudFileSystem, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudFileSystemById") +} + +func (self *SRegion) GetICloudAccessGroups() ([]cloudprovider.ICloudAccessGroup, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudAccessGroups") +} + +func (self *SRegion) GetICloudAccessGroupById(id string) (cloudprovider.ICloudAccessGroup, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudAccessGroupById") +} + +func (self *SRegion) CreateICloudAccessGroup(opts *cloudprovider.SAccessGroup) (cloudprovider.ICloudAccessGroup, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "CreateICloudAccessGroup") +} + +func (self *SRegion) CreateICloudFileSystem(opts *cloudprovider.FileSystemCraeteOptions) (cloudprovider.ICloudFileSystem, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "CreateICloudFileSystem") +}