From 7280621f611273008528327eb7ff270ecdb7f49a Mon Sep 17 00:00:00 2001 From: Lance <74246744+Lance52259@users.noreply.github.com> Date: Thu, 22 Apr 2021 14:12:58 +0800 Subject: [PATCH] feat: new bcs instance resource supported (#1064) --- docs/resources/bcs_instance.md | 342 ++++++++ huaweicloud/config/config.go | 4 + huaweicloud/config/endpoints.go | 4 + huaweicloud/endpoints_test.go | 12 + huaweicloud/provider.go | 1 + .../resource_huaweicloud_bcs_instance.go | 741 ++++++++++++++++++ .../resource_huaweicloud_bcs_instance_test.go | 314 ++++++++ .../openstack/bcs/v2/blockchains/requests.go | 239 ++++++ .../openstack/bcs/v2/blockchains/results.go | 266 +++++++ .../openstack/bcs/v2/blockchains/urls.go | 17 + vendor/modules.txt | 1 + 11 files changed, 1941 insertions(+) create mode 100644 docs/resources/bcs_instance.md create mode 100644 huaweicloud/resource_huaweicloud_bcs_instance.go create mode 100644 huaweicloud/resource_huaweicloud_bcs_instance_test.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/requests.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/results.go create mode 100644 vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/urls.go diff --git a/docs/resources/bcs_instance.md b/docs/resources/bcs_instance.md new file mode 100644 index 0000000000..a6778774c5 --- /dev/null +++ b/docs/resources/bcs_instance.md @@ -0,0 +1,342 @@ +--- +subcategory: "Blockchain Service (BCS)" +--- + +# huaweicloud\_bcs\_instance + +## Example Usage + +### Basic Instance + +```hcl +variable "instance_name" {} + +variable "instance_password" {} + +variable "enterprise_project_id" {} + +data "huaweicloud_availability_zones" "test" {} + +data "huaweicloud_cce_cluster" "test" { + ... +} + +resource "huaweicloud_bcs_instance" "test" { + name = var.instance_name + cce_cluster_id = data.huaweicloud_cce_cluster.test.id + consensus = "etcdraft" + edition = 1 + enterprise_project_id = var.enterprise_project_id + fabric_version = "2.0" + password = var.instance_password + volume_type = "nfs" + org_disk_size = 100 + security_mechanism = "ECDSA" + orderer_node_num = 1 + delete_storage = true + + peer_orgs { + org_name = "organization01" + count = 2 + } + channels { + name = "channel01" + org_names = [ + "organization01", + ] + } +} +``` + +### Instance With kafka consensus strategy + +```hcl +variable "instance_name" {} + +variable "instance_password" {} + +variable "enterprise_project_id" {} + +variable "database_user_name" {} + +variable "database_password" {} + +data "huaweicloud_availability_zones" "test" {} + +data "huaweicloud_cce_cluster" "test" { + ... +} + +resource "huaweicloud_bcs_instance" "test" { + name = var.instance_name + blockchain_type = "private" + cce_cluster_id = data.huaweicloud_cce_cluster.test.id + consensus = "kafka" + edition = 4 + fabric_version = "1.4" + enterprise_project_id = var.enterprise_project_id + password = var.instance_password + volume_type = "efs" + org_disk_size = 500 + database_type = "couchdb" + orderer_node_num = 2 + bandwidth_size = 5 + delete_storage = true + delete_obs = true + + couchdb { + user_name = var.database_user_name + password = var.database_password + } + peer_orgs { + org_name = "organization01" + count = 2 + } + peer_orgs { + org_name = "organization02" + count = 2 + } + channels { + name = "channel01" + org_names = [ + "organization02", + ] + } + channels { + name = "channel02" + org_names = [ + "organization01", + "organization02", + ] + } + sfs_turbo { + share_type = "STANDARD" + type = "efs-ha" + flavor = "sfs.turbo.standard" + availability_zone = data.huaweicloud_availability_zones.test.names[0] + } + kafka { + flavor = "c3.mini" + storage_size = 600 + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0], + data.huaweicloud_availability_zones.test.names[1], + data.huaweicloud_availability_zones.test.names[2], + ] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region in which to create the instance. + If omitted, the provider-level region will be used. + Changing this will create a new instance. + +* `name` - (Required, String, ForceNew) Specifies a unique name of the BCS instance. + The name consists of 4 to 24 characters, including letters, digits, chinese charactors and hyphens (-), and the name + cannot start with a hyphen. + Changing this will create a new instance. + +* `edition` - (Required, Int, ForceNew) Specifies Service edition of the BCS instance. + Valid values are `1`, `2` and `4`. + Changing this will create a new instance. + +* `fabric_version` - (Required, String, ForceNew) Specifies version of fabric for the BCS instance. + Valid values are `1.4` and `2.0` + Changing this will create a new instance. + +* `consensus` - (Required, String, ForceNew) Specifies the consensus algorithm used by the BCS instance. + The valid values of fabric 1.4 are `solo`, `kafka` and `SFLIC`, and the valid values of fabric 2.0 are `SFLIC` + and `etcdraft`. + Changing this will create a new instance. + +* `orderer_node_num` - (Required, Int, ForceNew) Specifies the number of peers in the orderer organizaion. + Changing this will create a new instance. + +* `cce_cluster_id` - (Required, String, ForceNew) Specifies the ID of the CCE cluster to attach to the BCS instance. + The BCS service needs to exclusively occupy the CCE cluster. Please make sure that the CCE cluster is not occupied + before deploying the BCS service. + Changing this will create a new instance. + +* `enterprise_project_id` - (Required, String, ForceNew) Specifies the ID of the enterprise project that the BCS + instance belong to. + Changing this will create a new instance. + +* `password` - (Required, String, ForceNew) Specifies the Resource access and blockchain management password. + The password consists of 8 to 12 characters and must consist at least three of following: uppercase letters, + lowercase letters, digits, chinese charactors, special charactors(!@$%^-_=+[{}]:,./?). + Changing this will create a new instance. + +* `volume_type` - (Required, String, ForceNew) Specifies the storage volume type to attach to each organization of the + BCS instance. Valid values are `nfs` (SFS) and `efs` (SFS Turbo). + Changing this will create a new instance. + +* `org_disk_size` - (Required, Int, ForceNew) Specifies the storage capacity of peer organization. + Changing this will create a new instance. + * The minimum storage capacity of `efs` volume type is 500GB. + + The specifications are as follows when `volume_type` is `nfs`: + * The minimum storage capacity of basic edition is 40 GB. + * The minimum storage capacity of enterprise and professional edition is 100 GB. + +* `block_info` - (Optional, List, ForceNew) Specifies the configuration of block generation. + The block_info object structure is documented below. + +* `blockchain_type` - (Optional, String, ForceNew) Specifies the blockchain type of the BCS instance. + Valid values are `private` and `union`. Default is `private`. + Changing this will create a new instance. + +* `channels` - (Optional, List, ForceNew) Specifies an array of one or more channels to attach to the BCS + instance. + If omitted, the bcs instance will create a `channels` named `channel` by default. + Changing this will create a new instance. + The channels object structure is documented below. + +* `couchdb` - (Optional, List, ForceNew) Specifies the NoSQL database used by BCS instance. + If omitted, the bcs instance will create a `goleveldb`(File Database) database by default. + Changing this will create a new instance. + The couchdb object structure is documented below. + +* `delete_storage` - (Optional, Bool) Specified whether to delete the associated SFS resources when deleting BCS + instance. Default is false. + +* `delete_obs` - (Optional, Bool) Specified whether to delete the associated OBS bucket when deleting BCS instance. + `delete_obs` is used to delete the OBS created by the BCS instance of the Kafka consensus strategy. Default is false. + +* `eip_enable` - (Optional, Bool, ForceNew) Specifies whether to use the EIP of the CCE to bind the BCS instance. + Changing this will create a new instance. Defalut is true. + * `true` means an EIP bound to the cluster will be used as the blockchain network access address. + If the cluster is not bound with any EIP, bind an EIP to the cluster first. + Please make sure that the cluster is bound to EIP. + * `false` means a private address of the cluster will be used ad the blockchain network access address to ensure that + the application can communicate with the internal network of the cluster. + +* `kafka` - (Optional, List, ForceNew) Specifies the kafka configuration for the BCS instance. + Changing this will create a new instance. + The kafka object structure is documented below. + +* `peer_orgs` - (Optional, List, ForceNew) Specifies an array of one or more Peer organizations to attach to the BCS + instance. Changing this will create a new instance. + If omitted, the bcs instance will create a `peer_orgs` named `organization` by default and the node count is 2. + The peer_orgs object structure is documented below. + +* `restful_api_support` - (Optional, Bool, ForceNew) Specified whether to add RESTful API support. + Changing this will create a new instance. + +* `sfs_turbo` - (Optional, List, ForceNew) Specifies the information about the SFS Turbo file system. + Changing this will create a new instance. + The sfs_turbo object structure is documented below. + +* `security_mechanism` - (Optional, String, ForceNew) Specifies the secutity mechanism used by the BCS instance. + Valid values are `ECDSA` and `SM2`(Chinese cryptographic algorithms, The basic and professional don't support this + algorithm). Default is `ECDSA`. + Changing this will create a new instance. + +* `tc3_need` - (Optional, Bool, ForceNew) Specified whether to add Trusted computing platform. + Changing this will create a new instance. + +The `peer_orgs` block supports: + +* `org_name` - (Required, String, ForceNew) Specifies the name of the peer organization. + Changing this creates a new instance. + +* `count` - (Required, Int, ForceNew) Specifies the number of peers in organization. + Changing this creates a new instance. + +The `channels` block supports: + +* `name` - (Required, String, ForceNew) Specifies the name of the channel. + Changing this creates a new instance. + +* `org_name` - (Optional, List, ForceNew) Specifies the name of the peer organization. + Changing this creates a new instance. + +The `couchdb` block supports: + +* `user_name` - (Required, String, ForceNew) Specifies the user name of the couch datebase. + Changing this creates a new instance. + +* `password` - (Required, String, ForceNew) Specifies the password of the couch datebase. + The password consists of 8 to 26 characters and must consist at least three of following: uppercase letters, + lowercase letters, digits, special charactors(!@$%^-_=+[{}]:,./?). + Changing this creates a new instance. + +The `sfs_turbo` block supports: + +* `availability_zone` - (Optional, String, ForceNew) Specifies the availability zone in which to create the SFS turbo. + Please following [reference](https://developer.huaweicloud.com/en-us/endpoint/?all) for the values. + Changing this creates a new instance. + +* `flavor` - (Optional, String, ForceNew) Specifies the flavor of SFS turbo. + Changing this creates a new instance. + +* `share_type` - (Optional, String, ForceNew) Specifies the share type of the SFS turbo. + Changing this creates a new instance. + +* `type` - (Optional, String, ForceNew) Specifies the type of SFS turbo. + Changing this creates a new instance. + +The `block_info` block supports: + +* `transaction_quantity` - (Optional, Int, ForceNew) Specifies the number of transactions included in the block. + The defalt value is 500. + Changing this creates a new instance. + +* `block_size` - (Optional, Int, ForceNew) Specifies the volume of the block, the unit is MB. + The default value is 2. + Changing this creates a new instance. + +* `generation_interval` - (Optional, Int, ForceNew) Specifies the block generation time, the unit is second. + The defalt value is 2. + Changing this creates a new instance. + +The `kafka` block supports: + +* `availability_zone` - (Optional, List, ForceNew) Specifies the availability zone in which to create the kafka. + The list must contain one or more than three availability zone. + Please following [reference](https://developer.huaweicloud.com/en-us/endpoint/?all) for the values. + Changing this creates a new instance. + +* `flavor` - (Optional, String, ForceNew) Specifies the kafka flavor type. + Changing this creates a new instance. + * `c3.mini` : Mini type, the reference bandwidth is 100MB/s. + * `c3.small.2` : Small type, the reference bandwidth is 300MB/s. + * `c3.middle.2` : Middle type, the reference bandwidth is 600MB/s. + * `c3.high.2` : High type, the reference bandwidth is 1200MB/s. + +* `storage_size` - (Optional, Int, ForceNew) Specifies the kafka storage capacity. + The storage capacity must be an integral multiple of 100 and the maximum is 90000GB. + Changing this creates a new instance. + * The minimum storage capacity of mini type is 600GB. + * The minimum storage capacity of small type is 1200GB. + * The minimum storage capacity of middle type is 2400GB. + * The minimum storage capacity of high type is 4800GB. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - A resource ID in UUID format. +* `status` - The status of the BCS instance. +* `version` - The service verison of the BCS instance. +* `purchase_type` - The deployment type of the BCS instance. +* `cross_region_support` - Whether the BCS instance is deployed across regions. +* `rollback_support` - Whether rollback is supported when the BCS service fails to br upgraded. +* `old_service_version` - The version of an old BCS service. +* `agent_portal_address` - The agent addresses and port numbers on the user data plane of the BCS service. +* `peer_orgs/pvc_name` - The name of the PersistenetVolumeClaim (PVC) used by the peer. +* `peer_orgs/status` - The peer status. The value contains `IsCreating`, `IsUpgrading`, `Adding/IsScaling`, + `Isdeleting`, `Normal`, `AbNormal` and `Unknown`. +* `peer_orgs/status_detail` - The peer status in the format like `1/1`. The denominator is the total number of peers in + the organization, and the numerator is the number of normal peers. +* `peer_orgs/address/domain_port` - The domain name address. +* `peer_orgs/address/ip_port` - The IP address. +* `kafka/name` - The Kafka instance name. + +## Timeouts +This resource provides the following timeouts configuration options: +- `create` - Default is 90 minute. +- `delete` - Default is 30 minute. diff --git a/huaweicloud/config/config.go b/huaweicloud/config/config.go index c1c1c408d5..4097c35513 100644 --- a/huaweicloud/config/config.go +++ b/huaweicloud/config/config.go @@ -719,6 +719,10 @@ func (c *Config) ApiGatewayV1Client(region string) (*golangsdk.ServiceClient, er return c.NewServiceClient("apig", region) } +func (c *Config) BcsV2Client(region string) (*golangsdk.ServiceClient, error) { + return c.NewServiceClient("bcs", region) +} + func (c *Config) DcsV1Client(region string) (*golangsdk.ServiceClient, error) { return c.NewServiceClient("dcsv1", region) } diff --git a/huaweicloud/config/endpoints.go b/huaweicloud/config/endpoints.go index 95d79d1303..8008b4b686 100644 --- a/huaweicloud/config/endpoints.go +++ b/huaweicloud/config/endpoints.go @@ -287,6 +287,10 @@ var allServiceCatalog = map[string]ServiceCatalog{ ResourceBase: "apigw", WithOutProjectID: true, }, + "bcs": { + Name: "bcs", + Version: "v2", + }, "dcsv1": { Name: "dcs", Version: "v1.0", diff --git a/huaweicloud/endpoints_test.go b/huaweicloud/endpoints_test.go index 386561fd3d..fc03efe13a 100644 --- a/huaweicloud/endpoints_test.go +++ b/huaweicloud/endpoints_test.go @@ -314,6 +314,18 @@ func TestAccServiceEndpoints_Application(t *testing.T) { } t.Logf("API-GW endpoint:\t %s", actualURL) + // test the endpoint of BCS v2 service + serviceClient, err = config.BcsV2Client(HW_REGION_NAME) + if err != nil { + t.Fatalf("Error creating HuaweiCloud BCS v2 client: %s", err) + } + expectedURL = fmt.Sprintf("https://bcs.%s.%s/v2/%s/", HW_REGION_NAME, config.Cloud, config.TenantID) + actualURL = serviceClient.ResourceBaseURL() + if actualURL != expectedURL { + t.Fatalf("BCS v2 endpoint: expected %s but got %s", green(expectedURL), yellow(actualURL)) + } + t.Logf("BCS v2 endpoint:\t %s", actualURL) + // test the endpoint of DCS v1 service serviceClient, err = config.DcsV1Client(HW_REGION_NAME) if err != nil { diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index bd2480607e..f3616ba779 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -355,6 +355,7 @@ func Provider() terraform.ResourceProvider { "huaweicloud_as_configuration": ResourceASConfiguration(), "huaweicloud_as_group": ResourceASGroup(), "huaweicloud_as_policy": ResourceASPolicy(), + "huaweicloud_bcs_instance": resourceBCSInstanceV2(), "huaweicloud_cce_cluster": ResourceCCEClusterV3(), "huaweicloud_cce_node": ResourceCCENodeV3(), "huaweicloud_cce_addon": ResourceCCEAddonV3(), diff --git a/huaweicloud/resource_huaweicloud_bcs_instance.go b/huaweicloud/resource_huaweicloud_bcs_instance.go new file mode 100644 index 0000000000..970e41f7c7 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_bcs_instance.go @@ -0,0 +1,741 @@ +package huaweicloud + +import ( + "bytes" + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains" + "github.com/huaweicloud/golangsdk/openstack/cce/v3/clusters" + "github.com/huaweicloud/golangsdk/openstack/dms/v1/instances" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func resourceBCSInstanceV2() *schema.Resource { + return &schema.Resource{ + Create: resourceBCSInstanceV2Create, + Read: resourceBCSInstanceV2Read, + Update: resourceBCSInstanceV2Update, + Delete: resourceBCSInstanceV2Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(90 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "orderer_node_num": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ForceNew: true, + }, + "consensus": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "edition": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "enterprise_project_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "fabric_version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "volume_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "org_disk_size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "blockchain_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "security_mechanism": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "database_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "eip_enable": { + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + "cce_cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "peer_orgs": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "org_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "count": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "pvc_name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "status_detail": { + Type: schema.TypeString, + Computed: true, + }, + "address": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_port": { + Type: schema.TypeString, + Required: true, + }, + "ip_port": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + Set: resourcePeerOrgsHash, + }, + "channels": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "org_names": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + Set: resourceChannelsHash, + }, + "couchdb": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "user_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + }, + }, + }, + }, + "sfs_turbo": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "share_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "block_info": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "transaction_quantity": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "block_size": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "generation_interval": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "kafka": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "flavor": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "storage_size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "tc3_need": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "restful_api_support": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "delete_obs": { + Type: schema.TypeBool, + Optional: true, + }, + "delete_storage": { + Type: schema.TypeBool, + Optional: true, + }, + "cluster_type": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + "purchase_type": { + Type: schema.TypeString, + Computed: true, + }, + "cross_region_support": { + Type: schema.TypeBool, + Computed: true, + }, + "rollback_support": { + Type: schema.TypeBool, + Computed: true, + }, + "old_service_version": { + Type: schema.TypeString, + Computed: true, + }, + "agent_portal_address": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceBCSInstanceV2Create(d *schema.ResourceData, meta interface{}) error { + var newCluster bool = false + config := meta.(*config.Config) + client, err := config.BcsV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud Blockchain client: %s", err) + } + createOpts := blockchains.CreateOpts{ + CreateNewCluster: &newCluster, + Name: d.Get("name").(string), + VersionType: d.Get("edition").(int), + FabricVersion: d.Get("fabric_version").(string), + BlockChainType: d.Get("blockchain_type").(string), + Consensus: d.Get("consensus").(string), + EIPEnable: d.Get("eip_enable").(bool), + SignAlgorithm: d.Get("security_mechanism").(string), + VolumeType: d.Get("volume_type").(string), + OrgDiskSize: d.Get("org_disk_size").(int), + DatabaseType: d.Get("database_type").(string), + Password: d.Get("password").(string), + OrdererNodeNumber: d.Get("orderer_node_num").(int), + TC3Need: d.Get("tc3_need").(bool), + RestfulAPISupport: d.Get("restful_api_support").(bool), + EnterpriseProjectId: GetEnterpriseProjectID(d, config), + PeerOrgs: resourceOrgPeer(d), + Channels: resourceChannelInfo(d), + CouchDBInfo: resourceCouchDBInfo(d), + SFSTurbo: resourceTurboInfo(d), + Block: resourceBlockInfo(d), + Kafka: resourceKafkaCreateInfo(d), + } + + if v, err := resourceClusterInfo(config, d.Get("cce_cluster_id").(string), GetRegion(d, config)); err != nil { + return fmt.Errorf("Get cluster information failed: %s ", err) + } else { + createOpts.ClusterType = "cce" + createOpts.CCEClusterInfo = v + } + + res, err := blockchains.Create(client, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating Blockchain instance: %s ", err) + } + + d.SetId(res.ID) + instanceID := d.Id() + stateConf := &resource.StateChangeConf{ + Pending: []string{"IsCreating"}, + Target: []string{"Normal"}, + Refresh: blockchainStateRefreshFunc(client, instanceID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 150 * time.Second, + PollInterval: 15 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for blockchain instance (%s) status to Normal: %s ", instanceID, err) + } + + return resourceBCSInstanceV2Read(d, meta) +} + +func resourceBCSInstanceV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + client, err := config.BcsV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud Blockchain client: %s", err) + } + + instanceID := d.Id() + instance, err := blockchains.Get(client, instanceID).Extract() + if err != nil { + return fmt.Errorf("Error getting Blockchain instance (%s): %s", instanceID, err) + } + log.Printf("[DEBUG] Retrieved Blockchain instance %s: %#v", instanceID, instance) + + d.Set("name", instance.Basic.Name) + d.Set("edition", instance.Basic.VersionType) + d.Set("blockchain_type", instance.Basic.ServiceType) + d.Set("consensus", instance.Basic.Consensus) + d.Set("security_mechanism", instance.Basic.SignAlgorithm) + d.Set("database_type", instance.Basic.DatabaseType) + d.Set("restful_api_support", instance.Basic.IsSupportRestful) + d.Set("status", instance.Basic.Status) + d.Set("tc3_need", instance.CouchDB != (blockchains.CouchDB{})) + d.Set("cluster_type", instance.Basic.ClusterType) + d.Set("cce_cluster_id", instance.Basic.ClusterID) + d.Set("version", instance.Basic.Version) + d.Set("purchase_type", instance.Basic.PurchaseType) + d.Set("cross_region_support", instance.Basic.IsCrossRegion) + d.Set("rollback_support", instance.Basic.IsSupportRollback) + d.Set("old_service_version", instance.Basic.OldServiceVersion) + + portalAddrs := make([]string, len(instance.Basic.AgentPortalAddress)) + for i, v := range instance.Basic.AgentPortalAddress { + portalAddrs[i] = v + } + d.Set("agent_portal_address", portalAddrs) + + channelList := make([]map[string]interface{}, len(instance.Channels)) + for i, v := range instance.Channels { + channel := map[string]interface{}{ + "name": v.Name, + "org_names": v.OrgNames, + } + channelList[i] = channel + } + d.Set("channels", channelList) + + peerList := make([]map[string]interface{}, len(instance.Peer)) + for i, org := range instance.Peer { + address := make([]map[string]interface{}, len(org.Address)) + for j, v := range org.Address { + addr := map[string]interface{}{ + "domain_port": v.DomainPort, + "ip_port": v.IPPort, + } + address[j] = addr + } + peerList[i] = map[string]interface{}{ + "org_name": org.Name, + "count": org.NodeCount, + "status": org.Status, + "status_detail": org.StatusDetail, + "pvc_name": org.PVCName, + "address": address, + } + } + d.Set("peer_orgs", peerList) + + if instance.CouchDB != (blockchains.CouchDB{}) { + couchDBList := make([]map[string]interface{}, 1) + info := map[string]interface{}{ + "user_name": instance.CouchDB.User, + "password": d.Get("couchdb.0.password"), + } + couchDBList[0] = info + d.Set("couchdb", couchDBList) + } + + if instance.Basic.Consensus == "kafka" { + kafkaList := make([]map[string]interface{}, 1) + info := map[string]interface{}{ + "name": instance.DMSKafka.Name, + "flavor": d.Get("kafka.0.flavor"), + "storage_size": d.Get("kafka.0.storage_size"), + "availability_zone": d.Get("kafka.0.availability_zone"), + } + kafkaList[0] = info + d.Set("kafka", kafkaList) + } + + return nil +} + +func resourceBCSInstanceV2Update(d *schema.ResourceData, meta interface{}) error { + //Since delete_obs and delete_storage involve updates but contains cloud service modifications, + //an empty udpate function will be set and only the read method will be called + return nil +} + +func resourceBCSInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + bcsClient, err := config.BcsV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud Blockchain client: %s", err) + } + if d.Get("consensus").(string) == "kafka" { + dmsClient, err := config.DmsV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud Kafka client: %s", err) + } + kafkaName := d.Get("kafka.0.name").(string) + listOpts := instances.ListDmsInstanceOpts{ + Engine: "kafka", + Name: kafkaName, + } + pages, err := instances.List(dmsClient, listOpts).AllPages() + if err != nil { + return fmt.Errorf("Error getting kafka instance in queue (%s): %s", kafkaName, err) + } + res, err := instances.ExtractDmsInstances(pages) + if err != nil { + return fmt.Errorf("Error quering HuaweiCloud kafka instances: %s", err) + } + if len(res.Instances) < 1 { + return fmt.Errorf("Error quering kafka, returned no results") + } + if len(res.Instances) > 1 { + return fmt.Errorf("Error quering kafka, returned more than one result") + } + kafkaID := res.Instances[0].InstanceID + if r := instances.Delete(dmsClient, kafkaID); r.Result.Err != nil { + return fmt.Errorf("Error deleting kafka instance (%s): %s ", kafkaID, r.Result.Err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING", "RUNNING"}, + Target: []string{"DELETED"}, + Refresh: DmsInstancesV1StateRefreshFunc(dmsClient, kafkaID), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + if _, err = stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for instance (%s) to delete: %s", kafkaID, err) + } + } + + blockchainID := d.Id() + deleteOpts := blockchains.DeleteOpts{} + if v, ok := d.GetOk("delete_obs"); ok { + deleteOpts.IsDeleteOBS = v.(bool) + } + if v, ok := d.GetOk("delete_storage"); ok { + deleteOpts.IsDeleteStorage = v.(bool) + } + + if err := blockchains.Delete(bcsClient, deleteOpts, blockchainID).Extract(); err != nil { + return fmt.Errorf("Error deleting HuaweiCloud Blockchain instance (%s): %s", blockchainID, err) + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"IsDeleting"}, + Target: []string{"IsDeleted"}, + Refresh: blockchainStateRefreshFunc(bcsClient, blockchainID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 15 * time.Second, + PollInterval: 10 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for blockchain instance (%s) status to deleted: %s ", blockchainID, err) + } + d.SetId("") + + return nil +} + +func blockchainStateRefreshFunc(client *golangsdk.ServiceClient, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + instance, err := blockchains.Get(client, instanceID).Extract() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault400); ok { + return instance, "IsDeleted", nil + } + return nil, "FOUND ERROR", err + } + if instance.Basic.ID == "" { + return instance, "IsDeleted", nil + } + if instance.Basic.ProcessStatus != "" { + return instance, instance.Basic.ProcessStatus, nil + } + + return instance, instance.Basic.Status, nil + } +} + +func resourceClusterInfo(config *config.Config, clusterID, region string) (*blockchains.CCEClusterInfo, error) { + clusterInfo := new(blockchains.CCEClusterInfo) + + client, err := config.CceV3Client(region) + if err != nil { + return clusterInfo, fmt.Errorf("Error creating HuaweiCloud CCE client: %s", err) + } + n, err := clusters.Get(client, clusterID).Extract() + if err != nil { + return clusterInfo, fmt.Errorf("Error retrieving HuaweiCloud CCE: %s", err) + } + clusterInfo.ID = clusterID + clusterInfo.Name = n.Metadata.Name + + return clusterInfo, nil +} + +func resourceOrgPeer(d *schema.ResourceData) []blockchains.PeerOrg { + infoRaw := d.Get("peer_orgs") + nodeList := make([]blockchains.PeerOrg, infoRaw.(*schema.Set).Len()) + for i, v := range infoRaw.(*schema.Set).List() { + var peerOrgInfo blockchains.PeerOrg + peerOrgInfo.Name = v.(map[string]interface{})["org_name"].(string) + peerOrgInfo.NodeCount = v.(map[string]interface{})["count"].(int) + nodeList[i] = peerOrgInfo + } + return nodeList +} + +func resourceChannelInfo(d *schema.ResourceData) []blockchains.ChannelInfo { + chRaw := d.Get("channels") + channelList := make([]blockchains.ChannelInfo, 0, chRaw.(*schema.Set).Len()) + + for _, v := range chRaw.(*schema.Set).List() { + var channelInfo blockchains.ChannelInfo + channelInfo.Name = v.(map[string]interface{})["name"].(string) + orgNameList := make([]string, len(v.(map[string]interface{})["org_names"].([]interface{}))) + for j, org := range v.(map[string]interface{})["org_names"].([]interface{}) { + orgNameList[j] = org.(string) + } + channelInfo.OrgNames = orgNameList + channelList = append(channelList, channelInfo) + } + return channelList +} + +func resourceCouchDBInfo(d *schema.ResourceData) *blockchains.CouchDBInfo { + var couchDBInfo *blockchains.CouchDBInfo + var infoRaw []interface{} + if v, ok := d.GetOk("couchdb"); ok { + infoRaw = v.([]interface{}) + } + if len(infoRaw) == 1 { + couchDBInfo = new(blockchains.CouchDBInfo) + couchDBInfo.UserName = infoRaw[0].(map[string]interface{})["user_name"].(string) + couchDBInfo.Password = infoRaw[0].(map[string]interface{})["password"].(string) + } + return couchDBInfo +} + +func resourceTurboInfo(d *schema.ResourceData) *blockchains.SFSTurbo { + var turboInfo *blockchains.SFSTurbo + var infoRaw []interface{} + if v, ok := d.GetOk("sfs_turbo"); ok { + infoRaw = v.([]interface{}) + } + if len(infoRaw) == 1 { + turboInfo = new(blockchains.SFSTurbo) + turboInfo.ShareType = infoRaw[0].(map[string]interface{})["share_type"].(string) + turboInfo.Type = infoRaw[0].(map[string]interface{})["type"].(string) + turboInfo.AvailabilityZone = infoRaw[0].(map[string]interface{})["availability_zone"].(string) + turboInfo.Flavor = infoRaw[0].(map[string]interface{})["flavor"].(string) + } + return turboInfo +} + +func resourceBlockInfo(d *schema.ResourceData) *blockchains.BlockInfo { + var blockInfo *blockchains.BlockInfo + var infoRaw []interface{} + if v, ok := d.GetOk("block_info"); ok { + infoRaw = v.([]interface{}) + } + if len(infoRaw) == 1 { + blockInfo = new(blockchains.BlockInfo) + blockInfo.BatchTimeout = infoRaw[0].(map[string]interface{})["generation_interval"].(int) + blockInfo.MaxMessageCount = infoRaw[0].(map[string]interface{})["transaction_quantity"].(int) + blockInfo.PreferredMaxbytes = infoRaw[0].(map[string]interface{})["block_size"].(int) + } + return blockInfo +} + +func resourceKafkaCreateInfo(d *schema.ResourceData) *blockchains.KafkaInfo { + var createInfo *blockchains.KafkaInfo + var buffer bytes.Buffer + var infoRaw []interface{} + + buffer.WriteString("dms.instance.kafka.cluster.") + if v, ok := d.GetOk("kafka"); ok { + infoRaw = v.([]interface{}) + } + if len(infoRaw) == 1 { + createInfo = new(blockchains.KafkaInfo) + createInfo.Storage = infoRaw[0].(map[string]interface{})["storage_size"].(int) + buffer.WriteString(infoRaw[0].(map[string]interface{})["flavor"].(string)) + createInfo.Flavor = buffer.String() + sliceAZ := make([]string, len(infoRaw[0].(map[string]interface{})["availability_zone"].([]interface{}))) + for i, v := range infoRaw[0].(map[string]interface{})["availability_zone"].([]interface{}) { + sliceAZ[i] = v.(string) + } + createInfo.AvailabilityZone = strings.Join(sliceAZ, ",") + } + return createInfo +} + +func resourceChannelsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if m["name"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + } + + return hashcode.String(buf.String()) +} + +func resourcePeerOrgsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if m["org_name"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["org_name"].(string))) + } + + return hashcode.String(buf.String()) +} diff --git a/huaweicloud/resource_huaweicloud_bcs_instance_test.go b/huaweicloud/resource_huaweicloud_bcs_instance_test.go new file mode 100644 index 0000000000..2abcd1ee71 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_bcs_instance_test.go @@ -0,0 +1,314 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" +) + +func TestAccBCSV2Instance_basic(t *testing.T) { + var instance blockchains.BCSInstance + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + password := fmt.Sprintf("%s%s%d", acctest.RandString(5), acctest.RandStringFromCharSet(3, "!@$%^-_=+[{}]:,./?"), + acctest.RandIntRange(1, 3)) + resourceName := "huaweicloud_bcs_instance.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEpsID(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBCSInstanceV2Destroy(), + Steps: []resource.TestStep{ + { + Config: testBCSInstanceV2_basic(rName, password), + Check: resource.ComposeTestCheckFunc( + testAccCheckBCSInstanceV2Exists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "edition", "4"), + resource.TestCheckResourceAttr(resourceName, "consensus", "etcdraft"), + resource.TestCheckResourceAttr(resourceName, "fabric_version", "2.0"), + resource.TestCheckResourceAttr(resourceName, "blockchain_type", "private"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", HW_ENTERPRISE_PROJECT_ID_TEST), + resource.TestCheckResourceAttr(resourceName, "volume_type", "nfs"), + resource.TestCheckResourceAttr(resourceName, "org_disk_size", "100"), + resource.TestCheckResourceAttr(resourceName, "security_mechanism", "ECDSA"), + resource.TestCheckResourceAttr(resourceName, "database_type", "goleveldb"), + resource.TestCheckResourceAttr(resourceName, "orderer_node_num", "3"), + resource.TestCheckResourceAttr(resourceName, "channels.#", "1"), + resource.TestCheckResourceAttr(resourceName, "peer_orgs.#", "1"), + ), + }, + }, + }) +} + +func TestAccBCSV2Instance_kafka(t *testing.T) { + var instance blockchains.BCSInstance + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + password := fmt.Sprintf("%s%s%d", acctest.RandString(5), acctest.RandStringFromCharSet(3, "!@$%^-_=+[{}]:,./?"), + acctest.RandIntRange(1, 3)) + resourceName := "huaweicloud_bcs_instance.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEpsID(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBCSInstanceV2Destroy(), + Steps: []resource.TestStep{ + { + Config: testBCSInstanceV2_kafka(rName, password), + Check: resource.ComposeTestCheckFunc( + testAccCheckBCSInstanceV2Exists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "edition", "4"), + resource.TestCheckResourceAttr(resourceName, "consensus", "kafka"), + resource.TestCheckResourceAttr(resourceName, "fabric_version", "1.4"), + resource.TestCheckResourceAttr(resourceName, "blockchain_type", "private"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", HW_ENTERPRISE_PROJECT_ID_TEST), + resource.TestCheckResourceAttr(resourceName, "volume_type", "efs"), + resource.TestCheckResourceAttr(resourceName, "org_disk_size", "500"), + resource.TestCheckResourceAttr(resourceName, "security_mechanism", "ECDSA"), + resource.TestCheckResourceAttr(resourceName, "database_type", "couchdb"), + resource.TestCheckResourceAttr(resourceName, "orderer_node_num", "2"), + resource.TestCheckResourceAttr(resourceName, "couchdb.0.user_name", "Administrator"), + resource.TestCheckResourceAttr(resourceName, "channels.#", "2"), + resource.TestCheckResourceAttr(resourceName, "peer_orgs.#", "2"), + resource.TestCheckResourceAttr(resourceName, "sfs_turbo.0.share_type", "STANDARD"), + resource.TestCheckResourceAttr(resourceName, "sfs_turbo.0.type", "efs-ha"), + resource.TestCheckResourceAttr(resourceName, "sfs_turbo.0.flavor", "sfs.turbo.standard"), + resource.TestCheckResourceAttrSet(resourceName, "sfs_turbo.0.availability_zone"), + resource.TestCheckResourceAttr(resourceName, "kafka.0.flavor", "c3.mini"), + resource.TestCheckResourceAttr(resourceName, "kafka.0.storage_size", "600"), + resource.TestCheckResourceAttr(resourceName, "kafka.0.availability_zone.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckBCSInstanceV2Destroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*config.Config) + client, err := config.BcsV2Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating huaweicloud BCS client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_bcs_instance" { + continue + } + + id := rs.Primary.ID + instance, err := blockchains.Get(client, id).Extract() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault400); ok { + return nil + } + return err + } + if instance.Basic.ID != "" { + return fmt.Errorf("%s (%s) still exists", rs.Type, id) + } + } + return nil + } +} + +func testAccCheckBCSInstanceV2Exists(name string, instance *blockchains.BCSInstance) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + id := rs.Primary.ID + if id == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*config.Config) + client, err := config.BcsV2Client(HW_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating huaweicloud BCS client: %s", err) + } + + found, err := blockchains.Get(client, id).Extract() + if err != nil { + return fmt.Errorf("Error checking %s exist, err=%s", name, err) + } + if found.Basic.ID == "" { + return fmt.Errorf("resource %s does not exist", name) + } + + instance = found + return nil + } +} + +func testBCSInstanceV2_base(rName string) string { + return fmt.Sprintf(` +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_vpc" "test" { + name = "%s" + cidr = "192.168.0.0/24" +} + +resource "huaweicloud_vpc_subnet" "test" { + name = "%s" + cidr = "192.168.0.0/24" + gateway_ip = "192.168.0.1" + primary_dns = "100.125.1.250" + secondary_dns = "100.125.129.250" + vpc_id = huaweicloud_vpc.test.id +} + +resource "huaweicloud_cce_cluster" "test" { + name = "%s" + flavor_id = "cce.s2.small" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + container_network_type = "overlay_l2" + service_network_cidr = "10.248.0.0/16" + cluster_version = "v1.15.11-r1" + delete_sfs = true +} + +resource "huaweicloud_compute_keypair" "test" { + name = "%s" + + lifecycle { + ignore_changes = [ + public_key, + ] + } +} + +resource "huaweicloud_cce_node" "test" { + name = "%s" + cluster_id = huaweicloud_cce_cluster.test.id + flavor_id = "s6.xlarge.2" + availability_zone = data.huaweicloud_availability_zones.test.names[0] + key_pair = huaweicloud_compute_keypair.test.name + max_pods = 30 + ecs_performance_type = "normal" + os = "CentOS 7.6" + iptype = "5_bgp" + bandwidth_charge_mode = "traffic" + sharetype = "PER" + bandwidth_size = 5 + + root_volume { + size = 40 + volumetype = "SAS" + } + data_volumes { + size = 100 + volumetype = "SAS" + } +} +`, rName, rName, rName, rName, rName) +} + +func testBCSInstanceV2_basic(rName, password string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_bcs_instance" "test" { + depends_on = [huaweicloud_cce_node.test] + + name = "%s" + cce_cluster_id = huaweicloud_cce_cluster.test.id + consensus = "etcdraft" + edition = 4 + enterprise_project_id = "%s" + fabric_version = "2.0" + password = "%s" + volume_type = "nfs" + org_disk_size = 100 + security_mechanism = "ECDSA" + orderer_node_num = 3 + delete_storage = true + + peer_orgs { + org_name = "organization01" + count = 1 + } + channels { + name = "channeldemo001" + org_names = [ + "organization01", + ] + } +} +`, testBCSInstanceV2_base(rName), rName, HW_ENTERPRISE_PROJECT_ID_TEST, password) +} + +func testBCSInstanceV2_kafka(rName, password string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_bcs_instance" "test" { + depends_on = [huaweicloud_cce_node.test] + + name = "%s" + cce_cluster_id = huaweicloud_cce_cluster.test.id + consensus = "kafka" + edition = 4 + enterprise_project_id = "%s" + fabric_version = "1.4" + password = "%s" + volume_type = "efs" + org_disk_size = 500 + database_type = "couchdb" + orderer_node_num = 2 + delete_storage = true + delete_obs = true + + couchdb { + user_name = "Administrator" + password = "%s" + } + peer_orgs { + org_name = "organization01" + count = 2 + } + peer_orgs { + org_name = "organization02" + count = 2 + } + channels { + name = "channeldemo001" + org_names = [ + "organization01", + "organization02", + ] + } + channels { + name = "channeldemo002" + org_names = [ + "organization02", + ] + } + sfs_turbo { + share_type = "STANDARD" + type = "efs-ha" + flavor = "sfs.turbo.standard" + availability_zone = data.huaweicloud_availability_zones.test.names[0] + } + kafka { + flavor = "c3.mini" + storage_size = 600 + availability_zone = [ + data.huaweicloud_availability_zones.test.names[0], + ] + } +} +`, testBCSInstanceV2_base(rName), rName, HW_ENTERPRISE_PROJECT_ID_TEST, password, password) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/requests.go new file mode 100644 index 0000000000..c5af559590 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/requests.go @@ -0,0 +1,239 @@ +package blockchains + +import ( + "github.com/huaweicloud/golangsdk" +) + +//CreateOpts is a struct which will be used to create a bcs instance +type CreateOpts struct { + Name string `json:"name" required:"true"` + ClusterType string `json:"cluster_type" required:"true"` + CreateNewCluster *bool `json:"create_new_cluster" required:"true"` + EnterpriseProjectId string `json:"enterprise_project_id" required:"true"` + FabricVersion string `json:"fabric_version" required:"true"` + Password string `json:"resource_password" required:"true"` + VersionType int `json:"version_type" required:"true"` + BlockChainType string `json:"blockchain_type,omitempty"` + Consensus string `json:"consensus,omitempty"` + SignAlgorithm string `json:"sign_algorithm,omitempty"` + VolumeType string `json:"volume_type,omitempty"` + EvsDiskType string `json:"evs_disk_type,omitempty"` + OrgDiskSize int `json:"org_disk_size,omitempty"` + DatabaseType string `json:"database_type,omitempty"` + OrdererNodeNumber int `json:"orderer_node_number,omitempty"` + EIPEnable bool `json:"use_eip,omitempty"` + BandwidthSize int `json:"bandwidth_size,omitempty"` + CCEClusterInfo *CCEClusterInfo `json:"cce_cluster_info,omitempty"` + CCECreateInfo *CCECreateInfo `json:"cce_create_info,omitempty"` + IEFDeployMode int `json:"ief_deploy_mode,omitempty"` + IEFNodesInfo []IEFNode `json:"ief_nodes_info,omitempty"` + PeerOrgs []PeerOrg `json:"peer_orgs,omitempty"` + Channels []ChannelInfo `json:"channels,omitempty"` + CouchDBInfo *CouchDBInfo `json:"couchdb_info,omitempty"` + SFSTurbo *SFSTurbo `json:"turbo_info,omitempty"` + Block *BlockInfo `json:"block_info,omitempty"` + Kafka *KafkaInfo `json:"kafka_create_info,omitempty"` + TC3Need bool `json:"tc3_need,omitempty"` + RestfulAPISupport bool `json:"restful_api_support,omitempty"` + IsInvitee bool `json:"is_invitee,omitempty"` + InvitorInfo *InvitorInfo `json:"invitor_infos,omitempty"` +} + +//CCEClusterInfo is the CCE cluster struct that will be used to associate when creating a bcs instance +type CCEClusterInfo struct { + ID string `json:"cluster_id" required:"true"` + Name string `json:"cluster_name" required:"true"` +} + +//CCECreateInfo is the struct that will be used to specify the creation of a new CCE cluster +//when creating a bcs instance +type CCECreateInfo struct { + NodeNum int `json:"node_num" required:"true"` + Flavor string `json:"node_flavor" required:"true"` + ClusterFlavor string `json:"cce_flavor" required:"true"` + Password string `json:"init_node_pwd" required:"true"` + AvailabilityZone string `json:"az" required:"true"` + PlatformType string `json:"cluster_platform_type" required:"true"` +} + +//IEFNode is the IEF node struct that will be used to associate when creating a bcs instance +type IEFNode struct { + ID string `json:"id" required:"true"` + Status string `json:"status" required:"true"` + IPAddress string `json:"public_ip_address" required:"true"` +} + +//PeerOrg is the peer organization struct that will be used to creating a bcs instance +type PeerOrg struct { + Name string `json:"name" required:"true"` + NodeCount int `json:"node_count" required:"true"` +} + +//ChannelInfo is the channel struct that will be used to creating a bcs instance +type ChannelInfo struct { + Name string `json:"name" required:"true"` + OrgNames []string `json:"org_names" required:"true"` + Description string `json:"desctiption,omitempty"` +} + +//CouchDBInfo is the couch database struct that will be used to creating a bcs instance +type CouchDBInfo struct { + UserName string `json:"user_name" required:"true"` + Password string `json:"password" required:"true"` +} + +//SFSTurbo is the turbo struct that will be used to creating a bcs instance +type SFSTurbo struct { + ShareType string `json:"share_type" required:"true"` + Type string `json:"type" required:"true"` + AvailabilityZone string `json:"availability_zone" required:"true"` + Flavor string `json:"resource_spec_code" required:"true"` +} + +//BlockInfo is the turbo struct that will be used to creating a bcs instance +type BlockInfo struct { + BatchTimeout int `json:"batch_timeout,omitempty"` + MaxMessageCount int `json:"max_message_count,omitempty"` + PreferredMaxbytes int `json:"preferred_maxbytes,omitempty"` +} + +//KafkaInfo is the block generation struct that be used to config when creating a bcs instance +type KafkaInfo struct { + Flavor string `json:"spec" required:"true"` + Storage int `json:"storage" required:"true"` + AvailabilityZone string `json:"az" required:"true"` +} + +//InvitorInfo is the invitor struct that be used to config when creating a bcs instance +type InvitorInfo struct { + TenantID string `json:"tenant_id" required:"true"` + ProjectID string `json:"project_id" required:"true"` + BlockchainID string `json:"blockchain_id" required:"true"` +} + +type CreateOptsBuilder interface { + ToInstancesCreateMap() (map[string]interface{}, error) +} + +func (opts CreateOpts) ToInstancesCreateMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +//Create is a method by which can be able to access the create function that create a bcs instance +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToInstancesCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(rootURL(client), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} + +//DeleteOpts is a struct which will be used to delete an existing bcs instance +type DeleteOpts struct { + IsDeleteStorage bool `q:"is_delete_storage"` + IsDeleteOBS bool `q:"is_delete_obs"` + IsDeleteResource bool `q:"is_delete_resource"` +} + +type DeleteOptsBuilder interface { + ToInstanceDeleteQuery() (string, error) +} + +func (opts DeleteOpts) ToInstanceDeleteQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), err +} + +//Delete is a method to delete an existing bcs instance +func Delete(client *golangsdk.ServiceClient, opts DeleteOptsBuilder, id string) (r DeleteResult) { + url := resourceURL(client, id) + if opts != nil { + query, err := opts.ToInstanceDeleteQuery() + if err != nil { + r.Err = err + return + } + url += query + } + _, r.Err = client.Delete(url, &golangsdk.RequestOpts{ + OkCodes: []int{200, 202, 204}, + JSONResponse: nil, + MoreHeaders: map[string]string{"Content-Type": "application/json"}, + }) + return +} + +//Get is a method to obtain the detailed information of an existing bcs instance +func Get(client *golangsdk.ServiceClient, id string) (r ShowResult) { + _, r.Err = client.Get(resourceURL(client, id), &r.Body, nil) + return +} + +//GetStatus is a method to obtain all block status of an existing bcs instance +func GetStatus(client *golangsdk.ServiceClient, id string) (r StatusResult) { + _, r.Err = client.Get(extraURL(client, id, "status"), &r.Body, nil) + return +} + +//List is a method to obtain the detailed information list of all existing bcs instance +func List(client *golangsdk.ServiceClient) (r ListResult) { + _, r.Err = client.Get(rootURL(client), &r.Body, nil) + return +} + +//GetNodes is a method to obtain the node information list of an existing bcs instance +func GetNodes(client *golangsdk.ServiceClient, id string) (r NodesResult) { + _, r.Err = client.Get(extraURL(client, id, "nodes"), &r.Body, nil) + return +} + +//UpdateOpts is a struct which will be used to update an existing bcs instance +type UpdateOpts struct { + NodePeer []NodePeer `json:"node_orgs" required:"true"` + PublicIPs []IEFNode `json:"publicips,omitempty"` +} + +//NodePeer is the peer organization struct that will be used to add a peer organization to an existing bcs instance +type NodePeer struct { + Name string `json:"name" required:"true"` + Count int `json:"node_count" required:"true"` + PVCName string `json:"pvc_name,omitempty"` +} + +type UpdateOptsBuilder interface { + ToInstancesUpdateMap() (map[string]interface{}, error) +} + +func (opts UpdateOpts) ToInstancesUpdateMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + return b, nil +} + +//Update is a method to update an existing bcs instance +func Update(client *golangsdk.ServiceClient, opts UpdateOptsBuilder, id string) (r UpdateResult) { + b, err := opts.ToInstancesUpdateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(resourceURL(client, id), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200, 202}, + }) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/results.go new file mode 100644 index 0000000000..71a129161c --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/results.go @@ -0,0 +1,266 @@ +package blockchains + +import "github.com/huaweicloud/golangsdk" + +type commonResult struct { + golangsdk.Result +} + +//CreateResult is a struct that represents the result of CreateNewBlockchain +type CreateResult struct { + commonResult +} + +type CreateResponse struct { + ID string `json:"blockchain_id"` + Name string `json:"blockchain_name"` +} + +func (r CreateResult) Extract() (*CreateResponse, error) { + var res CreateResponse + err := r.ExtractInto(&res) + return &res, err +} + +//DeleteResult is a struct that represents the result of DeleteBlockchain +type DeleteResult struct { + commonResult +} + +func (r DeleteResult) Extract() error { + return r.Err +} + +//ShowResult is a struct that represents the result of ShowBlockchainDetail +type ShowResult struct { + commonResult +} + +type BCSInstance struct { + Basic Basic `json:"basic_info"` + Channels []Channel `json:"channels"` + Peer []Peer `json:"peer_info"` + LightPeer []Peer `json:"loght_peer_info"` + Orderer Peer `json:"orderer_info"` + CouchDB CouchDB `json:"couch_db_info"` + DMSKafka DMSKafka `json:"dms_kafka_info"` + IEF IEF `json:"ief_info"` + SFS SFS `json:"sfs_info"` + Agent Peer `json:"agent_info"` + RestfulAPI Peer `json:"restapi_info"` + PVC PVC `json:"evs_pvc_info"` + TaskServer Peer `json:"tc3_taskserver_info"` + OBS OBS `json:"obs_bucket_info"` +} + +type Basic struct { + ID string `json:"id"` + Name string `json:"name"` + KernelType string `json:"kernel_type"` + Version string `json:"version"` + VersionType int `json:"version_type"` + VolumeType string `json:"volume_type"` + ServiceType string `json:"service_type"` + PurchaseType string `json:"purchase_type"` + SignAlgorithm string `json:"sign_algorithm"` + Consensus string `json:"consensus"` + ChargingMode int `json:"charging_mode"` + DatabaseType string `json:"database_type"` + ClusterID string `json:"cluster_id"` + ClusterName string `json:"cluster_name"` + ClusterType string `json:"cluster_type"` + ClusterPlatformType string `json:"cluster_platform_type"` + ClusterAvailabilityZone string `json:"cluster_az"` + CreatedTime string `json:"created_time"` + DeployType string `json:"deploy_type"` + DeployScale int `json:"deploy_scale"` + DeployStatus int `json:"deploy_status"` + DetailStatus DetailStatus `json:"detail_status"` + IsCrossRegion bool `json:"is_cross_region"` + IsSupportRollback bool `json:"is_support_rollback"` + IsSupportRestful bool `json:"is_support_restful"` + IsSupportTc3 bool `json:"is_support_tc3"` + IsOldService bool `json:"is_old_service"` + OldServiceVersion string `json:"old_service_version"` + AgentPortalAddress []string `json:"agent_portal_addrs"` + Status string `json:"status"` + ProcessStatus string `json:"process_status"` + Tc3TaskServerPortalAddrs []string `json:"tc3_taskserver_portal_addrs"` + TotalDeployPeer int `json:"total_deploy_peer"` + OrderStatus int `json:"order_status"` + OrderInfo OrderInfo `json:"order_info"` + OrderFadeCache int `json:"order_fade_cache"` + OrderFadeEnable bool `json:"order_fade_enable"` + IEFClusterInfo IEFCluster `json:"ief_cluster_info"` + IEFAPIVersion string `json:"iefapi_version"` +} + +type Channel struct { + Name string `json:"name"` + OrgNames []string `json:"org_names"` + OrgNameHash []string `json:"org_name_hash"` + Peers map[string][]string `json:"peers"` +} + +type Peer struct { + Name string `json:"name"` + NodeCount int `json:"node_cnt"` + Status string `json:"status"` + StatusDetail string `json:"status_detail"` + PVCName string `json:"pvc_name"` + Address []PeerAddress `json:"address"` +} + +type PeerAddress struct { + DomainPort string `json:"domain_port"` + IPPort string `json:"ip_port"` +} + +type CouchDB struct { + User string `json:"user"` +} + +type DMSKafka struct { + Address []string `json:"addr"` + Name string `json:"name"` + Status string `json:"status"` + NodeCount int `json:"node_cnt"` + OrderFadeEnable bool `json:"order_fade_enable"` + OrderFadeCache int `json:"order_fade_cache"` +} + +type IEF struct { + DeployMode int `json:"deploy_mode"` +} + +type IEFCluster struct { + GroupID string `json:"group_id"` + GroupName string `json:"group_name"` + InstanceID string `json:"instance_id"` + InstanceName string `json:"instance_name"` +} + +type SFS struct { + Name string `json:"name"` + PVCName string `json:"pvc_name"` + Address string `json:"addr"` + Type string `json:"type"` +} + +type PVC struct { + DeployMode int `json:"deploy_mode"` +} + +type OBS struct { + Name string `json:"name"` + Address string `json:"addr"` +} + +type DetailStatus struct { + AgentStatus string `json:"agent_status"` + ConsensusStatus string `json:"consensus_status"` + OrgStatus string `json:"org_status"` + PeerStatus string `json:"peer_status"` + PluginStatus string `json:"plugin_status"` +} + +type OrderInfo struct { + Delete int `json:"delete"` + Operate int `json:"operate"` + OrderID string `json:"order_id"` + OrderStatus int `json:"order_status"` + OrderType int `json:"order_type"` + Release int `json:"release"` + ResourceErrorCode string `json:"resource_error_code"` + ResourceStatus int `json:"resource_status"` +} + +func (r ShowResult) Extract() (*BCSInstance, error) { + var res BCSInstance + err := r.ExtractInto(&res) + return &res, err +} + +//ListResult is a struct that represents the result of ListBlockchain +type ListResult struct { + commonResult +} + +type BlockChain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +func (r ListResult) Extract() (*[]BlockChain, error) { + var s struct { + BlockChains []BlockChain `json:"blockchains"` + } + err := r.ExtractInto(&s) + return &s.BlockChains, err +} + +//StatusResult is a struct that represents the result of ShowBlockchainStatus +type StatusResult struct { + commonResult +} + +type Status struct { + BCSStatus StatusDetail `json:"bcs"` + EIPStatus StatusDetail `json:"eip"` + SFSStatus StatusDetail `json:"sfs"` + OBSStatus StatusDetail `json:"obs"` + KafkaStatus StatusDetail `json:"kafka"` + CCEStatus CCEEngine `json:"cce"` +} + +type CCEEngine struct { + Cluster StatusDetail `json:"cluster"` + Network StatusDetail `json:"network"` + SecurityGroup StatusDetail `json:"security_group"` +} + +type StatusDetail struct { + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Status string `json:"status"` + Detail string `json:"detail"` +} + +func (r StatusResult) Extract() (*Status, error) { + var s Status + err := r.ExtractInto(&s) + return &s, err +} + +//NodesResult is a struct that represents the result of ShowBlockchainNode +type NodesResult struct { + commonResult +} + +type Org struct { + OrgMSPID string `json:"org_msp_id"` + OrgDomain string `json:"org_domain"` + Peers map[string]Node `json:"peers"` +} + +type Node struct { + Port string `json:"ip_port"` + Channels []string `json:"channels"` +} + +func (r NodesResult) Extract() (*map[string]Org, error) { + var s struct { + NodeOrgs map[string]Org `json:"node_orgs"` + } + err := r.ExtractInto(&s) + return &s.NodeOrgs, err +} + +//UpdateResult is a struct which represents the result of UpdateBlockchain +type UpdateResult struct { + commonResult +} + +func (r UpdateResult) Extract() error { + return r.Err +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/urls.go new file mode 100644 index 0000000000..4cac1429f1 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains/urls.go @@ -0,0 +1,17 @@ +package blockchains + +import "github.com/huaweicloud/golangsdk" + +const resourcePath = "blockchains" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, instanceID string) string { + return c.ServiceURL(resourcePath, instanceID) +} + +func extraURL(c *golangsdk.ServiceClient, instanceID, extra string) string { + return c.ServiceURL(resourcePath, instanceID, extra) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 767aafd910..e458e4991b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -271,6 +271,7 @@ github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/groups github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/instances github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/policies github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/tags +github.com/huaweicloud/golangsdk/openstack/bcs/v2/blockchains github.com/huaweicloud/golangsdk/openstack/blockstorage/extensions/volumeactions github.com/huaweicloud/golangsdk/openstack/blockstorage/v2/volumes github.com/huaweicloud/golangsdk/openstack/bss/v2/orders