From 47fec450943e7ec8b3d2eb49a4ff0f209c83cfa9 Mon Sep 17 00:00:00 2001 From: guowei17 Date: Tue, 18 Jun 2024 14:55:17 +0800 Subject: [PATCH] Release cce-network-v2/2.10.0 --- README.md | 35 + bce-sdk-go/.gitignore | 16 + bce-sdk-go/LICENSE | 177 + bce-sdk-go/README.md | 284 ++ bce-sdk-go/auth/credentials.go | 62 + bce-sdk-go/auth/signer.go | 184 + bce-sdk-go/bce/builder.go | 189 + bce-sdk-go/bce/client.go | 279 ++ bce-sdk-go/bce/config.go | 81 + bce-sdk-go/bce/error.go | 64 + bce-sdk-go/bce/request.go | 224 + bce-sdk-go/bce/response.go | 128 + bce-sdk-go/bce/retry.go | 118 + bce-sdk-go/doc/APPBLB.md | 1288 ++++++ bce-sdk-go/doc/AS.md | 321 ++ bce-sdk-go/doc/BBC.md | 1529 +++++++ bce-sdk-go/doc/BCC.md | 3747 +++++++++++++++++ bce-sdk-go/doc/BCI.md | 565 +++ bce-sdk-go/doc/BCM.md | 289 ++ bce-sdk-go/doc/BEC.md | 1434 +++++++ bce-sdk-go/doc/BLB.md | 808 ++++ bce-sdk-go/doc/BLS.md | 872 ++++ bce-sdk-go/doc/BOS.md | 1986 +++++++++ bce-sdk-go/doc/BVW.md | 596 +++ bce-sdk-go/doc/CCE.md | 612 +++ bce-sdk-go/doc/CCEv2.md | 949 +++++ bce-sdk-go/doc/CDN.md | 1440 +++++++ bce-sdk-go/doc/CERT.md | 496 +++ bce-sdk-go/doc/CFC.md | 841 ++++ bce-sdk-go/doc/CFS.md | 427 ++ bce-sdk-go/doc/CFW.md | 492 +++ bce-sdk-go/doc/CSN.md | 667 +++ bce-sdk-go/doc/DBSC.md | 173 + bce-sdk-go/doc/DDC.md | 1221 ++++++ bce-sdk-go/doc/DDCv2.md | 2831 +++++++++++++ bce-sdk-go/doc/DNS.md | 463 ++ bce-sdk-go/doc/DOC.md | 205 + bce-sdk-go/doc/DTS.md | 684 +++ bce-sdk-go/doc/ECCR.md | 282 ++ bce-sdk-go/doc/EIP.md | 579 +++ bce-sdk-go/doc/ENIC.md | 631 +++ bce-sdk-go/doc/ESG.md | 498 +++ bce-sdk-go/doc/ETGateway.md | 473 +++ bce-sdk-go/doc/GAIADB.md | 1225 ++++++ bce-sdk-go/doc/HAVIP.md | 511 +++ bce-sdk-go/doc/IAM.md | 941 +++++ bce-sdk-go/doc/LBDC.md | 514 +++ bce-sdk-go/doc/LOCALDNS.md | 420 ++ bce-sdk-go/doc/MCP.md | 894 ++++ bce-sdk-go/doc/MMS.md | 568 +++ bce-sdk-go/doc/RDS.md | 2435 +++++++++++ bce-sdk-go/doc/SCS.md | 1537 +++++++ bce-sdk-go/doc/SMS.md | 508 +++ bce-sdk-go/doc/SNIC.md | 451 ++ bce-sdk-go/doc/STS.md | 98 + bce-sdk-go/doc/VPC.md | 1811 ++++++++ bce-sdk-go/doc/VPN.md | 799 ++++ bce-sdk-go/go.mod | 3 + bce-sdk-go/http/client.go | 175 + bce-sdk-go/http/constants.go | 86 + bce-sdk-go/http/request.go | 225 + bce-sdk-go/http/response.go | 74 + bce-sdk-go/init.go | 57 + bce-sdk-go/model/tag.go | 6 + bce-sdk-go/services/appblb/appblb.go | 145 + bce-sdk-go/services/appblb/appipgroup.go | 300 ++ bce-sdk-go/services/appblb/appservergroup.go | 351 ++ bce-sdk-go/services/appblb/client.go | 140 + bce-sdk-go/services/appblb/client_test.go | 673 +++ bce-sdk-go/services/appblb/config.json | 5 + .../appblb/enterprisesecuritygroups.go | 96 + bce-sdk-go/services/appblb/listener.go | 636 +++ bce-sdk-go/services/appblb/model.go | 758 ++++ bce-sdk-go/services/appblb/securitygroup.go | 96 + bce-sdk-go/services/as/as.go | 141 + bce-sdk-go/services/as/client.go | 69 + bce-sdk-go/services/as/client_test.go | 123 + bce-sdk-go/services/as/model.go | 155 + bce-sdk-go/services/bbc/cds.go | 57 + bce-sdk-go/services/bbc/client.go | 1352 ++++++ bce-sdk-go/services/bbc/client_test.go | 923 ++++ bce-sdk-go/services/bbc/deploySet.go | 207 + bce-sdk-go/services/bbc/flavor.go | 207 + bce-sdk-go/services/bbc/image.go | 478 +++ bce-sdk-go/services/bbc/instance.go | 1295 ++++++ bce-sdk-go/services/bbc/model.go | 1049 +++++ bce-sdk-go/services/bbc/operationLog.go | 75 + bce-sdk-go/services/bbc/repairPlat.go | 480 +++ bce-sdk-go/services/bbc/securityGroup.go | 75 + bce-sdk-go/services/bbc/tag.go | 89 + bce-sdk-go/services/bbc/util.go | 95 + .../services/bcc/api/autoSnapshotPolicy.go | 298 ++ bce-sdk-go/services/bcc/api/cds.go | 900 ++++ bce-sdk-go/services/bcc/api/deploySet.go | 264 ++ bce-sdk-go/services/bcc/api/image.go | 557 +++ bce-sdk-go/services/bcc/api/instance.go | 2238 ++++++++++ bce-sdk-go/services/bcc/api/keypair.go | 298 ++ bce-sdk-go/services/bcc/api/model.go | 2278 ++++++++++ bce-sdk-go/services/bcc/api/other.go | 236 ++ bce-sdk-go/services/bcc/api/securityGroup.go | 270 ++ bce-sdk-go/services/bcc/api/snapshot.go | 318 ++ bce-sdk-go/services/bcc/api/util.go | 437 ++ bce-sdk-go/services/bcc/client.go | 2270 ++++++++++ bce-sdk-go/services/bcc/client_test.go | 1760 ++++++++ bce-sdk-go/services/bcc/config.json | 5 + bce-sdk-go/services/bci/bci.go | 139 + bce-sdk-go/services/bci/client.go | 52 + bce-sdk-go/services/bci/client_test.go | 207 + bce-sdk-go/services/bci/model.go | 294 ++ bce-sdk-go/services/bcm/bcm.go | 119 + bce-sdk-go/services/bcm/client.go | 49 + bce-sdk-go/services/bcm/client_test.go | 133 + bce-sdk-go/services/bcm/model.go | 64 + bce-sdk-go/services/bec/api/common.go | 120 + bce-sdk-go/services/bec/api/model.go | 1736 ++++++++ bce-sdk-go/services/bec/api/util.go | 172 + bce-sdk-go/services/bec/appBlb.go | 698 +++ bce-sdk-go/services/bec/appBlb_test.go | 276 ++ bce-sdk-go/services/bec/client.go | 42 + bce-sdk-go/services/bec/deploySet.go | 175 + bce-sdk-go/services/bec/deploySet_test.go | 66 + bce-sdk-go/services/bec/image.go | 151 + bce-sdk-go/services/bec/image_test.go | 39 + bce-sdk-go/services/bec/init_test.go | 68 + bce-sdk-go/services/bec/loadBalancer.go | 588 +++ bce-sdk-go/services/bec/loadBalancer_test.go | 180 + bce-sdk-go/services/bec/node.go | 45 + bce-sdk-go/services/bec/node_test.go | 14 + bce-sdk-go/services/bec/scenario_test.go | 305 ++ bce-sdk-go/services/bec/service.go | 490 +++ bce-sdk-go/services/bec/service_test.go | 161 + bce-sdk-go/services/bec/vm.go | 271 ++ bce-sdk-go/services/bec/vmInstance.go | 335 ++ bce-sdk-go/services/bec/vm_instance_test.go | 132 + bce-sdk-go/services/bec/vm_test.go | 125 + bce-sdk-go/services/bie/api/common.go | 113 + bce-sdk-go/services/bie/api/config.go | 281 ++ bce-sdk-go/services/bie/api/const.go | 20 + bce-sdk-go/services/bie/api/core.go | 111 + bce-sdk-go/services/bie/api/group.go | 148 + bce-sdk-go/services/bie/api/image.go | 187 + bce-sdk-go/services/bie/api/model.go | 411 ++ bce-sdk-go/services/bie/api/volume.go | 435 ++ bce-sdk-go/services/bie/client.go | 628 +++ bce-sdk-go/services/bie/client_test.go | 608 +++ bce-sdk-go/services/blb/backendserver.go | 166 + bce-sdk-go/services/blb/blb.go | 222 + bce-sdk-go/services/blb/client.go | 111 + bce-sdk-go/services/blb/client_test.go | 431 ++ bce-sdk-go/services/blb/config.json | 5 + .../services/blb/enterprisesecuritygroups.go | 96 + bce-sdk-go/services/blb/listener.go | 542 +++ bce-sdk-go/services/blb/model.go | 632 +++ bce-sdk-go/services/blb/securitygroup.go | 95 + bce-sdk-go/services/bls/api/fastquery.go | 168 + bce-sdk-go/services/bls/api/index.go | 122 + bce-sdk-go/services/bls/api/logrecord.go | 131 + bce-sdk-go/services/bls/api/logshipper.go | 297 ++ bce-sdk-go/services/bls/api/logstore.go | 170 + bce-sdk-go/services/bls/api/logstream.go | 70 + bce-sdk-go/services/bls/api/model.go | 266 ++ bce-sdk-go/services/bls/api/utils.go | 63 + bce-sdk-go/services/bls/client.go | 286 ++ bce-sdk-go/services/bls/client_test.go | 549 +++ bce-sdk-go/services/bos/api/bucket.go | 1241 ++++++ bce-sdk-go/services/bos/api/model.go | 622 +++ bce-sdk-go/services/bos/api/multipart.go | 452 ++ bce-sdk-go/services/bos/api/object.go | 1016 +++++ bce-sdk-go/services/bos/api/util.go | 295 ++ bce-sdk-go/services/bos/client.go | 2380 +++++++++++ bce-sdk-go/services/bos/client_test.go | 1177 ++++++ bce-sdk-go/services/bvw/api/edit.go | 152 + bce-sdk-go/services/bvw/api/matlib.go | 289 ++ bce-sdk-go/services/bvw/api/model.go | 300 ++ bce-sdk-go/services/bvw/api/utils.go | 41 + bce-sdk-go/services/bvw/client.go | 126 + bce-sdk-go/services/bvw/client_test.go | 362 ++ bce-sdk-go/services/cce/cce.go | 335 ++ bce-sdk-go/services/cce/client.go | 78 + bce-sdk-go/services/cce/client_test.go | 251 ++ bce-sdk-go/services/cce/model.go | 469 +++ bce-sdk-go/services/cce/v2/ccev2.go | 676 +++ bce-sdk-go/services/cce/v2/client.go | 216 + bce-sdk-go/services/cce/v2/client_test.go | 688 +++ bce-sdk-go/services/cce/v2/model.go | 879 ++++ bce-sdk-go/services/cce/v2/types/bcc.go | 76 + bce-sdk-go/services/cce/v2/types/bccimage.go | 42 + .../services/cce/v2/types/cce_supported.go | 111 + bce-sdk-go/services/cce/v2/types/cluster.go | 291 ++ bce-sdk-go/services/cce/v2/types/eip.go | 30 + bce-sdk-go/services/cce/v2/types/instance.go | 368 ++ .../services/cce/v2/types/instance_group.go | 91 + .../services/cce/v2/types/internalblb.go | 21 + .../services/cce/v2/types/internalvpc.go | 33 + bce-sdk-go/services/cce/v2/types/k8stypes.go | 193 + bce-sdk-go/services/cce/v2/types/logicbcc.go | 57 + bce-sdk-go/services/cce/v2/types/tag.go | 22 + bce-sdk-go/services/cce/v2/types/task.go | 56 + bce-sdk-go/services/cdn/api/cache.go | 335 ++ bce-sdk-go/services/cdn/api/cert.go | 108 + bce-sdk-go/services/cdn/api/domain.go | 247 ++ bce-sdk-go/services/cdn/api/domain_config.go | 1936 +++++++++ bce-sdk-go/services/cdn/api/dsa.go | 115 + bce-sdk-go/services/cdn/api/log.go | 169 + bce-sdk-go/services/cdn/api/stat.go | 767 ++++ bce-sdk-go/services/cdn/api/tool.go | 93 + bce-sdk-go/services/cdn/api/util.go | 80 + bce-sdk-go/services/cdn/client.go | 1387 ++++++ bce-sdk-go/services/cdn/client_test.go | 1049 +++++ bce-sdk-go/services/cert/cert.go | 164 + bce-sdk-go/services/cert/client.go | 50 + bce-sdk-go/services/cert/client_test.go | 124 + bce-sdk-go/services/cert/model.go | 57 + bce-sdk-go/services/cfc/api/cfc.go | 762 ++++ bce-sdk-go/services/cfc/api/errors.go | 32 + bce-sdk-go/services/cfc/api/model.go | 520 +++ bce-sdk-go/services/cfc/api/request.go | 41 + bce-sdk-go/services/cfc/api/util.go | 189 + bce-sdk-go/services/cfc/api/validator.go | 416 ++ bce-sdk-go/services/cfc/api/workflow.go | 227 + bce-sdk-go/services/cfc/client.go | 525 +++ bce-sdk-go/services/cfc/client_test.go | 747 ++++ bce-sdk-go/services/cfc/config.json | 5 + bce-sdk-go/services/cfc/nodejs.zip | Bin 0 -> 610 bytes bce-sdk-go/services/cfc/nodejs2.zip | Bin 0 -> 639 bytes bce-sdk-go/services/cfc/python.zip | Bin 0 -> 201 bytes bce-sdk-go/services/cfs/cfs.go | 195 + bce-sdk-go/services/cfs/client.go | 54 + bce-sdk-go/services/cfs/client_test.go | 188 + bce-sdk-go/services/cfs/model.go | 116 + bce-sdk-go/services/cfw/cfw.go | 489 +++ bce-sdk-go/services/cfw/client.go | 236 ++ bce-sdk-go/services/cfw/client_test.go | 236 ++ bce-sdk-go/services/cfw/model.go | 203 + bce-sdk-go/services/csn/client.go | 525 +++ bce-sdk-go/services/csn/client_test.go | 374 ++ bce-sdk-go/services/csn/csn.go | 1258 ++++++ bce-sdk-go/services/csn/model.go | 397 ++ bce-sdk-go/services/dbsc/client.go | 82 + bce-sdk-go/services/dbsc/client_test.go | 135 + .../services/dbsc/dedicatedVolumeCluster.go | 235 ++ bce-sdk-go/services/dbsc/model.go | 114 + bce-sdk-go/services/dbsc/util.go | 33 + bce-sdk-go/services/dcc/client.go | 27 + bce-sdk-go/services/dcc/dedicatedHost.go | 134 + bce-sdk-go/services/dcc/model.go | 162 + bce-sdk-go/services/ddc/client.go | 207 + bce-sdk-go/services/ddc/client_test.go | 530 +++ bce-sdk-go/services/ddc/ddc.go | 1868 ++++++++ bce-sdk-go/services/ddc/ddc_util/util.go | 54 + bce-sdk-go/services/ddc/model.go | 817 ++++ bce-sdk-go/services/ddc/v2/client.go | 353 ++ bce-sdk-go/services/ddc/v2/client_test.go | 1811 ++++++++ bce-sdk-go/services/ddc/v2/ddc.go | 2524 +++++++++++ bce-sdk-go/services/ddc/v2/ddcrds.go | 1636 +++++++ bce-sdk-go/services/ddc/v2/model.go | 1237 ++++++ bce-sdk-go/services/dns/client.go | 287 ++ bce-sdk-go/services/dns/client_test.go | 217 + bce-sdk-go/services/dns/dns.go | 606 +++ bce-sdk-go/services/dns/model.go | 200 + bce-sdk-go/services/doc/api/document.go | 261 ++ bce-sdk-go/services/doc/api/model.go | 180 + bce-sdk-go/services/doc/client.go | 168 + bce-sdk-go/services/doc/client_test.go | 172 + bce-sdk-go/services/dts/client.go | 50 + bce-sdk-go/services/dts/client_test.go | 260 ++ bce-sdk-go/services/dts/dts.go | 379 ++ bce-sdk-go/services/dts/model.go | 278 ++ bce-sdk-go/services/eccr/api.go | 589 +++ bce-sdk-go/services/eccr/client.go | 107 + bce-sdk-go/services/eccr/client_test.go | 475 +++ bce-sdk-go/services/eccr/model.go | 381 ++ bce-sdk-go/services/eip/client.go | 102 + bce-sdk-go/services/eip/client_test.go | 434 ++ bce-sdk-go/services/eip/config.json | 5 + bce-sdk-go/services/eip/eip.go | 500 +++ bce-sdk-go/services/eip/eipbp.go | 188 + bce-sdk-go/services/eip/eipgroup.go | 273 ++ bce-sdk-go/services/eip/model.go | 393 ++ bce-sdk-go/services/endpoint/client.go | 51 + bce-sdk-go/services/endpoint/client_test.go | 175 + bce-sdk-go/services/endpoint/endpoint.go | 198 + bce-sdk-go/services/endpoint/model.go | 96 + bce-sdk-go/services/eni/client.go | 51 + bce-sdk-go/services/eni/client_test.go | 310 ++ bce-sdk-go/services/eni/config.json | 5 + bce-sdk-go/services/eni/eni.go | 440 ++ bce-sdk-go/services/eni/model.go | 165 + bce-sdk-go/services/esg/client.go | 55 + bce-sdk-go/services/esg/client_test.go | 191 + bce-sdk-go/services/esg/config.json | 5 + bce-sdk-go/services/esg/esg.go | 154 + bce-sdk-go/services/esg/model.go | 94 + bce-sdk-go/services/et/client.go | 78 + bce-sdk-go/services/et/client_test.go | 298 ++ bce-sdk-go/services/et/et.go | 372 ++ bce-sdk-go/services/et/model.go | 262 ++ bce-sdk-go/services/etGateway/client.go | 55 + bce-sdk-go/services/etGateway/client_test.go | 163 + bce-sdk-go/services/etGateway/etGateway.go | 155 + bce-sdk-go/services/etGateway/model.go | 101 + bce-sdk-go/services/gaiadb/client.go | 41 + bce-sdk-go/services/gaiadb/client_test.go | 700 +++ bce-sdk-go/services/gaiadb/gaiadb.go | 1391 ++++++ bce-sdk-go/services/gaiadb/model.go | 693 +++ bce-sdk-go/services/gaiadb/util.go | 36 + bce-sdk-go/services/havip/client.go | 49 + bce-sdk-go/services/havip/client_test.go | 186 + bce-sdk-go/services/havip/havip.go | 226 + bce-sdk-go/services/havip/model.go | 90 + bce-sdk-go/services/iam/api/accesskey.go | 130 + bce-sdk-go/services/iam/api/constants.go | 32 + bce-sdk-go/services/iam/api/group.go | 196 + bce-sdk-go/services/iam/api/model.go | 227 + bce-sdk-go/services/iam/api/policy.go | 285 ++ bce-sdk-go/services/iam/api/role.go | 125 + bce-sdk-go/services/iam/api/user.go | 225 + bce-sdk-go/services/iam/client.go | 283 ++ bce-sdk-go/services/iam/client_test.go | 592 +++ bce-sdk-go/services/lbdc/client.go | 49 + bce-sdk-go/services/lbdc/client_test.go | 185 + bce-sdk-go/services/lbdc/lbdc.go | 190 + bce-sdk-go/services/lbdc/model.go | 147 + bce-sdk-go/services/localDns/client.go | 221 + bce-sdk-go/services/localDns/client_test.go | 169 + bce-sdk-go/services/localDns/ld.go | 431 ++ bce-sdk-go/services/localDns/model.go | 144 + bce-sdk-go/services/media/api/job.go | 121 + bce-sdk-go/services/media/api/media_info.go | 29 + bce-sdk-go/services/media/api/model.go | 453 ++ bce-sdk-go/services/media/api/notification.go | 88 + bce-sdk-go/services/media/api/pipline.go | 140 + bce-sdk-go/services/media/api/preset.go | 128 + .../services/media/api/thumbnail_job.go | 83 + bce-sdk-go/services/media/api/util.go | 44 + bce-sdk-go/services/media/api/watermark.go | 87 + bce-sdk-go/services/media/client.go | 232 + bce-sdk-go/services/media/client_test.go | 664 +++ bce-sdk-go/services/mms/api/api.go | 303 ++ bce-sdk-go/services/mms/api/model.go | 96 + bce-sdk-go/services/mms/client.go | 212 + bce-sdk-go/services/mms/client_test.go | 180 + bce-sdk-go/services/quotacenter/client.go | 76 + .../services/quotacenter/client_test.go | 135 + bce-sdk-go/services/quotacenter/config.json | 5 + bce-sdk-go/services/quotacenter/model.go | 148 + .../services/quotacenter/quota_center.go | 238 ++ bce-sdk-go/services/rds/client.go | 50 + bce-sdk-go/services/rds/client_test.go | 1179 ++++++ bce-sdk-go/services/rds/model.go | 1212 ++++++ bce-sdk-go/services/rds/rds.go | 2308 ++++++++++ bce-sdk-go/services/rds/util.go | 35 + bce-sdk-go/services/scs/client.go | 40 + bce-sdk-go/services/scs/client_test.go | 1191 ++++++ bce-sdk-go/services/scs/model.go | 786 ++++ bce-sdk-go/services/scs/scs.go | 1632 +++++++ bce-sdk-go/services/scs/util.go | 36 + bce-sdk-go/services/sms/api/mobile_black.go | 157 + bce-sdk-go/services/sms/api/model.go | 253 ++ bce-sdk-go/services/sms/api/quota_rate.go | 81 + bce-sdk-go/services/sms/api/send_sms.go | 77 + bce-sdk-go/services/sms/api/signature.go | 177 + bce-sdk-go/services/sms/api/statistics.go | 90 + bce-sdk-go/services/sms/api/template.go | 182 + bce-sdk-go/services/sms/api/util.go | 38 + bce-sdk-go/services/sms/client.go | 235 ++ bce-sdk-go/services/sms/client_test.go | 236 ++ bce-sdk-go/services/sts/api/mode.go | 30 + bce-sdk-go/services/sts/api/sts.go | 140 + bce-sdk-go/services/sts/client.go | 75 + bce-sdk-go/services/sts/client_test.go | 96 + bce-sdk-go/services/userservice/client.go | 84 + .../services/userservice/client_test.go | 206 + bce-sdk-go/services/userservice/model.go | 105 + .../services/userservice/userservice.go | 306 ++ bce-sdk-go/services/vca/api/media.go | 74 + bce-sdk-go/services/vca/api/model.go | 71 + bce-sdk-go/services/vca/client.go | 68 + bce-sdk-go/services/vca/client_test.go | 43 + bce-sdk-go/services/vcr/api/image.go | 53 + bce-sdk-go/services/vcr/api/media.go | 71 + bce-sdk-go/services/vcr/api/model.go | 106 + bce-sdk-go/services/vcr/api/text.go | 53 + bce-sdk-go/services/vcr/client.go | 91 + bce-sdk-go/services/vcr/client_test.go | 88 + bce-sdk-go/services/vpc/acl.go | 127 + bce-sdk-go/services/vpc/client.go | 145 + bce-sdk-go/services/vpc/client_test.go | 1125 +++++ bce-sdk-go/services/vpc/ipv6gateway.go | 295 ++ bce-sdk-go/services/vpc/model.go | 995 +++++ bce-sdk-go/services/vpc/nat.go | 412 ++ bce-sdk-go/services/vpc/peerconn.go | 259 ++ bce-sdk-go/services/vpc/probe.go | 145 + bce-sdk-go/services/vpc/route.go | 166 + bce-sdk-go/services/vpc/subnet.go | 236 ++ bce-sdk-go/services/vpc/vpc.go | 240 ++ bce-sdk-go/services/vpn/client.go | 70 + bce-sdk-go/services/vpn/client_test.go | 359 ++ bce-sdk-go/services/vpn/model.go | 290 ++ bce-sdk-go/services/vpn/vpn.go | 455 ++ bce-sdk-go/util/crypto/ebc.go | 37 + bce-sdk-go/util/log/cce_logger.go | 15 + bce-sdk-go/util/log/logger.go | 400 ++ bce-sdk-go/util/log/util.go | 243 ++ bce-sdk-go/util/mime.go | 664 +++ bce-sdk-go/util/string.go | 88 + bce-sdk-go/util/time.go | 46 + cce-network-v2/Makefile | 10 +- cce-network-v2/VERSION | 2 +- cce-network-v2/cmd/Makefile | 2 +- cce-network-v2/cmd/agent/cmd/daemon.go | 13 - cce-network-v2/cmd/agent/cmd/daemon_main.go | 47 +- cce-network-v2/cmd/agent/cmd/ipam.go | 8 +- .../deploy/cce-network-v2-2.9.tar.gz | Bin 0 -> 19969 bytes .../deploy/cce-network-v2/Chart.yaml | 4 +- .../templates/cni-config-template.yaml | 58 - .../cce-network-v2/templates/ds-agent.yaml | 9 +- .../deploy/cce-network-v2/values.yaml | 34 +- .../docs/plugin/user-spec-cni-plugin.md | 58 + cce-network-v2/docs/release.md | 29 +- .../vpc-eni/images/scale-cluster-bbc-0.jpg | Bin 0 -> 405543 bytes .../vpc-eni/images/scale-cluster-bbc-1.jpg | Bin 0 -> 274228 bytes .../vpc-eni/images/scale-cluster-bbc-2.jpg | Bin 0 -> 300089 bytes .../vpc-eni-node-spec-max-ip-num.md | 45 + .../primary-interface-with-secondary-ip.md | 169 + .../docs/vpc-eni/vpc-eni-cluster-add-bbc.md | 85 + .../docs/vpc-eni/vpc-eni-node-spec-subnet.md | 2 +- cce-network-v2/docs/vpc-eni/vpc-eni.md | 55 +- cce-network-v2/docs/vpc-route/vpc-route.md | 43 +- cce-network-v2/go.mod | 2 +- cce-network-v2/go.sum | 4 +- cce-network-v2/operator/Makefile | 2 +- cce-network-v2/operator/main.go | 20 +- cce-network-v2/operator/option/config.go | 5 - .../operator/provider_vpc_eni_register.go | 7 +- ...oute.go => provider_vpc_route_register.go} | 0 cce-network-v2/pkg/bce/agent/eni_link.go | 22 +- cce-network-v2/pkg/bce/agent/eni_provider.go | 121 +- cce-network-v2/pkg/bce/agent/source_route.go | 4 +- cce-network-v2/pkg/bce/api/cloud/cloud.go | 64 +- cce-network-v2/pkg/bce/api/cloud/error.go | 7 +- .../pkg/bce/api/cloud/flow_control.go | 72 + cce-network-v2/pkg/bce/api/cloud/logger.go | 33 + .../pkg/bce/api/cloud/testing/fake_cloud.go | 20 + .../pkg/bce/api/cloud/testing/mock_cloud.go | 59 + cce-network-v2/pkg/bce/api/cloud/types.go | 14 + .../pkg/bce/bcesync/bcc_primary_eni.go | 121 + cce-network-v2/pkg/bce/bcesync/eni.go | 494 ++- .../bcesync/{bbc_eni.go => physical_eni.go} | 98 +- cce-network-v2/pkg/bce/bcesync/subnet.go | 2 +- .../pkg/bce/limit/ip_resource_manager.go | 403 -- .../pkg/bce/limit/ip_resource_manager_test.go | 402 -- cce-network-v2/pkg/bce/option/client.go | 3 +- .../pkg/bce/vpceni/allocator_provider.go | 8 +- .../pkg/bce/vpceni/instances_test.go | 5 +- cce-network-v2/pkg/bce/vpceni/node_bbc.go | 35 +- .../pkg/bce/vpceni/node_bbc_test.go | 18 +- cce-network-v2/pkg/bce/vpceni/node_bcc.go | 109 +- cce-network-v2/pkg/bce/vpceni/node_bcc_eni.go | 20 +- .../pkg/bce/vpceni/node_bcc_test.go | 23 + cce-network-v2/pkg/bce/vpceni/node_ebc.go | 233 + cce-network-v2/pkg/bce/vpceni/node_super.go | 243 +- .../pkg/bce/vpceni/node_super_test.go | 59 +- .../pkg/bce/vpceni/resource_quota_manager.go | 170 + .../pkg/bce/vpcroute/clusterpool.go | 14 +- cce-network-v2/pkg/datapath/bandwidth/tc.go | 4 +- .../pkg/endpoint/agent_endpoint_allocator.go | 12 +- cce-network-v2/pkg/enim/endpoint_enim.go | 5 +- .../ipam/allocator/clusterpool/clusterpool.go | 2 +- .../pkg/ipam/allocator/podcidr/podcidr.go | 113 +- .../ipam/allocator/podcidr/podcidr_test.go | 2 +- .../pkg/ipam/allocator/podcidr/podcidr_v2.go | 324 ++ .../ipam/allocator/podcidr/podcidr_v2_test.go | 417 ++ .../privatecloudbase/private_cloud_base.go | 2 +- cce-network-v2/pkg/ipam/allocator/provider.go | 8 +- cce-network-v2/pkg/ipam/allocator_test.go | 3 +- cce-network-v2/pkg/ipam/crd.go | 49 +- cce-network-v2/pkg/ipam/crd_test.go | 17 +- cce-network-v2/pkg/ipam/ipam_test.go | 7 +- .../pkg/ipam/net_resource_set_manager.go | 28 +- cce-network-v2/pkg/ipam/types.go | 6 +- .../apis/cce.baidubce.com/v2/cce_eni_types.go | 6 + cce-network-v2/pkg/k8s/labels.go | 7 +- cce-network-v2/pkg/logging/logging.go | 13 + cce-network-v2/pkg/netns/netns_test.go | 2 - .../pkg/nodediscovery/nodediscovery.go | 23 +- cce-network-v2/pkg/option/config.go | 41 +- .../pkg/os/systemd_networkd_test.go | 6 +- cce-network-v2/plugins/Makefile | 2 +- cce-network-v2/plugins/cipam/main.go | 106 +- cce-network-v2/plugins/cptp/cptp.go | 1 + cce-network-v2/plugins/cptp/ipam_linux.go | 7 +- cce-network-v2/plugins/cptp/ptp.go | 62 +- cce-network-v2/plugins/endpoint-probe/main.go | 110 +- cce-network-v2/plugins/enim/enim.go | 94 +- .../exclusive-device/exclusive-device.go | 75 +- .../plugins/pluginmanager/plugin_manager.go | 234 + cce-network-v2/plugins/sbr-eip/sbr_eip.go | 81 +- cce-network-v2/test/mock/ccemock/nrs.go | 65 + .../internal/controller/eip_controller.go | 1 + go.work | 7 +- go.work.sum | 260 ++ 502 files changed, 162080 insertions(+), 1960 deletions(-) create mode 100644 README.md create mode 100644 bce-sdk-go/.gitignore create mode 100644 bce-sdk-go/LICENSE create mode 100644 bce-sdk-go/README.md create mode 100644 bce-sdk-go/auth/credentials.go create mode 100644 bce-sdk-go/auth/signer.go create mode 100644 bce-sdk-go/bce/builder.go create mode 100644 bce-sdk-go/bce/client.go create mode 100644 bce-sdk-go/bce/config.go create mode 100644 bce-sdk-go/bce/error.go create mode 100644 bce-sdk-go/bce/request.go create mode 100644 bce-sdk-go/bce/response.go create mode 100644 bce-sdk-go/bce/retry.go create mode 100644 bce-sdk-go/doc/APPBLB.md create mode 100644 bce-sdk-go/doc/AS.md create mode 100644 bce-sdk-go/doc/BBC.md create mode 100644 bce-sdk-go/doc/BCC.md create mode 100644 bce-sdk-go/doc/BCI.md create mode 100644 bce-sdk-go/doc/BCM.md create mode 100644 bce-sdk-go/doc/BEC.md create mode 100644 bce-sdk-go/doc/BLB.md create mode 100644 bce-sdk-go/doc/BLS.md create mode 100644 bce-sdk-go/doc/BOS.md create mode 100644 bce-sdk-go/doc/BVW.md create mode 100644 bce-sdk-go/doc/CCE.md create mode 100644 bce-sdk-go/doc/CCEv2.md create mode 100644 bce-sdk-go/doc/CDN.md create mode 100644 bce-sdk-go/doc/CERT.md create mode 100644 bce-sdk-go/doc/CFC.md create mode 100644 bce-sdk-go/doc/CFS.md create mode 100644 bce-sdk-go/doc/CFW.md create mode 100644 bce-sdk-go/doc/CSN.md create mode 100644 bce-sdk-go/doc/DBSC.md create mode 100644 bce-sdk-go/doc/DDC.md create mode 100644 bce-sdk-go/doc/DDCv2.md create mode 100644 bce-sdk-go/doc/DNS.md create mode 100644 bce-sdk-go/doc/DOC.md create mode 100644 bce-sdk-go/doc/DTS.md create mode 100644 bce-sdk-go/doc/ECCR.md create mode 100644 bce-sdk-go/doc/EIP.md create mode 100644 bce-sdk-go/doc/ENIC.md create mode 100644 bce-sdk-go/doc/ESG.md create mode 100644 bce-sdk-go/doc/ETGateway.md create mode 100644 bce-sdk-go/doc/GAIADB.md create mode 100644 bce-sdk-go/doc/HAVIP.md create mode 100644 bce-sdk-go/doc/IAM.md create mode 100644 bce-sdk-go/doc/LBDC.md create mode 100644 bce-sdk-go/doc/LOCALDNS.md create mode 100644 bce-sdk-go/doc/MCP.md create mode 100644 bce-sdk-go/doc/MMS.md create mode 100644 bce-sdk-go/doc/RDS.md create mode 100644 bce-sdk-go/doc/SCS.md create mode 100644 bce-sdk-go/doc/SMS.md create mode 100644 bce-sdk-go/doc/SNIC.md create mode 100644 bce-sdk-go/doc/STS.md create mode 100644 bce-sdk-go/doc/VPC.md create mode 100644 bce-sdk-go/doc/VPN.md create mode 100644 bce-sdk-go/go.mod create mode 100644 bce-sdk-go/http/client.go create mode 100644 bce-sdk-go/http/constants.go create mode 100644 bce-sdk-go/http/request.go create mode 100644 bce-sdk-go/http/response.go create mode 100644 bce-sdk-go/init.go create mode 100644 bce-sdk-go/model/tag.go create mode 100644 bce-sdk-go/services/appblb/appblb.go create mode 100644 bce-sdk-go/services/appblb/appipgroup.go create mode 100644 bce-sdk-go/services/appblb/appservergroup.go create mode 100644 bce-sdk-go/services/appblb/client.go create mode 100644 bce-sdk-go/services/appblb/client_test.go create mode 100644 bce-sdk-go/services/appblb/config.json create mode 100644 bce-sdk-go/services/appblb/enterprisesecuritygroups.go create mode 100644 bce-sdk-go/services/appblb/listener.go create mode 100644 bce-sdk-go/services/appblb/model.go create mode 100644 bce-sdk-go/services/appblb/securitygroup.go create mode 100644 bce-sdk-go/services/as/as.go create mode 100644 bce-sdk-go/services/as/client.go create mode 100644 bce-sdk-go/services/as/client_test.go create mode 100644 bce-sdk-go/services/as/model.go create mode 100644 bce-sdk-go/services/bbc/cds.go create mode 100644 bce-sdk-go/services/bbc/client.go create mode 100644 bce-sdk-go/services/bbc/client_test.go create mode 100644 bce-sdk-go/services/bbc/deploySet.go create mode 100644 bce-sdk-go/services/bbc/flavor.go create mode 100644 bce-sdk-go/services/bbc/image.go create mode 100644 bce-sdk-go/services/bbc/instance.go create mode 100644 bce-sdk-go/services/bbc/model.go create mode 100644 bce-sdk-go/services/bbc/operationLog.go create mode 100644 bce-sdk-go/services/bbc/repairPlat.go create mode 100644 bce-sdk-go/services/bbc/securityGroup.go create mode 100644 bce-sdk-go/services/bbc/tag.go create mode 100644 bce-sdk-go/services/bbc/util.go create mode 100644 bce-sdk-go/services/bcc/api/autoSnapshotPolicy.go create mode 100644 bce-sdk-go/services/bcc/api/cds.go create mode 100644 bce-sdk-go/services/bcc/api/deploySet.go create mode 100644 bce-sdk-go/services/bcc/api/image.go create mode 100644 bce-sdk-go/services/bcc/api/instance.go create mode 100644 bce-sdk-go/services/bcc/api/keypair.go create mode 100644 bce-sdk-go/services/bcc/api/model.go create mode 100644 bce-sdk-go/services/bcc/api/other.go create mode 100644 bce-sdk-go/services/bcc/api/securityGroup.go create mode 100644 bce-sdk-go/services/bcc/api/snapshot.go create mode 100644 bce-sdk-go/services/bcc/api/util.go create mode 100644 bce-sdk-go/services/bcc/client.go create mode 100644 bce-sdk-go/services/bcc/client_test.go create mode 100644 bce-sdk-go/services/bcc/config.json create mode 100644 bce-sdk-go/services/bci/bci.go create mode 100644 bce-sdk-go/services/bci/client.go create mode 100644 bce-sdk-go/services/bci/client_test.go create mode 100644 bce-sdk-go/services/bci/model.go create mode 100644 bce-sdk-go/services/bcm/bcm.go create mode 100644 bce-sdk-go/services/bcm/client.go create mode 100644 bce-sdk-go/services/bcm/client_test.go create mode 100644 bce-sdk-go/services/bcm/model.go create mode 100644 bce-sdk-go/services/bec/api/common.go create mode 100644 bce-sdk-go/services/bec/api/model.go create mode 100644 bce-sdk-go/services/bec/api/util.go create mode 100644 bce-sdk-go/services/bec/appBlb.go create mode 100644 bce-sdk-go/services/bec/appBlb_test.go create mode 100644 bce-sdk-go/services/bec/client.go create mode 100644 bce-sdk-go/services/bec/deploySet.go create mode 100644 bce-sdk-go/services/bec/deploySet_test.go create mode 100644 bce-sdk-go/services/bec/image.go create mode 100644 bce-sdk-go/services/bec/image_test.go create mode 100644 bce-sdk-go/services/bec/init_test.go create mode 100644 bce-sdk-go/services/bec/loadBalancer.go create mode 100644 bce-sdk-go/services/bec/loadBalancer_test.go create mode 100644 bce-sdk-go/services/bec/node.go create mode 100644 bce-sdk-go/services/bec/node_test.go create mode 100644 bce-sdk-go/services/bec/scenario_test.go create mode 100644 bce-sdk-go/services/bec/service.go create mode 100644 bce-sdk-go/services/bec/service_test.go create mode 100644 bce-sdk-go/services/bec/vm.go create mode 100644 bce-sdk-go/services/bec/vmInstance.go create mode 100644 bce-sdk-go/services/bec/vm_instance_test.go create mode 100644 bce-sdk-go/services/bec/vm_test.go create mode 100644 bce-sdk-go/services/bie/api/common.go create mode 100644 bce-sdk-go/services/bie/api/config.go create mode 100644 bce-sdk-go/services/bie/api/const.go create mode 100644 bce-sdk-go/services/bie/api/core.go create mode 100644 bce-sdk-go/services/bie/api/group.go create mode 100644 bce-sdk-go/services/bie/api/image.go create mode 100644 bce-sdk-go/services/bie/api/model.go create mode 100644 bce-sdk-go/services/bie/api/volume.go create mode 100644 bce-sdk-go/services/bie/client.go create mode 100644 bce-sdk-go/services/bie/client_test.go create mode 100644 bce-sdk-go/services/blb/backendserver.go create mode 100644 bce-sdk-go/services/blb/blb.go create mode 100644 bce-sdk-go/services/blb/client.go create mode 100644 bce-sdk-go/services/blb/client_test.go create mode 100644 bce-sdk-go/services/blb/config.json create mode 100644 bce-sdk-go/services/blb/enterprisesecuritygroups.go create mode 100644 bce-sdk-go/services/blb/listener.go create mode 100644 bce-sdk-go/services/blb/model.go create mode 100644 bce-sdk-go/services/blb/securitygroup.go create mode 100644 bce-sdk-go/services/bls/api/fastquery.go create mode 100644 bce-sdk-go/services/bls/api/index.go create mode 100644 bce-sdk-go/services/bls/api/logrecord.go create mode 100644 bce-sdk-go/services/bls/api/logshipper.go create mode 100644 bce-sdk-go/services/bls/api/logstore.go create mode 100644 bce-sdk-go/services/bls/api/logstream.go create mode 100644 bce-sdk-go/services/bls/api/model.go create mode 100644 bce-sdk-go/services/bls/api/utils.go create mode 100644 bce-sdk-go/services/bls/client.go create mode 100644 bce-sdk-go/services/bls/client_test.go create mode 100644 bce-sdk-go/services/bos/api/bucket.go create mode 100644 bce-sdk-go/services/bos/api/model.go create mode 100644 bce-sdk-go/services/bos/api/multipart.go create mode 100644 bce-sdk-go/services/bos/api/object.go create mode 100644 bce-sdk-go/services/bos/api/util.go create mode 100644 bce-sdk-go/services/bos/client.go create mode 100644 bce-sdk-go/services/bos/client_test.go create mode 100644 bce-sdk-go/services/bvw/api/edit.go create mode 100644 bce-sdk-go/services/bvw/api/matlib.go create mode 100644 bce-sdk-go/services/bvw/api/model.go create mode 100644 bce-sdk-go/services/bvw/api/utils.go create mode 100644 bce-sdk-go/services/bvw/client.go create mode 100644 bce-sdk-go/services/bvw/client_test.go create mode 100644 bce-sdk-go/services/cce/cce.go create mode 100644 bce-sdk-go/services/cce/client.go create mode 100644 bce-sdk-go/services/cce/client_test.go create mode 100644 bce-sdk-go/services/cce/model.go create mode 100644 bce-sdk-go/services/cce/v2/ccev2.go create mode 100644 bce-sdk-go/services/cce/v2/client.go create mode 100644 bce-sdk-go/services/cce/v2/client_test.go create mode 100644 bce-sdk-go/services/cce/v2/model.go create mode 100644 bce-sdk-go/services/cce/v2/types/bcc.go create mode 100644 bce-sdk-go/services/cce/v2/types/bccimage.go create mode 100644 bce-sdk-go/services/cce/v2/types/cce_supported.go create mode 100644 bce-sdk-go/services/cce/v2/types/cluster.go create mode 100644 bce-sdk-go/services/cce/v2/types/eip.go create mode 100644 bce-sdk-go/services/cce/v2/types/instance.go create mode 100644 bce-sdk-go/services/cce/v2/types/instance_group.go create mode 100644 bce-sdk-go/services/cce/v2/types/internalblb.go create mode 100644 bce-sdk-go/services/cce/v2/types/internalvpc.go create mode 100644 bce-sdk-go/services/cce/v2/types/k8stypes.go create mode 100644 bce-sdk-go/services/cce/v2/types/logicbcc.go create mode 100644 bce-sdk-go/services/cce/v2/types/tag.go create mode 100644 bce-sdk-go/services/cce/v2/types/task.go create mode 100644 bce-sdk-go/services/cdn/api/cache.go create mode 100644 bce-sdk-go/services/cdn/api/cert.go create mode 100644 bce-sdk-go/services/cdn/api/domain.go create mode 100644 bce-sdk-go/services/cdn/api/domain_config.go create mode 100644 bce-sdk-go/services/cdn/api/dsa.go create mode 100644 bce-sdk-go/services/cdn/api/log.go create mode 100644 bce-sdk-go/services/cdn/api/stat.go create mode 100644 bce-sdk-go/services/cdn/api/tool.go create mode 100644 bce-sdk-go/services/cdn/api/util.go create mode 100644 bce-sdk-go/services/cdn/client.go create mode 100644 bce-sdk-go/services/cdn/client_test.go create mode 100644 bce-sdk-go/services/cert/cert.go create mode 100644 bce-sdk-go/services/cert/client.go create mode 100644 bce-sdk-go/services/cert/client_test.go create mode 100644 bce-sdk-go/services/cert/model.go create mode 100644 bce-sdk-go/services/cfc/api/cfc.go create mode 100644 bce-sdk-go/services/cfc/api/errors.go create mode 100644 bce-sdk-go/services/cfc/api/model.go create mode 100644 bce-sdk-go/services/cfc/api/request.go create mode 100644 bce-sdk-go/services/cfc/api/util.go create mode 100644 bce-sdk-go/services/cfc/api/validator.go create mode 100644 bce-sdk-go/services/cfc/api/workflow.go create mode 100644 bce-sdk-go/services/cfc/client.go create mode 100644 bce-sdk-go/services/cfc/client_test.go create mode 100644 bce-sdk-go/services/cfc/config.json create mode 100644 bce-sdk-go/services/cfc/nodejs.zip create mode 100644 bce-sdk-go/services/cfc/nodejs2.zip create mode 100644 bce-sdk-go/services/cfc/python.zip create mode 100644 bce-sdk-go/services/cfs/cfs.go create mode 100644 bce-sdk-go/services/cfs/client.go create mode 100644 bce-sdk-go/services/cfs/client_test.go create mode 100644 bce-sdk-go/services/cfs/model.go create mode 100644 bce-sdk-go/services/cfw/cfw.go create mode 100644 bce-sdk-go/services/cfw/client.go create mode 100644 bce-sdk-go/services/cfw/client_test.go create mode 100644 bce-sdk-go/services/cfw/model.go create mode 100644 bce-sdk-go/services/csn/client.go create mode 100644 bce-sdk-go/services/csn/client_test.go create mode 100644 bce-sdk-go/services/csn/csn.go create mode 100644 bce-sdk-go/services/csn/model.go create mode 100644 bce-sdk-go/services/dbsc/client.go create mode 100644 bce-sdk-go/services/dbsc/client_test.go create mode 100644 bce-sdk-go/services/dbsc/dedicatedVolumeCluster.go create mode 100644 bce-sdk-go/services/dbsc/model.go create mode 100644 bce-sdk-go/services/dbsc/util.go create mode 100644 bce-sdk-go/services/dcc/client.go create mode 100644 bce-sdk-go/services/dcc/dedicatedHost.go create mode 100644 bce-sdk-go/services/dcc/model.go create mode 100644 bce-sdk-go/services/ddc/client.go create mode 100644 bce-sdk-go/services/ddc/client_test.go create mode 100644 bce-sdk-go/services/ddc/ddc.go create mode 100644 bce-sdk-go/services/ddc/ddc_util/util.go create mode 100644 bce-sdk-go/services/ddc/model.go create mode 100644 bce-sdk-go/services/ddc/v2/client.go create mode 100644 bce-sdk-go/services/ddc/v2/client_test.go create mode 100644 bce-sdk-go/services/ddc/v2/ddc.go create mode 100644 bce-sdk-go/services/ddc/v2/ddcrds.go create mode 100644 bce-sdk-go/services/ddc/v2/model.go create mode 100644 bce-sdk-go/services/dns/client.go create mode 100644 bce-sdk-go/services/dns/client_test.go create mode 100644 bce-sdk-go/services/dns/dns.go create mode 100644 bce-sdk-go/services/dns/model.go create mode 100644 bce-sdk-go/services/doc/api/document.go create mode 100644 bce-sdk-go/services/doc/api/model.go create mode 100644 bce-sdk-go/services/doc/client.go create mode 100644 bce-sdk-go/services/doc/client_test.go create mode 100644 bce-sdk-go/services/dts/client.go create mode 100644 bce-sdk-go/services/dts/client_test.go create mode 100644 bce-sdk-go/services/dts/dts.go create mode 100644 bce-sdk-go/services/dts/model.go create mode 100644 bce-sdk-go/services/eccr/api.go create mode 100644 bce-sdk-go/services/eccr/client.go create mode 100644 bce-sdk-go/services/eccr/client_test.go create mode 100644 bce-sdk-go/services/eccr/model.go create mode 100644 bce-sdk-go/services/eip/client.go create mode 100644 bce-sdk-go/services/eip/client_test.go create mode 100644 bce-sdk-go/services/eip/config.json create mode 100644 bce-sdk-go/services/eip/eip.go create mode 100644 bce-sdk-go/services/eip/eipbp.go create mode 100644 bce-sdk-go/services/eip/eipgroup.go create mode 100644 bce-sdk-go/services/eip/model.go create mode 100644 bce-sdk-go/services/endpoint/client.go create mode 100644 bce-sdk-go/services/endpoint/client_test.go create mode 100644 bce-sdk-go/services/endpoint/endpoint.go create mode 100644 bce-sdk-go/services/endpoint/model.go create mode 100644 bce-sdk-go/services/eni/client.go create mode 100644 bce-sdk-go/services/eni/client_test.go create mode 100644 bce-sdk-go/services/eni/config.json create mode 100644 bce-sdk-go/services/eni/eni.go create mode 100644 bce-sdk-go/services/eni/model.go create mode 100644 bce-sdk-go/services/esg/client.go create mode 100644 bce-sdk-go/services/esg/client_test.go create mode 100644 bce-sdk-go/services/esg/config.json create mode 100644 bce-sdk-go/services/esg/esg.go create mode 100644 bce-sdk-go/services/esg/model.go create mode 100644 bce-sdk-go/services/et/client.go create mode 100644 bce-sdk-go/services/et/client_test.go create mode 100644 bce-sdk-go/services/et/et.go create mode 100644 bce-sdk-go/services/et/model.go create mode 100644 bce-sdk-go/services/etGateway/client.go create mode 100644 bce-sdk-go/services/etGateway/client_test.go create mode 100644 bce-sdk-go/services/etGateway/etGateway.go create mode 100644 bce-sdk-go/services/etGateway/model.go create mode 100644 bce-sdk-go/services/gaiadb/client.go create mode 100644 bce-sdk-go/services/gaiadb/client_test.go create mode 100644 bce-sdk-go/services/gaiadb/gaiadb.go create mode 100644 bce-sdk-go/services/gaiadb/model.go create mode 100644 bce-sdk-go/services/gaiadb/util.go create mode 100644 bce-sdk-go/services/havip/client.go create mode 100644 bce-sdk-go/services/havip/client_test.go create mode 100644 bce-sdk-go/services/havip/havip.go create mode 100644 bce-sdk-go/services/havip/model.go create mode 100644 bce-sdk-go/services/iam/api/accesskey.go create mode 100644 bce-sdk-go/services/iam/api/constants.go create mode 100644 bce-sdk-go/services/iam/api/group.go create mode 100644 bce-sdk-go/services/iam/api/model.go create mode 100644 bce-sdk-go/services/iam/api/policy.go create mode 100644 bce-sdk-go/services/iam/api/role.go create mode 100644 bce-sdk-go/services/iam/api/user.go create mode 100644 bce-sdk-go/services/iam/client.go create mode 100644 bce-sdk-go/services/iam/client_test.go create mode 100644 bce-sdk-go/services/lbdc/client.go create mode 100644 bce-sdk-go/services/lbdc/client_test.go create mode 100644 bce-sdk-go/services/lbdc/lbdc.go create mode 100644 bce-sdk-go/services/lbdc/model.go create mode 100644 bce-sdk-go/services/localDns/client.go create mode 100644 bce-sdk-go/services/localDns/client_test.go create mode 100644 bce-sdk-go/services/localDns/ld.go create mode 100644 bce-sdk-go/services/localDns/model.go create mode 100644 bce-sdk-go/services/media/api/job.go create mode 100644 bce-sdk-go/services/media/api/media_info.go create mode 100644 bce-sdk-go/services/media/api/model.go create mode 100644 bce-sdk-go/services/media/api/notification.go create mode 100644 bce-sdk-go/services/media/api/pipline.go create mode 100644 bce-sdk-go/services/media/api/preset.go create mode 100644 bce-sdk-go/services/media/api/thumbnail_job.go create mode 100644 bce-sdk-go/services/media/api/util.go create mode 100644 bce-sdk-go/services/media/api/watermark.go create mode 100644 bce-sdk-go/services/media/client.go create mode 100644 bce-sdk-go/services/media/client_test.go create mode 100644 bce-sdk-go/services/mms/api/api.go create mode 100644 bce-sdk-go/services/mms/api/model.go create mode 100644 bce-sdk-go/services/mms/client.go create mode 100644 bce-sdk-go/services/mms/client_test.go create mode 100644 bce-sdk-go/services/quotacenter/client.go create mode 100644 bce-sdk-go/services/quotacenter/client_test.go create mode 100644 bce-sdk-go/services/quotacenter/config.json create mode 100644 bce-sdk-go/services/quotacenter/model.go create mode 100644 bce-sdk-go/services/quotacenter/quota_center.go create mode 100644 bce-sdk-go/services/rds/client.go create mode 100644 bce-sdk-go/services/rds/client_test.go create mode 100644 bce-sdk-go/services/rds/model.go create mode 100644 bce-sdk-go/services/rds/rds.go create mode 100644 bce-sdk-go/services/rds/util.go create mode 100644 bce-sdk-go/services/scs/client.go create mode 100644 bce-sdk-go/services/scs/client_test.go create mode 100644 bce-sdk-go/services/scs/model.go create mode 100644 bce-sdk-go/services/scs/scs.go create mode 100644 bce-sdk-go/services/scs/util.go create mode 100644 bce-sdk-go/services/sms/api/mobile_black.go create mode 100644 bce-sdk-go/services/sms/api/model.go create mode 100644 bce-sdk-go/services/sms/api/quota_rate.go create mode 100644 bce-sdk-go/services/sms/api/send_sms.go create mode 100644 bce-sdk-go/services/sms/api/signature.go create mode 100644 bce-sdk-go/services/sms/api/statistics.go create mode 100644 bce-sdk-go/services/sms/api/template.go create mode 100644 bce-sdk-go/services/sms/api/util.go create mode 100644 bce-sdk-go/services/sms/client.go create mode 100644 bce-sdk-go/services/sms/client_test.go create mode 100644 bce-sdk-go/services/sts/api/mode.go create mode 100644 bce-sdk-go/services/sts/api/sts.go create mode 100644 bce-sdk-go/services/sts/client.go create mode 100644 bce-sdk-go/services/sts/client_test.go create mode 100644 bce-sdk-go/services/userservice/client.go create mode 100644 bce-sdk-go/services/userservice/client_test.go create mode 100644 bce-sdk-go/services/userservice/model.go create mode 100644 bce-sdk-go/services/userservice/userservice.go create mode 100644 bce-sdk-go/services/vca/api/media.go create mode 100644 bce-sdk-go/services/vca/api/model.go create mode 100644 bce-sdk-go/services/vca/client.go create mode 100644 bce-sdk-go/services/vca/client_test.go create mode 100644 bce-sdk-go/services/vcr/api/image.go create mode 100644 bce-sdk-go/services/vcr/api/media.go create mode 100644 bce-sdk-go/services/vcr/api/model.go create mode 100644 bce-sdk-go/services/vcr/api/text.go create mode 100644 bce-sdk-go/services/vcr/client.go create mode 100644 bce-sdk-go/services/vcr/client_test.go create mode 100644 bce-sdk-go/services/vpc/acl.go create mode 100644 bce-sdk-go/services/vpc/client.go create mode 100644 bce-sdk-go/services/vpc/client_test.go create mode 100644 bce-sdk-go/services/vpc/ipv6gateway.go create mode 100644 bce-sdk-go/services/vpc/model.go create mode 100644 bce-sdk-go/services/vpc/nat.go create mode 100644 bce-sdk-go/services/vpc/peerconn.go create mode 100644 bce-sdk-go/services/vpc/probe.go create mode 100644 bce-sdk-go/services/vpc/route.go create mode 100644 bce-sdk-go/services/vpc/subnet.go create mode 100644 bce-sdk-go/services/vpc/vpc.go create mode 100644 bce-sdk-go/services/vpn/client.go create mode 100644 bce-sdk-go/services/vpn/client_test.go create mode 100644 bce-sdk-go/services/vpn/model.go create mode 100644 bce-sdk-go/services/vpn/vpn.go create mode 100644 bce-sdk-go/util/crypto/ebc.go create mode 100644 bce-sdk-go/util/log/cce_logger.go create mode 100644 bce-sdk-go/util/log/logger.go create mode 100644 bce-sdk-go/util/log/util.go create mode 100644 bce-sdk-go/util/mime.go create mode 100644 bce-sdk-go/util/string.go create mode 100644 bce-sdk-go/util/time.go create mode 100644 cce-network-v2/deploy/cce-network-v2-2.9.tar.gz delete mode 100644 cce-network-v2/deploy/cce-network-v2/templates/cni-config-template.yaml create mode 100644 cce-network-v2/docs/plugin/user-spec-cni-plugin.md create mode 100644 cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-0.jpg create mode 100644 cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-1.jpg create mode 100644 cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-2.jpg create mode 100644 cce-network-v2/docs/vpc-eni/node-spec-config/vpc-eni-node-spec-max-ip-num.md create mode 100644 cce-network-v2/docs/vpc-eni/primary-interface-with-secondary-ip.md create mode 100644 cce-network-v2/docs/vpc-eni/vpc-eni-cluster-add-bbc.md rename cce-network-v2/operator/{provider_vpc_route.go => provider_vpc_route_register.go} (100%) create mode 100644 cce-network-v2/pkg/bce/api/cloud/logger.go create mode 100644 cce-network-v2/pkg/bce/bcesync/bcc_primary_eni.go rename cce-network-v2/pkg/bce/bcesync/{bbc_eni.go => physical_eni.go} (69%) delete mode 100644 cce-network-v2/pkg/bce/limit/ip_resource_manager.go delete mode 100644 cce-network-v2/pkg/bce/limit/ip_resource_manager_test.go create mode 100644 cce-network-v2/pkg/bce/vpceni/node_bcc_test.go create mode 100644 cce-network-v2/pkg/bce/vpceni/node_ebc.go create mode 100644 cce-network-v2/pkg/bce/vpceni/resource_quota_manager.go create mode 100644 cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2.go create mode 100644 cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2_test.go create mode 100644 cce-network-v2/plugins/pluginmanager/plugin_manager.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c273eb --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Baidu Cloud CNI Plugin +## Introduction +Baidu Cloud CNI plugin implement an interface between CNI enabled Container Orchestrator and Baidu Cloud Network Infrastructure. + +## Getting Started +These instructions will get you a copy of the project up and running on your environment for development and testing purposes. See installing for notes on how to deploy the project on a Baidu Cloud CCE cluster. + +## Prerequisites +A healthy CCE kubernetes cluster. See documents for creating a CCE cluster. + +## Components +There are 2 components: +* cce-network-v2: The v2 version of the container network has been optimized for the internal state and availability of the network. Provides network modes such as `VPC-ENI`and `VPC-Route` that are more suitable for BCE Cloud. +* eip-operator: Assign EIP directly to Pod to help Pod connect to the Internet directly + +## design +### VPC-ENI +[VPC-ENI](docs/vpc-eni/vpc-eni.md) provides a container network originating from BCE Cloud, where container addresses and node addresses use the same network segment. + + +### VPC-Route +The [VPC-Route](docs/vpc-route/vpc-route.md)) mode utilizes the custom routing rule capability provided by VPC to make the virtual network address segment of the container accessible to the entire VPC. + +## Contributing +Please go through CNI Spec to get some basic understanding of CNI driver before you start. + +## Requirements +* Recommended linux kervel version >= 5.10 +* Gloang version >= 1.21 +* k8s version >= 1.20 + +## Issues +Please create an issue in issue list. +Contact Committers/Owners for further discussion if needed. + diff --git a/bce-sdk-go/.gitignore b/bce-sdk-go/.gitignore new file mode 100644 index 0000000..bf20aac --- /dev/null +++ b/bce-sdk-go/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +.idea \ No newline at end of file diff --git a/bce-sdk-go/LICENSE b/bce-sdk-go/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/bce-sdk-go/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/bce-sdk-go/README.md b/bce-sdk-go/README.md new file mode 100644 index 0000000..c10319f --- /dev/null +++ b/bce-sdk-go/README.md @@ -0,0 +1,284 @@ +# GO SDK 文档 + +# 概述 + +本文档主要介绍百度云Go语言版的开发者工具包(SDK),用户可基于该SDK使用Go语言接入百度云的各项产品(详见支持产品列表)。SDK封装了便捷的调用接口,保持了多种编程语言版的使用方式、调用接口相似,提供了统一的错误码和返回格式,方便开发者调试。 + +# 安装SDK工具包 + +## 运行环境 + +GO SDK可以在go1.3及以上环境下运行。 + +## 安装SDK + +**直接从github下载** + +使用`go get`工具从github进行下载: + +```shell +go get github.com/baidubce/bce-sdk-go +``` + +**SDK目录结构** + +```text +bce-sdk-go +|--auth //BCE签名和权限认证 +|--bce //BCE公用基础组件 +|--http //BCE的http通信模块 +|--services //BCE相关服务目录 +| |--appblb //应用型负载均衡服务目录 +| |--as //弹性伸缩 +| |--bbc //物理服务器 +| |--bcc //云服务器 +| |--bcm //云监控 +| |--bec //百度边缘计算 +| |--bie //百度智能边缘 +| |--bls //日志服务 +| |--bos //BOS服务目录 +| | |--bos_client.go //BOS客户端入口 +| | |--api //BOS相关API目录 +| | |--bucket.go //BOS的Bucket相关API实现 +| | |--object.go //BOS的Object相关API实现 +| | |--multipart.go //BOS的Multipart相关API实现 +| | |--module.go //BOS相关API的数据模型 +| | |--util.go //BOS相关API实现使用的工具 +| |--bvw //BVW服务目录 +| |--cce //容器引擎 +| |--cdn //内容分布网络 +| |--cert //SSL证书服务 +| |--cfc //函数计算 +| |--cfs //CFS文件存储服务 +| |--cfw //云防火墙 +| |--csn //云智能网 +| |--ddc //DDC数据库专属集群 +| |--dts //数据传输服务 +| |--eccr //容器镜像服务企业版 +| |--eip //弹性公网IP +| |--endpoint //SNIC服务网卡 +| |--eni //ENIC弹性网卡 +| |--esg //企业安全组 +| |--etGateway //专线网关 +| |--havip //高可用虚拟IP +| |--iam //身份管理 +| |--media //音视频处理MCP +| |--rds //云数据库 +| |--scs //SCS服务目录 +| |--sms //SMS服务目录 +| |--sts //STS服务目录 +| |--vca //VCA服务目录 +| |--vcr //VCR服务目录 +| |--vpc //私有网络 +| |--vpn //VPN网关 +| |--mms //多模态媒资检索目录 +|--util //BCE公用的工具实现 +``` + +## 卸载SDK + +预期卸载SDK时,删除下载的源码即可。 + +# 使用步骤 + +## 确认Endpoint + +在使用SDK之前,需确认您将接入的百度云产品的Endpoint(服务域名)。以百度对象存储产品为例,可阅读[BOS访问域名](https://cloud.baidu.com/doc/BOS/DevRef.html#BOS.E8.AE.BF.E9.97.AE.E5.9F.9F.E5.90.8D)的部分,理解Endpoint相关的概念。其他服务类似,需理解并确认对应服务的Endpoint。 + +## 创建Client对象 + +每种具体的服务都有一个`Client`对象,为开发者与对应的服务进行交互封装了一系列易用的方法。开发者可参考SDK中具体服务对应的目录下的说明文档使用相应的服务。 + +## 调用功能接口 + +开发者基于创建的对应服务的`Client`对象,即可调用相应的功能接口,使用百度云产品的功能。 + +## 示例 + +下面以百度云对象存储服务(BOS)为例,给出一个基本的使用示例,详细使用说明请参考各服务的详细说明文档。 + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bos" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := "", "" + + // BOS服务的Endpoint + ENDPOINT := "" + + // 创建BOS服务的Client + bosClient, err := bos.NewClient(AK, SK, ENDPOINT) + + // 创建Bucket + location, err := bosClient.PutBucket(""); err != nil { + if err != nil { + fmt.Println("create bucket failed:", err) + } + fmt.Println("create bucket success in", location) + + // 上传对象 + etag, err := bosClient.PutObjectFromFile("", "", "", nil) + if err != nil { + fmt.Println("upload file to BOS failed:", err) + } + fmt.Println("upload file to BOS success, etag = ", etag) +} +``` + +# 配置 + +## 使用HTTPS协议 + +该SDK支持使用HTTPS协议访问百度云的服务产品。要使用HTTPS协议,只需在您创建对应服务的`Client`对象时指定的Endpoint中指明使用https协议的域名即可,SDK会自动识别并使用HTTPS协议访问。 + +## 详细配置 + +开发者使用GO SDK时,创建的对应服务的`Client`对象,其导出字段`Config`提供如下参数以便支持详细配置: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +开发者可据此进行详细参数的配置,下面给出部分配置示例: + +```go +// client为某一种具体服务的`Client`对象 + +// 配置请求代理地址 +client.Config.ProxyUrl = "127.0.0.1:8080" + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 + +// 配置签名使用的HTTP请求头为`Host` +client.Config.SignOption.HeadersToSign = map[string]struct{}{"Host": struct{}{}} + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +# 错误处理 + +GO语言以error类型标识错误,定义了如下两种错误类型: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BOS服务返回的错误 + +用户使用SDK调用各服务的相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误的详细信息进行处理。实例如下: + +```go +// bosClient 为已创建的BOS服务的Client对象 +bucketLocation, err := bosClient.PutBucket("test-bucket") +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +fmt.Println("create bucket success, bucket location:", bucketLocation) +``` + +## 客户端异常 + +客户端异常表示客户端尝试向百度云服务发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当服务端出现异常时,百度云服务端会返回给用户相应的错误信息,以便定位问题。每种服务端的异常需参考各服务的官网文档。 + +## SDK日志 + +GO SDK自行实现了支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +该日志模块无任何外部依赖,开发者使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用。可使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +```go +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the sdk") +``` + +# 支持产品列表 + +产品名称 | 产品缩写 | 导入路径 | 说明文档 +-----------|-----------|---------------------------------------------------|---------- +应用型负载均衡 | APPBLB | github.com/baidubce/bce-sdk-go/services/appblb | [APPBLB.md](./doc/APPBLB.md) +物理服务器 | BBC | github.com/baidubce/bce-sdk-go/services/bbc | [BBC.md](./doc/BBC.md) +云服务器 | BCC | github.com/baidubce/bce-sdk-go/services/bcc | [BCC.md](./doc/BCC.md) +云监控 | BCM | github.com/baidubce/bce-sdk-go/services/bcm | [BCM.md](./doc/BCM.md) +边缘计算节点 | BEC | github.com/baidubce/bce-sdk-go/services/bec |[BEC.md](./doc/BEC.md) +百度智能边缘 | BIE | github.com/baidubce/bce-sdk-go/services/bie | +负载均衡 | BLB | github.com/baidubce/bce-sdk-go/services/blb | [BLB.md](./doc/BLB.md) +日志服务 | BLS | github.com/baidubce/bce-sdk-go/services/bls | [BLS.md](./doc/BLS.md) +百度对象存储 | BOS | github.com/baidubce/bce-sdk-go/services/bos | [BOS.md](./doc/BOS.md) +百度视频创作分发平台 | BVW | github.com/baidubce/bce-sdk-go/services/bvw | [BVW.md](./doc/BVW.md) +容器引擎 | CCE | github.com/baidubce/bce-sdk-go/services/cce | [CCE.md](./doc/CCE.md) +内容分布网络 | CDN | github.com/baidubce/bce-sdk-go/services/cdn | [CDN.md](./doc/CDN.md) +SSL证书服务 | CERT | github.com/baidubce/bce-sdk-go/services/cert | [CERT.md](./doc/CERT.md) +函数计算 | CFC | github.com/baidubce/bce-sdk-go/services/cfc | [CFC.md](./doc/CFC.md) +文件存储服务 | CFS | github.com/baidubce/bce-sdk-go/services/cfs | [CFS.md](./doc/CFS.md) +云防火墙 | CFW | github.com/baidubce/bce-sdk-go/services/cfw | [CFW.md](./doc/CFW.md) +云智能网 | CSN | github.com/baidubce/bce-sdk-go/services/csn | [CSN.md](./doc/CSN.md) +文档服务 | DOC | github.com/baidubce/bce-sdk-go/services/doc | [DOC.md](./doc/DOC.md) +数据传输服务 | DTS | github.com/baidubce/bce-sdk-go/services/dts | [DTS.md](./doc/DTS.md) +容器镜像服务 企业版 | ECCR | github.com/baidubce/bce-sdk-go/services/eccr | [ECCR.md](./doc/ECCR.md) +弹性公网IP | EIP | github.com/baidubce/bce-sdk-go/services/eip | [EIP.md](./doc/EIP.md) +ENIC弹性网卡 | ENIC | github.com/baidubce/bce-sdk-go/services/eni | [ENIC.md](./doc/ENIC.md) +企业安全组 | ESG | github.com/baidubce/bce-sdk-go/services/esg | [ESG.md](./doc/ESG.md) +高可用虚拟IP | HAVIP | github.com/baidubce/bce-sdk-go/services/havip | [HAVIP.md](./doc/HAVIP.md) +专线网关 | EtGateway | github.com/baidubce/bce-sdk-go/services/etGateway | [ETGateway.md](./doc/ETGateway.md) +身份管理 | IAM | github.com/baidubce/bce-sdk-go/services/iam | [IAM.md](./doc/IAM.md) +内网DNS | LOCALDNS | github.com/baidubce/bce-sdk-go/services/localDns | [LOCALDNS.md](./doc/LOCALDNS.md) +音视频处理MCP | MCP | github.com/baidubce/bce-sdk-go/services/media | [MCP.md](./doc/MCP.md) +云数据库 | RDS | github.com/baidubce/bce-sdk-go/services/rds | [RDS.md](./doc/RDS.md) +分布式缓存服务 | SCS | github.com/baidubce/bce-sdk-go/services/scs | [SCS.md](./doc/SCS.md) +SMS简单消息服务 | SMS | github.com/baidubce/bce-sdk-go/services/sms | [SMS.md](./doc/SMS.md) +SNIC服务网卡 | SNIC | github.com/baidubce/bce-sdk-go/services/endpoint | [SNIC.md](./doc/SNIC.md) +安全Token服务| STS | github.com/baidubce/bce-sdk-go/services/sts | [STS.md](./doc/STS.md) +视频内容分析 | VCA | github.com/baidubce/bce-sdk-go/services/vca | +视频内容审核 | VCR | github.com/baidubce/bce-sdk-go/services/vcr | +私有网络 | VPC | github.com/baidubce/bce-sdk-go/services/vpc | [VPC.md](./doc/VPC.md) +VPN网关 | VPN | github.com/baidubce/bce-sdk-go/services/vpn | [VPN.md](./doc/VPN.md) +多模态媒资检索 | MMS | github.com/baidubce/bce-sdk-go/services/mms | [MMS.md](./doc/MMS.md) +数据库专属集群 | DDC | github.com/baidubce/bce-sdk-go/services/ddc | [DDC.md](./doc/DDC.md) diff --git a/bce-sdk-go/auth/credentials.go b/bce-sdk-go/auth/credentials.go new file mode 100644 index 0000000..fb1e415 --- /dev/null +++ b/bce-sdk-go/auth/credentials.go @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// credentials.go - the credentials data structure definition + +// Package auth implements the authorization functionality for BCE. +// It use the BCE access key ID and secret access key with the specific sign algorithm to generate +// the authorization string. It also supports the temporary authorization by the STS token. +package auth + +import "errors" + +// BceCredentials define the data structure for authorization +type BceCredentials struct { + AccessKeyId string // access key id to the service + SecretAccessKey string // secret access key to the service + SessionToken string // session token generate by the STS service +} + +func (b *BceCredentials) String() string { + str := "ak: " + b.AccessKeyId + ", sk: " + b.SecretAccessKey + if len(b.SessionToken) != 0 { + return str + ", sessionToken: " + b.SessionToken + } + return str +} + +func NewBceCredentials(ak, sk string) (*BceCredentials, error) { + if len(ak) == 0 { + return nil, errors.New("accessKeyId should not be empty") + } + if len(sk) == 0 { + return nil, errors.New("secretKey should not be empty") + } + + return &BceCredentials{ak, sk, ""}, nil +} + +func NewSessionBceCredentials(ak, sk, token string) (*BceCredentials, error) { + if len(token) == 0 { + return nil, errors.New("sessionToken should not be empty") + } + + result, err := NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + result.SessionToken = token + + return result, nil +} diff --git a/bce-sdk-go/auth/signer.go b/bce-sdk-go/auth/signer.go new file mode 100644 index 0000000..212e1f5 --- /dev/null +++ b/bce-sdk-go/auth/signer.go @@ -0,0 +1,184 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// signer.go - implement the specific sign algorithm of BCE V1 protocol + +package auth + +import ( + "fmt" + "sort" + "strings" + + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BCE_AUTH_VERSION = "bce-auth-v1" + SIGN_JOINER = "\n" + SIGN_HEADER_JOINER = ";" + DEFAULT_EXPIRE_SECONDS = 1800 + DEFAULT_HEADERS_TO_SIGN = map[string]struct{}{ + strings.ToLower(http.HOST): {}, + strings.ToLower(http.CONTENT_LENGTH): {}, + strings.ToLower(http.CONTENT_TYPE): {}, + strings.ToLower(http.CONTENT_MD5): {}, + } +) + +// Signer abstracts the entity that implements the `Sign` method +type Signer interface { + // Sign the given Request with the Credentials and SignOptions + Sign(*http.Request, *BceCredentials, *SignOptions) +} + +// SignOptions defines the data structure used by Signer +type SignOptions struct { + HeadersToSign map[string]struct{} + Timestamp int64 + ExpireSeconds int +} + +func (opt *SignOptions) String() string { + return fmt.Sprintf(`SignOptions [ + HeadersToSign=%s; + Timestamp=%d; + ExpireSeconds=%d + ]`, opt.HeadersToSign, opt.Timestamp, opt.ExpireSeconds) +} + +// BceV1Signer implements the v1 sign algorithm +type BceV1Signer struct{} + +// Sign - generate the authorization string from the BceCredentials and SignOptions +// +// PARAMS: +// - req: *http.Request for this sign +// - cred: *BceCredentials to access the serice +// - opt: *SignOptions for this sign algorithm +func (b *BceV1Signer) Sign(req *http.Request, cred *BceCredentials, opt *SignOptions) { + if req == nil { + log.Fatal("request should not be null for sign") + return + } + if cred == nil { + log.Fatal("credentials should not be null for sign") + return + } + + // Prepare parameters + accessKeyId := cred.AccessKeyId + secretAccessKey := cred.SecretAccessKey + signDate := util.FormatISO8601Date(util.NowUTCSeconds()) + // Modify the sign time if it is not the default value but specified by client + if opt.Timestamp != 0 { + signDate = util.FormatISO8601Date(opt.Timestamp) + } + + // Set security token if using session credentials and session token not in param + if len(cred.SessionToken) != 0 && req.Param(http.BCE_SECURITY_TOKEN) == "" { + req.SetHeader(http.BCE_SECURITY_TOKEN, cred.SessionToken) + } + + // Prepare the canonical request components + signKeyInfo := fmt.Sprintf("%s/%s/%s/%d", + BCE_AUTH_VERSION, + accessKeyId, + signDate, + opt.ExpireSeconds) + signKey := util.HmacSha256Hex(secretAccessKey, signKeyInfo) + canonicalUri := getCanonicalURIPath(req.Uri()) + canonicalQueryString := getCanonicalQueryString(req.Params()) + canonicalHeaders, signedHeadersArr := getCanonicalHeaders(req.Headers(), opt.HeadersToSign) + + // Generate signed headers string + signedHeaders := "" + if len(signedHeadersArr) > 0 { + sort.Strings(signedHeadersArr) + signedHeaders = strings.Join(signedHeadersArr, SIGN_HEADER_JOINER) + } + + // Generate signature + canonicalParts := []string{req.Method(), canonicalUri, canonicalQueryString, canonicalHeaders} + canonicalReq := strings.Join(canonicalParts, SIGN_JOINER) + log.Debug("CanonicalRequest data:\n" + canonicalReq) + signature := util.HmacSha256Hex(signKey, canonicalReq) + + // Generate auth string and add to the reqeust header + authStr := signKeyInfo + "/" + signedHeaders + "/" + signature + log.Info("Authorization=" + authStr) + + req.SetHeader(http.AUTHORIZATION, authStr) +} + +func getCanonicalURIPath(path string) string { + if len(path) == 0 { + return "/" + } + canonical_path := path + if strings.HasPrefix(path, "/") { + canonical_path = path[1:] + } + canonical_path = util.UriEncode(canonical_path, false) + return "/" + canonical_path +} + +func getCanonicalQueryString(params map[string]string) string { + if len(params) == 0 { + return "" + } + + result := make([]string, 0, len(params)) + for k, v := range params { + if strings.ToLower(k) == strings.ToLower(http.AUTHORIZATION) { + continue + } + item := "" + if len(v) == 0 { + item = fmt.Sprintf("%s=", util.UriEncode(k, true)) + } else { + item = fmt.Sprintf("%s=%s", util.UriEncode(k, true), util.UriEncode(v, true)) + } + result = append(result, item) + } + sort.Strings(result) + return strings.Join(result, "&") +} + +func getCanonicalHeaders(headers map[string]string, + headersToSign map[string]struct{}) (string, []string) { + canonicalHeaders := make([]string, 0, len(headers)) + signHeaders := make([]string, 0, len(headersToSign)) + for k, v := range headers { + headKey := strings.ToLower(k) + if headKey == strings.ToLower(http.AUTHORIZATION) { + continue + } + _, headExists := headersToSign[headKey] + if headExists || + (strings.HasPrefix(headKey, http.BCE_PREFIX) && + (headKey != http.BCE_REQUEST_ID)) { + + headVal := strings.TrimSpace(v) + encoded := util.UriEncode(headKey, true) + ":" + util.UriEncode(headVal, true) + canonicalHeaders = append(canonicalHeaders, encoded) + signHeaders = append(signHeaders, headKey) + } + } + sort.Strings(canonicalHeaders) + sort.Strings(signHeaders) + return strings.Join(canonicalHeaders, SIGN_JOINER), signHeaders +} diff --git a/bce-sdk-go/bce/builder.go b/bce-sdk-go/bce/builder.go new file mode 100644 index 0000000..1b25504 --- /dev/null +++ b/bce-sdk-go/bce/builder.go @@ -0,0 +1,189 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// builder.go - defines the RequestBuilder structure for BCE servies + +package bce + +import ( + "encoding/json" + "fmt" +) + +// RequestBuilder holds config data for bce request. +// Some of fields are required and the others are optional. +// The builder pattern can simplify the execution of requests. +type RequestBuilder struct { + client Client + + url string // required + method string // required + queryParams map[string]string // optional + headers map[string]string // optional + body interface{} // optional + result interface{} // optional +} + +// create RequestBuilder with the client. +func NewRequestBuilder(client Client) *RequestBuilder { + return &RequestBuilder{ + client: client, + } +} + +func (b *RequestBuilder) WithURL(url string) *RequestBuilder { + b.url = url + return b +} + +func (b *RequestBuilder) WithMethod(method string) *RequestBuilder { + b.method = method + return b +} + +// set query param with the key/value directly. +func (b *RequestBuilder) WithQueryParam(key, value string) *RequestBuilder { + if b.queryParams == nil { + b.queryParams = make(map[string]string) + } + b.queryParams[key] = value + return b +} + +// set query param with the key/value only when the value is not blank. +func (b *RequestBuilder) WithQueryParamFilter(key, value string) *RequestBuilder { + if len(value) == 0 { + return b + } + return b.WithQueryParam(key, value) +} + +func (b *RequestBuilder) WithQueryParams(params map[string]string) *RequestBuilder { + if b.queryParams == nil { + b.queryParams = params + } else { + for key, value := range params { + b.queryParams[key] = value + } + } + return b +} + +func (b *RequestBuilder) WithHeader(key, value string) *RequestBuilder { + if b.headers == nil { + b.headers = make(map[string]string) + } + b.headers[key] = value + return b +} + +func (b *RequestBuilder) WithHeaders(headers map[string]string) *RequestBuilder { + if b.headers == nil { + b.headers = headers + } else { + for key, value := range headers { + b.headers[key] = value + } + } + return b +} + +func (b *RequestBuilder) WithBody(body interface{}) *RequestBuilder { + b.body = body + return b +} + +func (b *RequestBuilder) WithResult(result interface{}) *RequestBuilder { + b.result = result + return b +} + +// Do will send request to bce and get result with the builder's parameters. +func (b *RequestBuilder) Do() error { + if err := b.validate(); err != nil { + return err + } + + // build BceRequest + req, err := b.buildBceRequest() + if err != nil { + return err + } + + // get result from BceResponse + if err := b.buildBceResponse(req); err != nil { + return err + } + + return nil +} + +// Validate if the required fields are providered. +func (b *RequestBuilder) validate() error { + if len(b.url) == 0 { + return fmt.Errorf("The url can't be null.") + } + if len(b.method) == 0 { + return fmt.Errorf("The method can't be null.") + } + if b.client == nil { + return fmt.Errorf("The client can't be null.") + } + return nil +} + +func (b *RequestBuilder) buildBceRequest() (*BceRequest, error) { + // Build the request + req := &BceRequest{} + req.SetUri(b.url) + req.SetMethod(b.method) + + if b.headers != nil { + req.SetHeaders(b.headers) + } + if b.queryParams != nil { + req.SetParams(b.queryParams) + } + if b.body != nil { + bodyBytes, err := json.Marshal(b.body) + if err != nil { + return nil, err + } + body, err := NewBodyFromBytes(bodyBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + } + + return req, nil +} + +func (b *RequestBuilder) buildBceResponse(req *BceRequest) error { + // Send request and get response + resp := &BceResponse{} + if err := b.client.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer resp.Body().Close() + + if b.result == nil { + return nil + } + + return resp.ParseJsonBody(b.result) +} diff --git a/bce-sdk-go/bce/client.go b/bce-sdk-go/bce/client.go new file mode 100644 index 0000000..d8ea23d --- /dev/null +++ b/bce-sdk-go/bce/client.go @@ -0,0 +1,279 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - definiton the BceClientConfiguration and BceClient structure + +// Package bce implements the infrastructure to access BCE services. +// +// - BceClient: +// It is the general client of BCE to access all services. It builds http request to access the +// services based on the given client configuration. +// +// - BceClientConfiguration: +// The client configuration data structure which contains endpoint, region, credentials, retry +// policy, sign options and so on. It supports most of the default value and user can also +// access or change the default with its public fields' name. +// +// - Error types: +// The error types when making request or receiving response to the BCE services contains two +// types: the BceClientError when making request to BCE services and the BceServiceError when +// recieving response from them. +// +// - BceRequest: +// The request instance stands for an request to access the BCE services. +// +// - BceResponse: +// The response instance stands for an response from the BCE services. +package bce + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "time" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +// Client is the general interface which can perform sending request. Different service +// will define its own client in case of specific extension. +type Client interface { + SendRequest(*BceRequest, *BceResponse) error + SendRequestFromBytes(*BceRequest, *BceResponse, []byte) error + GetBceClientConfig() *BceClientConfiguration +} + +// BceClient defines the general client to access the BCE services. +type BceClient struct { + Config *BceClientConfiguration + Signer auth.Signer // the sign algorithm +} + +// BuildHttpRequest - the helper method for the client to build http request +// +// PARAMS: +// - request: the input request object to be built +func (c *BceClient) buildHttpRequest(request *BceRequest) { + // Construct the http request instance for the special fields + request.BuildHttpRequest() + + // Set the client specific configurations + if request.Endpoint() == "" { + request.SetEndpoint(c.Config.Endpoint) + } + if request.Protocol() == "" { + request.SetProtocol(DEFAULT_PROTOCOL) + } + if len(c.Config.ProxyUrl) != 0 { + request.SetProxyUrl(c.Config.ProxyUrl) + } + request.SetTimeout(c.Config.ConnectionTimeoutInMillis / 1000) + + // Set the BCE request headers + request.SetHeader(http.HOST, request.Host()) + request.SetHeader(http.USER_AGENT, c.Config.UserAgent) + request.SetHeader(http.BCE_DATE, util.FormatISO8601Date(util.NowUTCSeconds())) + + //set default content-type if null + if request.Header(http.CONTENT_TYPE) == "" { + request.SetHeader(http.CONTENT_TYPE, DEFAULT_CONTENT_TYPE) + } + + // Generate the auth string if needed + if c.Config.Credentials != nil { + c.Signer.Sign(&request.Request, c.Config.Credentials, c.Config.SignOption) + } +} + +// SendRequest - the client performs sending the http request with retry policy and receive the +// response from the BCE services. +// +// PARAMS: +// - req: the request object to be sent to the BCE service +// - resp: the response object to receive the content from BCE service +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *BceClient) SendRequest(req *BceRequest, resp *BceResponse) error { + // Return client error if it is not nil + if req.ClientError() != nil { + return req.ClientError() + } + + // Build the http request and prepare to send + c.buildHttpRequest(req) + log.Infof("send http request: %v", req) + + // Send request with the given retry policy + retries := 0 + if req.Body() != nil { + defer req.Body().Close() // Manually close the ReadCloser body for retry + } + for { + // The request body should be temporarily saved if retry to send the http request + var retryBuf bytes.Buffer + var teeReader io.Reader + if c.Config.Retry.ShouldRetry(nil, 0) && req.Body() != nil { + teeReader = io.TeeReader(req.Body(), &retryBuf) + req.Request.SetBody(ioutil.NopCloser(teeReader)) + } + httpResp, err := http.Execute(&req.Request) + + if err != nil { + if c.Config.Retry.ShouldRetry(err, retries) { + delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries) + time.Sleep(delay_in_mills) + } else { + return &BceClientError{ + fmt.Sprintf("execute http request failed! Retried %d times, error: %v", + retries, err)} + } + retries++ + log.Warnf("send request failed: %v, retry for %d time(s)", err, retries) + if req.Body() != nil { + ioutil.ReadAll(teeReader) + req.Request.SetBody(ioutil.NopCloser(&retryBuf)) + } + continue + } + resp.SetHttpResponse(httpResp) + resp.ParseResponse() + + log.Infof("receive http response: status: %s, debugId: %s, requestId: %s, elapsed: %v", + resp.StatusText(), resp.DebugId(), resp.RequestId(), resp.ElapsedTime()) + + if resp.ElapsedTime().Milliseconds() > DEFAULT_WARN_LOG_TIMEOUT_IN_MILLS { + log.Warnf("request time more than 5 second, debugId: %s, requestId: %s, elapsed: %v", + resp.DebugId(), resp.RequestId(), resp.ElapsedTime()) + } + for k, v := range resp.Headers() { + log.Debugf("%s=%s", k, v) + } + if resp.IsFail() { + err := resp.ServiceError() + if c.Config.Retry.ShouldRetry(err, retries) { + delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries) + time.Sleep(delay_in_mills) + } else { + return err + } + retries++ + log.Warnf("send request failed, retry for %d time(s)", retries) + if req.Body() != nil { + ioutil.ReadAll(teeReader) + req.Request.SetBody(ioutil.NopCloser(&retryBuf)) + } + continue + } + return nil + } +} + +// SendRequestFromBytes - the client performs sending the http request with retry policy and receive the +// response from the BCE services. +// +// PARAMS: +// - req: the request object to be sent to the BCE service +// - resp: the response object to receive the content from BCE service +// - content: the content of body +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *BceClient) SendRequestFromBytes(req *BceRequest, resp *BceResponse, content []byte) error { + // Return client error if it is not nil + if req.ClientError() != nil { + return req.ClientError() + } + // Build the http request and prepare to send + c.buildHttpRequest(req) + log.Infof("send http request: %v", req) + // Send request with the given retry policy + retries := 0 + for { + // The request body should be temporarily saved if retry to send the http request + buf := bytes.NewBuffer(content) + req.Request.SetBody(ioutil.NopCloser(buf)) + defer req.Request.Body().Close() // Manually close the ReadCloser body for retry + httpResp, err := http.Execute(&req.Request) + if err != nil { + if c.Config.Retry.ShouldRetry(err, retries) { + delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries) + time.Sleep(delay_in_mills) + } else { + return &BceClientError{ + fmt.Sprintf("execute http request failed! Retried %d times, error: %v", + retries, err)} + } + retries++ + log.Warnf("send request failed: %v, retry for %d time(s)", err, retries) + continue + } + resp.SetHttpResponse(httpResp) + resp.ParseResponse() + log.Infof("receive http response: status: %s, debugId: %s, requestId: %s, elapsed: %v", + resp.StatusText(), resp.DebugId(), resp.RequestId(), resp.ElapsedTime()) + for k, v := range resp.Headers() { + log.Debugf("%s=%s", k, v) + } + if resp.IsFail() { + err := resp.ServiceError() + if c.Config.Retry.ShouldRetry(err, retries) { + delay_in_mills := c.Config.Retry.GetDelayBeforeNextRetryInMillis(err, retries) + time.Sleep(delay_in_mills) + } else { + return err + } + retries++ + log.Warnf("send request failed, retry for %d time(s)", retries) + continue + } + return nil + } +} + +func (c *BceClient) GetBceClientConfig() *BceClientConfiguration { + return c.Config +} + +func NewBceClient(conf *BceClientConfiguration, sign auth.Signer) *BceClient { + clientConfig := http.ClientConfig{RedirectDisabled: conf.RedirectDisabled} + http.InitClient(clientConfig) + return &BceClient{conf, sign} +} + +func NewBceClientWithAkSk(ak, sk, endPoint string) (*BceClient, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &BceClientConfiguration{ + Endpoint: endPoint, + Region: DEFAULT_REGION, + UserAgent: DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS, + RedirectDisabled: false} + v1Signer := &auth.BceV1Signer{} + + return NewBceClient(defaultConf, v1Signer), nil +} diff --git a/bce-sdk-go/bce/config.go b/bce-sdk-go/bce/config.go new file mode 100644 index 0000000..37de16a --- /dev/null +++ b/bce-sdk-go/bce/config.go @@ -0,0 +1,81 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// config.go - define the client configuration for BCE + +package bce + +import ( + "fmt" + "reflect" + "runtime" + + "github.com/baidubce/bce-sdk-go/auth" +) + +// Constants and default values for the package bce +const ( + SDK_VERSION = "0.9.164" + URI_PREFIX = "/" // now support uri without prefix "v1" so just set root path + DEFAULT_DOMAIN = "baidubce.com" + DEFAULT_PROTOCOL = "http" + DEFAULT_REGION = "bj" + DEFAULT_CONTENT_TYPE = "application/json;charset=utf-8" + DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS = 1200 * 1000 + DEFAULT_WARN_LOG_TIMEOUT_IN_MILLS = 5 * 1000 +) + +var ( + DEFAULT_USER_AGENT string + DEFAULT_RETRY_POLICY = NewBackOffRetryPolicy(3, 20000, 300) +) + +func init() { + DEFAULT_USER_AGENT = "bce-sdk-go" + DEFAULT_USER_AGENT += "/" + SDK_VERSION + DEFAULT_USER_AGENT += "/" + runtime.Version() + DEFAULT_USER_AGENT += "/" + runtime.GOOS + DEFAULT_USER_AGENT += "/" + runtime.GOARCH +} + +// BceClientConfiguration defines the config components structure. +type BceClientConfiguration struct { + Endpoint string + ProxyUrl string + Region string + UserAgent string + Credentials *auth.BceCredentials + SignOption *auth.SignOptions + Retry RetryPolicy + ConnectionTimeoutInMillis int + // CnameEnabled should be true when use custom domain as endpoint to visit bos resource + CnameEnabled bool + BackupEndpoint string + RedirectDisabled bool +} + +func (c *BceClientConfiguration) String() string { + return fmt.Sprintf(`BceClientConfiguration [ + Endpoint=%s; + ProxyUrl=%s; + Region=%s; + UserAgent=%s; + Credentials=%v; + SignOption=%v; + RetryPolicy=%v; + ConnectionTimeoutInMillis=%v; + RedirectDisabled=%v + ]`, c.Endpoint, c.ProxyUrl, c.Region, c.UserAgent, c.Credentials, + c.SignOption, reflect.TypeOf(c.Retry).Name(), c.ConnectionTimeoutInMillis, c.RedirectDisabled) +} diff --git a/bce-sdk-go/bce/error.go b/bce-sdk-go/bce/error.go new file mode 100644 index 0000000..e625651 --- /dev/null +++ b/bce-sdk-go/bce/error.go @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// error.go - define the error types for BCE + +package bce + +const ( + EACCESS_DENIED = "AccessDenied" + EINAPPROPRIATE_JSON = "InappropriateJSON" + EINTERNAL_ERROR = "InternalError" + EINVALID_ACCESS_KEY_ID = "InvalidAccessKeyId" + EINVALID_HTTP_AUTH_HEADER = "InvalidHTTPAuthHeader" + EINVALID_HTTP_REQUEST = "InvalidHTTPRequest" + EINVALID_URI = "InvalidURI" + EMALFORMED_JSON = "MalformedJSON" + EINVALID_VERSION = "InvalidVersion" + EOPT_IN_REQUIRED = "OptInRequired" + EPRECONDITION_FAILED = "PreconditionFailed" + EREQUEST_EXPIRED = "RequestExpired" + ESIGNATURE_DOES_NOT_MATCH = "SignatureDoesNotMatch" +) + +// BceError abstracts the error for BCE +type BceError interface { + error +} + +// BceClientError defines the error struct for the client when making request +type BceClientError struct{ Message string } + +func (b *BceClientError) Error() string { return b.Message } + +func NewBceClientError(msg string) *BceClientError { return &BceClientError{msg} } + +// BceServiceError defines the error struct for the BCE service when receiving response +type BceServiceError struct { + Code string + Message string + RequestId string + StatusCode int +} + +func (b *BceServiceError) Error() string { + ret := "[Code: " + b.Code + ret += "; Message: " + b.Message + ret += "; RequestId: " + b.RequestId + "]" + return ret +} + +func NewBceServiceError(code, msg, reqId string, status int) *BceServiceError { + return &BceServiceError{code, msg, reqId, status} +} diff --git a/bce-sdk-go/bce/request.go b/bce-sdk-go/bce/request.go new file mode 100644 index 0000000..49eecc2 --- /dev/null +++ b/bce-sdk-go/bce/request.go @@ -0,0 +1,224 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// request.go - defines the BCE servies request + +package bce + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// Body defines the data structure used in BCE request. +// Every BCE request that sets the body field must set its content-length and content-md5 headers +// to ensure the correctness of the body content forcely, and users can also set the content-sha256 +// header to strengthen the correctness with the "SetHeader" method. +type Body struct { + stream io.ReadCloser + size int64 + contentMD5 string +} + +func (b *Body) Stream() io.ReadCloser { return b.stream } + +func (b *Body) SetStream(stream io.ReadCloser) { b.stream = stream } + +func (b *Body) Size() int64 { return b.size } + +func (b *Body) ContentMD5() string { return b.contentMD5 } + +// NewBodyFromBytes - build a Body object from the byte stream to be used in the http request, it +// calculates the content-md5 of the byte stream and store the size as well as the stream. +// +// PARAMS: +// - stream: byte stream +// +// RETURNS: +// - *Body: the return Body object +// - error: error if any specific error occurs +func NewBodyFromBytes(stream []byte) (*Body, error) { + buf := bytes.NewBuffer(stream) + size := int64(buf.Len()) + contentMD5, err := util.CalculateContentMD5(buf, size) + if err != nil { + return nil, err + } + buf = bytes.NewBuffer(stream) + return &Body{ioutil.NopCloser(buf), size, contentMD5}, nil +} + +// NewBodyFromString - build a Body object from the string to be used in the http request, it +// calculates the content-md5 of the byte stream and store the size as well as the stream. +// +// PARAMS: +// - str: the input string +// +// RETURNS: +// - *Body: the return Body object +// - error: error if any specific error occurs +func NewBodyFromString(str string) (*Body, error) { + buf := bytes.NewBufferString(str) + size := int64(len(str)) + contentMD5, err := util.CalculateContentMD5(buf, size) + if err != nil { + return nil, err + } + buf = bytes.NewBufferString(str) + return &Body{ioutil.NopCloser(buf), size, contentMD5}, nil +} + +// NewBodyFromFile - build a Body object from the given file name to be used in the http request, +// it calculates the content-md5 of the byte stream and store the size as well as the stream. +// +// PARAMS: +// - fname: the given file name +// +// RETURNS: +// - *Body: the return Body object +// - error: error if any specific error occurs +func NewBodyFromFile(fname string) (*Body, error) { + file, err := os.Open(fname) + if err != nil { + return nil, err + } + fileInfo, infoErr := file.Stat() + if infoErr != nil { + return nil, infoErr + } + contentMD5, md5Err := util.CalculateContentMD5(file, fileInfo.Size()) + if md5Err != nil { + return nil, md5Err + } + if _, err = file.Seek(0, 0); err != nil { + return nil, err + } + return &Body{file, fileInfo.Size(), contentMD5}, nil +} + +// NewBodyFromSectionFile - build a Body object from the given file pointer with offset and size. +// It calculates the content-md5 of the given content and store the size as well as the stream. +// +// PARAMS: +// - file: the input file pointer +// - off: offset of current section body +// - size: current section body size +// +// RETURNS: +// - *Body: the return Body object +// - error: error if any specific error occurs +func NewBodyFromSectionFile(file *os.File, off, size int64) (*Body, error) { + if _, err := file.Seek(off, 0); err != nil { + return nil, err + } + contentMD5, md5Err := util.CalculateContentMD5(file, size) + if md5Err != nil { + return nil, md5Err + } + if _, err := file.Seek(0, 0); err != nil { + return nil, err + } + section := io.NewSectionReader(file, off, size) + return &Body{ioutil.NopCloser(section), size, contentMD5}, nil +} + +// NewBodyFromSizedReader - build a Body object from the given reader with size. +// It calculates the content-md5 of the given content and store the size as well as the stream. +// +// PARAMS: +// - r: the input reader +// - size: the size to be read, -1 is read all +// +// RETURNS: +// - *Body: the return Body object +// - error: error if any specific error occurs +func NewBodyFromSizedReader(r io.Reader, size int64) (*Body, error) { + var buffer bytes.Buffer + var rlen int64 + var err error + if size >= 0 { + rlen, err = io.CopyN(&buffer, r, size) + } else { + rlen, err = io.Copy(&buffer, r) + } + if err != nil { + return nil, err + } + if rlen != int64(buffer.Len()) { // must be equal + return nil, NewBceClientError("unexpected reader") + } + if size >= 0 { + if rlen < size { + return nil, NewBceClientError("size is great than reader actual size") + } + } + contentMD5, err := util.CalculateContentMD5(bytes.NewBuffer(buffer.Bytes()), rlen) + if err != nil { + return nil, err + } + body := &Body{ + stream: ioutil.NopCloser(&buffer), + size: rlen, + contentMD5: contentMD5, + } + return body, nil +} + +// BceRequest defines the request structure for accessing BCE services +type BceRequest struct { + http.Request + requestId string + clientError *BceClientError +} + +func (b *BceRequest) RequestId() string { return b.requestId } + +func (b *BceRequest) SetRequestId(val string) { b.requestId = val } + +func (b *BceRequest) ClientError() *BceClientError { return b.clientError } + +func (b *BceRequest) SetClientError(err *BceClientError) { b.clientError = err } + +func (b *BceRequest) SetBody(body *Body) { // override SetBody derived from http.Request + b.Request.SetBody(body.Stream()) + b.SetLength(body.Size()) // set field of "net/http.Request.ContentLength" + if body.Size() > 0 { + b.SetHeader(http.CONTENT_MD5, body.ContentMD5()) + b.SetHeader(http.CONTENT_LENGTH, fmt.Sprintf("%d", body.Size())) + } +} + +func (b *BceRequest) BuildHttpRequest() { + // Only need to build the specific `requestId` field for BCE, other fields are same as the + // `http.Request` as well as its methods. + if len(b.requestId) == 0 { + // Construct the request ID with UUID + b.requestId = util.NewRequestId() + } + b.SetHeader(http.BCE_REQUEST_ID, b.requestId) +} + +func (b *BceRequest) String() string { + requestIdStr := "requestId=" + b.requestId + if b.clientError != nil { + return requestIdStr + ", client error: " + b.ClientError().Error() + } + return requestIdStr + "\n" + b.Request.String() +} diff --git a/bce-sdk-go/bce/response.go b/bce-sdk-go/bce/response.go new file mode 100644 index 0000000..94f2ae4 --- /dev/null +++ b/bce-sdk-go/bce/response.go @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// response.go - defines the common BCE services response + +package bce + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "strings" + "time" + + "github.com/baidubce/bce-sdk-go/http" +) + +// BceResponse defines the response structure for receiving BCE services response. +type BceResponse struct { + statusCode int + statusText string + requestId string + debugId string + response *http.Response + serviceError *BceServiceError +} + +func (r *BceResponse) IsFail() bool { + return r.response.StatusCode() >= 400 +} + +func (r *BceResponse) StatusCode() int { + return r.statusCode +} + +func (r *BceResponse) StatusText() string { + return r.statusText +} + +func (r *BceResponse) RequestId() string { + return r.requestId +} + +func (r *BceResponse) DebugId() string { + return r.debugId +} + +func (r *BceResponse) Header(key string) string { + return r.response.GetHeader(key) +} + +func (r *BceResponse) Headers() map[string]string { + return r.response.GetHeaders() +} + +func (r *BceResponse) Body() io.ReadCloser { + return r.response.Body() +} + +func (r *BceResponse) SetHttpResponse(response *http.Response) { + r.response = response +} + +func (r *BceResponse) ElapsedTime() time.Duration { + return r.response.ElapsedTime() +} + +func (r *BceResponse) ServiceError() *BceServiceError { + return r.serviceError +} + +func (r *BceResponse) ParseResponse() { + r.statusCode = r.response.StatusCode() + r.statusText = r.response.StatusText() + r.requestId = r.response.GetHeader(http.BCE_REQUEST_ID) + r.debugId = r.response.GetHeader(http.BCE_DEBUG_ID) + if r.IsFail() { + r.serviceError = NewBceServiceError("", r.statusText, r.requestId, r.statusCode) + + // First try to read the error `Code' and `Message' from body + rawBody, _ := ioutil.ReadAll(r.Body()) + defer r.Body().Close() + if len(rawBody) != 0 { + jsonDecoder := json.NewDecoder(bytes.NewBuffer(rawBody)) + if err := jsonDecoder.Decode(r.serviceError); err != nil { + r.serviceError = NewBceServiceError( + EMALFORMED_JSON, + "Service json error message decode failed", + r.requestId, + r.statusCode) + } + return + } + + // Then guess the `Message' from by the return status code + switch r.statusCode { + case 400: + r.serviceError.Code = EINVALID_HTTP_REQUEST + case 403: + r.serviceError.Code = EACCESS_DENIED + case 412: + r.serviceError.Code = EPRECONDITION_FAILED + case 500: + r.serviceError.Code = EINTERNAL_ERROR + default: + words := strings.Split(r.statusText, " ") + r.serviceError.Code = strings.Join(words[1:], "") + } + } +} + +func (r *BceResponse) ParseJsonBody(result interface{}) error { + defer r.Body().Close() + jsonDecoder := json.NewDecoder(r.Body()) + return jsonDecoder.Decode(result) +} diff --git a/bce-sdk-go/bce/retry.go b/bce-sdk-go/bce/retry.go new file mode 100644 index 0000000..19675a3 --- /dev/null +++ b/bce-sdk-go/bce/retry.go @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// retry.go - define the retry policy when making requests to BCE services + +package bce + +import ( + "net" + "net/http" + "time" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +// RetryPolicy defines the two methods to retry for sending request. +type RetryPolicy interface { + ShouldRetry(BceError, int) bool + GetDelayBeforeNextRetryInMillis(BceError, int) time.Duration +} + +// NoRetryPolicy just does not retry. +type NoRetryPolicy struct{} + +func (_ *NoRetryPolicy) ShouldRetry(err BceError, attempts int) bool { + return false +} + +func (_ *NoRetryPolicy) GetDelayBeforeNextRetryInMillis( + err BceError, attempts int) time.Duration { + return 0 * time.Millisecond +} + +func NewNoRetryPolicy() *NoRetryPolicy { + return &NoRetryPolicy{} +} + +// BackOffRetryPolicy implements a policy that retries with exponential back-off strategy. +// This policy will keep retrying until the maximum number of retries is reached. The delay time +// will be a fixed interval for the first time then 2 * interval for the second, 4 * internal for +// the third, and so on. +// In general, the delay time will be 2^number_of_retries_attempted*interval. When a maximum of +// delay time is specified, the delay time will never exceed this limit. +type BackOffRetryPolicy struct { + maxErrorRetry int + maxDelayInMillis int64 + baseIntervalInMillis int64 +} + +func (b *BackOffRetryPolicy) ShouldRetry(err BceError, attempts int) bool { + // Do not retry any more when retry the max times + if attempts >= b.maxErrorRetry { + return false + } + + if err == nil { + return true + } + + // Always retry on IO error + if _, ok := err.(net.Error); ok { + return true + } + + // Only retry on a service error + if realErr, ok := err.(*BceServiceError); ok { + switch realErr.StatusCode { + case http.StatusInternalServerError: + log.Warn("retry for internal server error(500)") + return true + case http.StatusBadGateway: + log.Warn("retry for bad gateway(502)") + return true + case http.StatusServiceUnavailable: + log.Warn("retry for service unavailable(503)") + return true + case http.StatusBadRequest: + if realErr.Code != "Http400" { + return false + } + log.Warn("retry for bad request(400)") + return true + } + + if realErr.Code == EREQUEST_EXPIRED { + log.Warn("retry for request expired") + return true + } + } + return false +} + +func (b *BackOffRetryPolicy) GetDelayBeforeNextRetryInMillis( + err BceError, attempts int) time.Duration { + if attempts < 0 { + return 0 * time.Millisecond + } + delayInMillis := (1 << uint64(attempts)) * b.baseIntervalInMillis + if delayInMillis > b.maxDelayInMillis { + return time.Duration(b.maxDelayInMillis) * time.Millisecond + } + return time.Duration(delayInMillis) * time.Millisecond +} + +func NewBackOffRetryPolicy(maxRetry int, maxDelay, base int64) *BackOffRetryPolicy { + return &BackOffRetryPolicy{maxRetry, maxDelay, base} +} diff --git a/bce-sdk-go/doc/APPBLB.md b/bce-sdk-go/doc/APPBLB.md new file mode 100644 index 0000000..b7ade3b --- /dev/null +++ b/bce-sdk-go/doc/APPBLB.md @@ -0,0 +1,1288 @@ +# APPBLB服务 + +# 概述 + +本文档主要介绍应用型BLB GO SDK的使用。在使用本文档前,您需要先了解应用型BLB的一些基本知识。若您还不了解应用型BLB,可以参考[产品描述](https://cloud.baidu.com/doc/BLB/s/Ajwvxno34)和[入门指南](https://cloud.baidu.com/doc/BLB/s/cjwvxnr91)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BLB访问域名](https://cloud.baidu.com/doc/BLB/s/cjwvxnzix)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云BLB,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BLB做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建应用型BLB Client + +应用型BLB Client是应用型BLB服务的客户端,为开发者与BLB服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建应用型BLB Client + +通过AK/SK方式访问BLB,用户可以参考如下代码新建一个BLB Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/appblb" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BLBClient + blbClient, err := appblb.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BLB/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BLB的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`blb.bj.baidubce.com`。 + +### 使用STS创建BLB Client + +**申请STS token** + +BLB可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BLB,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建BLB Client** + +申请好STS后,可将STS Token配置到BLB Client中,从而实现通过STS Token创建BLB Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BLB Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/appblb" //导入APPBLB服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BLB服务的Client对象,Endpoint使用默认值 + blbClient, err := appblb.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create appblb client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + blbClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BLB Client时,无论对应BLB服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BLB + +BLB支持HTTPS传输协议,您可以通过在创建BLB Client对象时指定的Endpoint中指明HTTPS的方式,在BLB GO SDK中使用HTTPS访问BLB服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/appblb" + +ENDPOINT := "https://blb.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +blbClient, _ := appblb.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BLB Client + +如果用户需要配置BLB Client的一些细节的参数,可以在创建BLB Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BLB服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/appblb" + +//创建BLB Client对象 +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com +client, _ := appblb.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/appblb" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := appblb.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/appblb" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := appblb.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BLB时,创建的BLB Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BLB Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +应用型blb实例针对用户复杂应用部署架构,特别是大型网站架构。使用基于策略的网络管理框架构建,实现业务驱动的流量负载均衡。 + +## 实例管理 + +### 创建实例 + +通过以下代码,可以创建一个应用型LoadBalancer,返回分配的服务地址及实例ID +```go +args := &appblb.CreateLoadBalancerArgs{ + // 设置实例名称 + Name: "sdkBlb", + // 设置实例描述 + Description: "sdk create", + // 设置实例所属vpc + VpcId: vpcId, + // 设置实例所属子网 + SubnetId: subnetId, +} + +// 若要为实例设置标签,可以按照以下代码,标签设置之后,不可修改和删除 +args.Tags = []model.TagModel{ + { + TagKey: "key", + TagValue: "value", + }, +} +result, err := client.CreateLoadBalancer(args) +if err != nil { + fmt.Println("create appblb failed:", err) +} else { + fmt.Println("create appblb success: ", result) +} +``` + +### 更新实例 + +通过以下代码,可以更新一个LoadBalancer的配置信息,如实例名称和描述 +```go +args := &appblb.UpdateLoadBalancerArgs{ + Name: "testSdk", + Description: "test desc", +} +err := client.UpdateLoadBalancer(blbId, args) +if err != nil { + fmt.Println("update appblb failed:", err) +} else { + fmt.Println("update appblb success") +} +``` +### 查询已有的实例 + +通过以下代码,可以查询用户账户下所有LoadBalancer的信息 +```go +args := &appblb.DescribeLoadBalancersArgs{} + +// 支持按LoadBalancer的id、name、address进行查询,匹配规则支持部分包含(不支持正则) +args.BlbId = blbId +args.Name = blbName +args.Address = blbAddress +args.ExactlyMatch = true + +// 支持查找绑定指定BLB的LoadBalancer,通过blbId参数指定 +args.BlbId = blbId + +result, err := client.DescribeLoadBalancers(args) +if err != nil { + fmt.Println("list all appblb failed:", err) +} else { + fmt.Println("list all appblb success: ", result) +} +``` + +### 查询实例详情 + +通过以下代码,可以按id查询用户账户下特定的应用型LoadBalancer的详细信息,包含LoadBalancer所有的监听器端口信息 +```go +result, err := client.DescribeLoadBalancerDetail(blbId) +if err != nil { + fmt.Println("get appblb detail failed:", err) +} else { + fmt.Println("get appblb detail success: ", result) +} +``` + +### 释放实例 + +通过以下代码,可以释放指定LoadBalancer,被释放的LoadBalancer无法找回 +```go +err := client.DeleteLoadBalancer(blbId) +if err != nil { + fmt.Println("delete appblb failed:", err) +} else { + fmt.Println("delete appblb success") +} +``` + +## 服务器组管理 + +### 创建应用型服务器组 + +通过以下代码,在指定应用型BLB下,创建一个服务器组,用来绑定后端服务器,以及为监听器开放相应的端口 +```go +args := &appblb.CreateAppServerGroupArgs{ + // 设置服务器组名称 + Name: "sdkTest", + // 设置服务器组描述 + Description: "sdk test desc", +} + +// 若想直接绑定后端服务器,可以设置以下参数 +args.BackendServerList = []appblb.AppBackendServer{ + { + InstanceId: instanceId, + Weight: 50, + }, +} + +result, err := client.CreateAppServerGroup(blbId, args) +if err != nil { + fmt.Println("create server group failed:", err) +} else { + fmt.Println("create server group success: ", result) +} +``` + +### 更新服务器组 + +通过以下代码,更新指定LoadBalancer下的服务器组参数,所有请求参数中指定的域都会被更新,未指定的域保持不变 +```go +args := &appblb.UpdateAppServerGroupArgs{ + // 设置要更新的服务器组ID + SgId: serverGroupId, + // 设置新的服务器组名称 + Name: "testSdk", + // 设置新的服务器组描述 + Description: "test desc", +} +err := client.UpdateAppServerGroup(blbId, args) +if err != nil { + fmt.Println("update server group failed:", err) +} else { + fmt.Println("update server group success: ", result) +} +``` + +### 查询服务器组 + +通过以下代码,查询指定LoadBalancer下所有服务器组的信息 +```go +args := &appblb.DescribeAppServerGroupArgs{} + +// 按BLBID、name为条件进行全局查询 +args.BlbId = blbId +args.Name = servergroupName +args.ExactlyMatch = true + +result, err := client.DescribeAppServerGroup(blbId, args) +if err != nil { + fmt.Println("describe server group failed:", err) +} else { + fmt.Println("describe server group success: ", result) +} +``` + +### 删除服务器组 + +通过以下代码,删除服务器组,通过服务器组id指定 +```go +args := &appblb.DeleteAppServerGroupArgs{ + // 要删除的服务器组ID + SgId: serverGroupId, +} +err := client.DeleteAppServerGroup(blbId, args) +if err != nil { + fmt.Println("delete server group failed:", err) +} else { + fmt.Println("delete server group success: ", result) +} +``` + +### 创建应用型服务器组端口 + +通过以下代码,在指定应用型BLB下,创建一个服务器组后端端口,将发往该端口的所有流量按权重轮询分发到其绑定的对应服务器列表中的服务器 +```go +args := &appblb.CreateAppServerGroupPortArgs{ + // 端口所属服务器组ID + SgId: serverGroupId, + // 监听的端口号 + Port: 80, + // 监听的协议类型 + Type: "TCP", +} + +// 可以选择设置相应健康检查协议的参数 +args.HealthCheck = "TCP" +args.HealthCheckPort = 30 +args.HealthCheckIntervalInSecond = 10 +args.HealthCheckTimeoutInSecond = 10 + +result, err := client.CreateAppServerGroupPort(blbId, args) +if err != nil { + fmt.Println("create server group port failed:", err) +} else { + fmt.Println("create server group port success: ", result) +} +``` + +> **提示:** +> - 配置健康检查协议的参数,详细请参考BLB API 文档[创建应用型服务器组端口](https://cloud.baidu.com/doc/BLB/s/Bjwvxny4u#createappservergroupport%E5%88%9B%E5%BB%BA%E5%BA%94%E7%94%A8%E5%9E%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BB%84%E7%AB%AF%E5%8F%A3) + +### 更新服务器组端口 + +通过以下代码,根据id更新服务器组端口 +```go +args := &appblb.UpdateAppServerGroupPortArgs{ + // 端口所属服务器组ID + SgId: serverGroupId, + // 端口Id,由创建创建应用型服务器组端口返回 + PortId: portId, + // 设置健康检查协议参数 + HealthCheck: "TCP", + HealthCheckPort: 30, + HealthCheckIntervalInSecond: 10, + HealthCheckTimeoutInSecond: 10, +} +err := client.UpdateAppServerGroupPort(blbId, args) +if err != nil { + fmt.Println("update server group port failed:", err) +} else { + fmt.Println("update server group port success: ", result) +} +``` + +> **提示:** +> - 配置健康检查协议的参数,详细请参考BLB API 文档[更新服务器组端口](https://cloud.baidu.com/doc/BLB/s/Bjwvxny4u#updateappservergroupport%E6%9B%B4%E6%96%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BB%84%E7%AB%AF%E5%8F%A3) + +### 删除服务器组端口 + +通过以下代码,删除服务器组端口,通过服务器组id指定 +```go +args := &appblb.DeleteAppServerGroupPortArgs{ + // 端口所属服务器组ID + SgId: serverGroupId, + // 要删除的端口服务ID列表 + PortIdList: []string{portId}, +} +err := client.DeleteAppServerGroupPort(blbId, args) +if err != nil { + fmt.Println("delete server group port failed:", err) +} else { + fmt.Println("delete server group port success: ", result) +} +``` + +### 添加应用型BLB后端RS + +通过以下代码,在指定应用型BLB和服务器组下绑定后端服务器RS +```go +args := &appblb.CreateBlbRsArgs{ + BlbRsWriteOpArgs: appblb.BlbRsWriteOpArgs{ + // RS所属服务器组ID + SgId: serverGroupId, + // 配置后端服务器的列表及权重 + BackendServerList: []appblb.AppBackendServer{ + {InstanceId: instanceId, Weight: 30}, + }, + }, +} +err := client.CreateBlbRs(blbId, args) +if err != nil { + fmt.Println("create blbRs failed:", err) +} else { + fmt.Println("create blbRs success: ", result) +} +``` + +### 更新服务器组下挂载的RS权重 + +通过以下代码,更新指定服务器组下的RS信息 +```go +args := &appblb.UpdateBlbRsArgs{ + BlbRsWriteOpArgs: appblb.BlbRsWriteOpArgs{ + // RS所属服务器组ID + SgId: serverGroupId, + // 配置要更新的后端服务器的列表及权重 + BackendServerList: []appblb.AppBackendServer{ + {InstanceId: instanceId, Weight: 60}, + }, + }, +} +err := client.UpdateBlbRs(blbId, args) +if err != nil { + fmt.Println("update blbRs failed:", err) +} else { + fmt.Println("update blbRs success: ", result) +} +``` + +### 查询服务器组下的RS列表信息 + +通过以下代码,查询指定LoadBalancer下所有服务器组的信息 +```go +args := &appblb.DescribeBlbRsArgs{ + // RS所属服务器组ID + SgId: serverGroupId, +} +result, err := client.DescribeBlbRs(blbId, args) +if err != nil { + fmt.Println("describe blbRs failed:", err) +} else { + fmt.Println("describe blbRs success: ", result) +} +``` + +### 删除服务器组下挂载的rs + +通过以下代码,删除服务器组,通过服务器组id指定 +```go +args := &appblb.DeleteBlbRsArgs{ + // RS所属服务器组ID + SgId: serverGroupId, + // 要从RS列表中删除的实例列表 + BackendServerIdList: []string{instanceId}, +} +err := client.DeleteBlbRs(blbId, args) +if err != nil { + fmt.Println("delete blbRs failed:", err) +} else { + fmt.Println("delete blbRs success: ", result) +} +``` + +### 查询服务器组下绑定的server + +通过以下代码,查询服务器组下绑定的server +```go +result, err := client.DescribeRsMount(blbId, serverGroupId) +if err != nil { + fmt.Println("describe mount Rs list failed:", err) +} else { + fmt.Println("describe mount Rs list success: ", result) +} +``` + +### 查询服务器组下未绑定的RS + +通过以下代码,查询服务器组下未绑定的RS +```go +result, err := client.DescribeRsUnMount(blbId, serverGroupId) +if err != nil { + fmt.Println("describe unmount Rs list failed:", err) +} else { + fmt.Println("describe unmount Rs list success: ", result) +} +``` + +## 监听器管理 + +### 创建TCP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于TCP协议的应用型blb监听器,监听一个前端端口,将发往该端口的所有TCP流量,根据策略进行转发 +```go +args := &appblb.CreateAppTCPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 90, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", + // TCP设置链接超时时间,默认900秒,需要为10-4000之间的整数 + TcpSessionTimeout: 1000, +} +err := client.CreateAppTCPListener(BLBID, args) +if err != nil { + fmt.Println("create TCP Listener failed:", err) +} else { + fmt.Println("create TCP Listener success") +} +``` + +### 更新TCP监听器 + +通过以下代码,更新指定LoadBalancer下的TCP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &appblb.UpdateAppTCPListenerArgs{ + UpdateAppListenerArgs: appblb.UpdateAppListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 90, + // 更新负载均衡的算法 + Scheduler: "Hash", + // 更新tcp链接超时时间 + TcpSessionTimeout: 2000, + }, +} +err := client.UpdateAppTCPListener(BLBID, args) +if err != nil { + fmt.Println("update TCP Listener failed:", err) +} else { + fmt.Println("update TCP Listener success") +} +``` + +### 查询TCP监听器 + +通过以下代码,查询指定LoadBalancer下所有TCP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribeAppListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 90, +} +result, err := client.DescribeAppTCPListeners(BLBID, args) +if err != nil { + fmt.Println("describe TCP Listener failed:", err) +} else { + fmt.Println("describe TCP Listener success: ", result) +} +``` + +### 创建UDP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于UDP协议的应用型监听器,监听一个前端端口,将发往该端口的所有UDP流量,根据策略进行转发 +```go +args := &appblb.CreateAppUDPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", +} +err := client.CreateAppUDPListener(BLBID, args) +if err != nil { + fmt.Println("create UDP Listener failed:", err) +} else { + fmt.Println("create UDP Listener success") +} +``` + +### 更新UDP监听器 + +通过以下代码,更新指定LoadBalancer下的UDP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &appblb.UpdateAppUDPListenerArgs{ + UpdateAppListenerArgs: appblb.UpdateAppListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "Hash", + }, +} +err := client.UpdateAppUDPListener(BLBID, args) +if err != nil { + fmt.Println("update UDP Listener failed:", err) +} else { + fmt.Println("update UDP Listener success") +} +``` + +### 查询UDP监听器 + +通过以下代码,查询指定LoadBalancer下所有UDP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribeAppListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeAppUDPListeners(BLBID, args) +if err != nil { + fmt.Println("describe UDP Listener failed:", err) +} else { + fmt.Println("describe UDP Listener success: ", result) +} +``` + +### 创建HTTP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于HTTP协议的应用型监听器,监听一个前端端口,将发往该端口的所有HTTP请求,根据策略转发到后端服务器监听的后端端口上 +```go +args := &appblb.CreateAppHTTPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection + Scheduler: "RoundRobin", +} +err := client.CreateAppHTTPListener(BLBID, args) +if err != nil { + fmt.Println("create HTTP Listener failed:", err) +} else { + fmt.Println("create HTTP Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[创建HTTP监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#createapphttplistener%E5%88%9B%E5%BB%BAhttp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新HTTP监听器 + +通过以下代码,更新指定LoadBalancer下的HTTP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &appblb.UpdateAppHTTPListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 开启会话保持功能 + KeepSession: true, +} +err := client.UpdateAppHTTPListener(BLBID, args) +if err != nil { + fmt.Println("update HTTP Listener failed:", err) +} else { + fmt.Println("update HTTP Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[更新HTTP监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#updateapphttplistener%E6%9B%B4%E6%96%B0http%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询HTTP监听器 + +通过以下代码,查询指定LoadBalancer下所有HTTP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribeAppListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeAppHTTPListeners(BLBID, args) +if err != nil { + fmt.Println("describe HTTP Listener failed:", err) +} else { + fmt.Println("describe HTTP Listener success: ", result) +} +``` + +### 创建HTTPS监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于HTTPS协议的应用型监听器,监听一个前端端口,将发往该端口的所有HTTPS请求,先通过SSL卸载转换为HTTP请求后,再根据策略转发到后端服务器监听的后端端口上 +```go +args := &appblb.CreateAppHTTPSListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection + Scheduler: "RoundRobin", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.CreateAppHTTPSListener(BLBID, args) +if err != nil { + fmt.Println("create HTTPS Listener failed:", err) +} else { + fmt.Println("create HTTPS Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[创建HTTPS监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#createapphttpslistener%E5%88%9B%E5%BB%BAhttps%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新HTTPS监听器 + +通过以下代码,更新指定LoadBalancer下的HTTPS监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &appblb.UpdateAppHTTPSListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 开启会话保持功能 + KeepSession: true, + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.UpdateAppHTTPSListener(BLBID, args) +if err != nil { + fmt.Println("update HTTPS Listener failed:", err) +} else { + fmt.Println("update HTTPS Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[更新HTTPS监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#updateapphttpslistener%E6%9B%B4%E6%96%B0https%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询HTTPS监听器 + +通过以下代码,查询指定LoadBalancer下所有HTTPS监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribeAppListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeAppHTTPSListeners(BLBID, args) +if err != nil { + fmt.Println("describe HTTPS Listener failed:", err) +} else { + fmt.Println("describe HTTPS Listener success: ", result) +} +``` + +### 创建SSL监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于SSL协议的应用型blb监听器,监听一个前端端口,将发往该端口的所有SSL流量,根据策略进行转发 +```go +args := &appblb.CreateAppSSLListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.CreateAppSSLListener(BLBID, args) +if err != nil { + fmt.Println("create SSL Listener failed:", err) +} else { + fmt.Println("create SSL Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[创建SSL监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#createappssllistener%E5%88%9B%E5%BB%BAssl%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新SSL监听器 + +通过以下代码,更新指定LoadBalancer下的SSL监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &appblb.UpdateAppSSLListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.UpdateAppSSLListener(BLBID, args) +if err != nil { + fmt.Println("update SSL Listener failed:", err) +} else { + fmt.Println("update SSL Listener success") +} +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[更新SSL监听器](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#updateappssllistener%E6%9B%B4%E6%96%B0ssl%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询SSL监听器 + +通过以下代码,查询指定LoadBalancer下所有HTTPS监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribeAppListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeAppSSLListeners(BLBID, args) +if err != nil { + fmt.Println("describe SSL Listener failed:", err) +} else { + fmt.Println("describe SSL Listener success: ", result) +} +``` + +### 删除监听器 + +通过以下代码,释放指定LoadBalancer下的监听器,监听器通过监听端口来指定,支持批量释放 +```go +args := &appblb.DeleteAppListenersArgs{ + // 要删除的监听器监听的端口 + PortList: []uint16{80}, +} +err := client.DeleteAppListeners(BLBID, args) +if err != nil { + fmt.Println("delete Listeners failed:", err) +} else { + fmt.Println("delete Listeners success: ", result) +} +``` + +### 创建策略 + +通过以下代码,在指定应用型BLB监听器端口下创建策略 +```go +args := &appblb.CreatePolicysArgs{ + // 需要绑定策略的监听器监听的端口 + ListenerPort: 80, + // 需要绑定的策略,其中TCP/UDP/SSL仅支持绑定一个策略,HTTP/HTTPS支持绑定多个策略 + AppPolicyVos: []appblb.AppPolicy{ + { + // 策略描述 + Description: "test policy", + // 策略绑定的服务器组ID + AppServerGroupId: servergroupId, + // 目标端口号 + BackendPort: 80, + // 策略的优先级,有效取值范围为1-32768 + Priority: 301, + // 策略的规则列表,TCP/UDP/SSL仅支持{*:*}策略 + RuleList: []appblb.AppRule{ + { + Key: "*", + Value: "*", + }, + }, + }, + }, +} +err := client.CreatePolicys(blbId, args) +if err != nil { + fmt.Println("create policy failed:", err) +} else { + fmt.Println("create policy success") +} +``` + +> **提示:** +> - 策略中backendPort参数,即目标端口号,当listenerPort对应监听器为TCP或SSL时需要传入对应服务器组(appServerGroupId)下开放的TCP端口号;当listenerPort对应监听器为HTTP或HTTPS时需要传入对应服务器组(appServerGroupId)下开放的HTTP端口号;当listenerPort对应监听器为UDP时需要传入对应服务器组(appServerGroupId)下开放的UDP端口号 +> - 各个协议可绑定的策略的具体配置参数,可以参考BLB API 文档[创建策略](https://cloud.baidu.com/doc/BLB/s/ujwvxnyux#createpolicys%E5%88%9B%E5%BB%BA%E7%AD%96%E7%95%A5) + +### 查询对应BLB端口下策略信息 + +通过以下代码,查询指定LoadBalancer下所有服务器组的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &appblb.DescribePolicysArgs{ + // 要查询的监听器端口号 + Port: 80, +} +result, err := client.DescribePolicys(blbId, args) +if err != nil { + fmt.Println("describe policy failed:", err) +} else { + fmt.Println("describe policy success: ", result) +} +``` + +### 批量删除策略 + +通过以下代码,批量删除对应BLB端口下的策略 +```go +args := &appblb.DeletePolicysArgs{ + // 要删除策略所在监听器的端口号 + Port: 80, + // 要删除的策略ID列表 + PolicyIdList: []string{describeResult.PolicyList[0].Id}, +} +err := client.DeletePolicys(blbId, args) +if err != nil { + fmt.Println("delete policy failed:", err) +} else { + fmt.Println("delete policy success") +} +``` + +### 创建应用型IP组 + +通过以下代码,在指定应用型BLB下,创建一个IP组,用来添加IP组成员和IP组协议 +```go +args := &appblb.CreateAppIpGroupArgs{ + // 设置IP组名称 + Name: "sdkTest", + // 设置IP组描述 + Desc: "sdk test desc", +} + +// 若想直接添加IP组成员,可以设置以下参数 +args.MemberList = []appblb.AppIpGroupMember{ + { + Ip: "192.168.0.25", + Port: 80, + Weight: 50, + }, +} + +result, err := client.CreateAppIpGroup(blbId, args) +if err != nil { + fmt.Println("create ip group failed:", err) +} else { + fmt.Println("create ip group success: ", result) +} +``` + +### 更新IP组 + +通过以下代码,更新指定LoadBalancer下的IP组,所有请求参数中指定的域都会被更新,未指定的域保持不变 +```go +args := &appblb.UpdateAppIpGroupArgs{ + // 设置要更新的IP组ID + IpGroupId: ipGroupId, + // 设置新的IP组名称 + Name: "testSdk", + // 设置新的IP组描述 + Desc: "test desc", +} +err := client.UpdateAppIpGroup(blbId, args) +if err != nil { + fmt.Println("update ip group failed:", err) +} else { + fmt.Println("update ip group success: ") +} +``` + +### 查询IP组 + +通过以下代码,查询指定LoadBalancer下所有IP组的信息 +```go +args := &appblb.DescribeAppIpGroupArgs{} + +// 按blbId、name为条件进行全局查询 +args.Name = ipgroupName +args.ExactlyMatch = true + +result, err := client.DescribeAppIpGroup(blbId, args) +if err != nil { + fmt.Println("describe ip group failed:", err) +} else { + fmt.Println("describe ip group success: ", result) +} +``` + +### 删除IP组 + +通过以下代码,删除IP组,通过IP组id指定 +```go +args := &appblb.DeleteAppIpGroupArgs{ + // 要删除的IP组ID + IpGroupId: ipGroupId, +} +err := client.DeleteAppIpGroup(blbId, args) +if err != nil { + fmt.Println("delete ip group failed:", err) +} else { + fmt.Println("delete ip group success: ") +} +``` + +### 创建应用型IP组协议 + +通过以下代码,在指定应用型BLB下,创建一个IP组协议,定义IP组和后端ip之间传输协议 +```go +args := &appblb.CreateAppIpGroupBackendPolicyArgs{ + // 端口所属IP组ID + IpGroupId: ipGroupId, + // IP组协议类型 + Type: "TCP", +} + +// 可以选择设置相应健康检查协议的参数 +args.HealthCheckPort = 30 +args.HealthCheckIntervalInSecond = 10 +args.HealthCheckTimeoutInSecond = 10 +args.HealthCheckDownRetry = 3 +args.HealthCheckUpRetry = 3 +args.HealthCheckNormalStatus = "http_2xx|http_3xx" +args.HealthCheckUrlPath = "/" +args.UdpHealthCheckString = "udp" + +err := client.CreateAppIpGroupBackendPolicy(blbId, args) +if err != nil { + fmt.Println("create ip group backend policy failed:", err) +} else { + fmt.Println("create ip group backend policy success: ") +} +``` + +### 更新应用型IP组协议 + +通过以下代码,根据ID更新IP组协议 +```go +args := &appblb.UpdateAppIpGroupBackendPolicyArgs{ + // IP组ID + IpGroupId: ipGroupId, + // IP组协议的ID + Id: id, + // 设置健康检查协议参数 + HealthCheckPort: 30, + HealthCheckIntervalInSecond: 10, + HealthCheckTimeoutInSecond: 10, + HealthCheckDownRetry: 3, + HealthCheckUpRetry: 3, + HealthCheckNormalStatus = "http_2xx|http_3xx", + HealthCheckUrlPath = "/", + UdpHealthCheckString = "udp" + +} +err := client.UpdateAppIpGroupBackendPolicy(blbId, args) +if err != nil { + fmt.Println("update ip group backend policy failed:", err) +} else { + fmt.Println("update ip group backend policy success: ") +} +``` + +### 删除IP组协议 + +通过以下代码,删除IP组协议,通过IP组id指定 +```go +args := &appblb.DeleteAppIpGroupBackendPolicyArgs{ + // 端口所属IP组ID + IpGroupId: ipGroupId, + // 要删除的IP组协议ID列表 + BackendPolicyIdList: []string{backendPolicyId}, +} +err := client.DeleteAppIpGroupBackendPolicy(blbId, args) +if err != nil { + fmt.Println("delete ip group backend policy failed:", err) +} else { + fmt.Println("delete ip group backend policy success: ") +} +``` + +### 创建IP组成员 + +通过以下代码,在指定应用型BLB和IP组下创建IP组成员 +```go +args := &appblb.CreateAppIpGroupMemberArgs{ + AppIpGroupMemberWriteOpArgs: appblb.AppIpGroupMemberWriteOpArgs{ + // IP组成员所属IP组ID + IpGroupId: ipGroupId, + // 配置IP组列表IP、端口以及权重 + MemberList: []appblb.AppIpGroupMember{ + {Ip: "192.168.0.25", Port: 80, Weight: 30}, + }, + }, +} +err := client.CreateAppIpGroupMember(blbId, args) +if err != nil { + fmt.Println("create ip group member failed:", err) +} else { + fmt.Println("create ip group member success: ") +} +``` + +### 更新IP组成员 + +通过以下代码,更新指定IP组下的IP组成员信息 +```go +args := &appblb.UpdateAppIpGroupMemberArgs{ + AppIpGroupMemberWriteOpArgs: appblb.AppIpGroupMemberWriteOpArgs{ + // IP组成员所属IP组ID + IpGroupId: ipGroupId, + // IP组列表IP、端口以及权重 + MemberList: []appblb.AppIpGroupMember{ + {MemberId: memberId, Port: 80, Weight: 30}, + }, + }, +} +err := client.UpdateAppIpGroupMember(blbId, args) +if err != nil { + fmt.Println("update ip group member failed:", err) +} else { + fmt.Println("update ip group member success: ") +} +``` + +### 查询IP组成员列表信息 + +通过以下代码,查询指定LoadBalancer下所有IP组的信息 +```go +args := &appblb.DescribeAppIpGroupMemberArgs{ + // 成员所属IP组ID + IpGroupId: ipGroupId, +} +result, err := client.DescribeAppIpGroupMember(blbId, args) +if err != nil { + fmt.Println("describe ip group member failed:", err) +} else { + fmt.Println("describe ip group member success: ", result) +} +``` + +### 删除IP组成员 + +通过以下代码,删除IP组成员,通过IP组id指定 +```go +args := &appblb.DeleteAppIpGroupMemberArgs{ + // 成员所属IP组ID + IpGroupId: ipGroupId, + // 要删除的IP组成员id + MemberIdList: []string{memberId}, +} +err := client.DeleteAppIpGroupMember(blbId, args) +if err != nil { + fmt.Println("delete ip group member failed:", err) +} else { + fmt.Println("delete ip group member success: ") +} +``` + +### 创建策略(策略绑定IP组id) + +通过以下代码,在指定应用型BLB监听器端口下创建策略 +```go +args := &appblb.CreatePolicysArgs{ + // 需要绑定策略的监听器监听的端口 + ListenerPort: 80, + // 需要绑定的策略,其中TCP/UDP/SSL仅支持绑定一个策略,HTTP/HTTPS支持绑定多个策略 + AppPolicyVos: []appblb.AppPolicy{ + { + // 策略描述 + Description: "test policy", + // 策略绑定的服务器组ID + AppIpGroupId: ipGroupId, + // 策略的优先级,有效取值范围为1-32768 + Priority: 301, + // 策略的规则列表,TCP/UDP/SSL仅支持{*:*}策略 + RuleList: []appblb.AppRule{ + { + Key: "*", + Value: "*", + }, + }, + }, + }, +} +err := client.CreatePolicys(blbId, args) +if err != nil { + fmt.Println("create policy failed:", err) +} else { + fmt.Println("create policy success") +} +``` + + +# 错误处理 + +GO语言以error类型标识错误,BLB支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BLB服务返回的错误 + +用户使用SDK调用BLB相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// blbClient 为已创建的BLB Client对象 +blbDetail, err := blbClient.DescribeLoadBalancerDetail(blbId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get appblb detail success: ", blbDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BLB发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BLB服务端出现异常时,BLB服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BLB错误返回](https://cloud.baidu.com/doc/BLB/s/Djwvxnzw6) + +# 版本变更记录 + +## v0.9.1 [2019-09-26] + +首次发布: + + - 创建、查看、列表、更新、删除应用型BLB实例 + - 创建、列表、更新、删除服务器组 + - 创建、更新、删除服务器组端口 + - 创建、列表、更新、删除服务器组后端RS,并支持查询已绑定和未绑定的服务器 + - 创建、查看、更新、删除监听器端口,支持TCP/UDP/HTTP/HTTPS/SSL协议 + - 创建、查看、删除监听器相关策略 + +再次发布: + - 创建、列表、更新、删除IP组 + - 创建、更新、删除IP组协议 + - 创建、列表、更新、删除IP组成员 \ No newline at end of file diff --git a/bce-sdk-go/doc/AS.md b/bce-sdk-go/doc/AS.md new file mode 100644 index 0000000..cd4328b --- /dev/null +++ b/bce-sdk-go/doc/AS.md @@ -0,0 +1,321 @@ +# AS服务 + +# 概述 + +本文档主要介绍AS GO SDK的使用。在使用本文档前,您需要先了解 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[AS域名](https://cloud.baidu.com/doc/AS/s/fk3imcz57)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云AS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问AS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建AS Client + +AS Client是AS控制面服务的客户端,为开发者与AS控制面服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建AS Client + +通过AK/SK方式访问AS,用户可以参考如下代码新建一个AS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/as" //导入AS服务模块 +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个ASClient + asClient, err := as.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/as/index.html)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为AS的控制面服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`as.bj.baidubce.com`。 + +### 使用STS创建AS Client + +**申请STS token** + +AS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问AS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建AS Client** + +申请好STS后,可将STS Token配置到AS Client中,从而实现通过STS Token创建AS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建AS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/as" //导入AS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建AS控制面服务的Client对象,Endpoint使用默认值 + asClient, err := as.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create as client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + asClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置AS Client时,无论对应AS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问AS + +AS支持HTTPS传输协议,您可以通过在创建AS Client对象时指定的Endpoint中指明HTTPS的方式,在AS GO SDK中使用HTTPS访问AS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/as" + +ENDPOINT := "https://as.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +asClient, _ := as.NewClient(AK, SK, ENDPOINT) +``` + +## 配置AS Client + +如果用户需要配置AS Client的一些细节的参数,可以在创建AS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问AS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/as" + +//创建AS Client对象 +AK, SK := , +ENDPOINT := "as.bj.baidubce.com +client, _ := as.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/AS" + +AK, SK := , +ENDPOINT := "as.bj.baidubce.com" +client, _ := as.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/AS" + +AK, SK := , +ENDPOINT := "as.bj.baidubce.com" +client, _ := as.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问AS时,创建的AS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建AS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +百度智能云弹性伸缩(Auto Scaling)是自动化扩缩容用户云资源的管理服务,当您业务所需的云资源用量经常性变化时,弹性伸缩会是您使用云资源的理想方式。 + +## 查询伸缩组列表接口 + +### 接口描述 +可查询所有伸缩组的详细信息。 + +### 请求示例 + +```go +req := &as.ListAsGroupRequest{ + // 可选,伸缩组名称 + GroupName: "as-Group-Name", + // 可选,批量获取列表的查询的起始位置,是一个由系统生成的字符串 + Marker: "marker", + // 可选,每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 + MaxKeys: 100, +} +resp, err := asClient.ListAsGroup(req) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考AS API 文档[查询伸缩组列表](https://cloud.baidu.com/doc/AS/s/hk3imj0oq#%E6%9F%A5%E8%AF%A2%E4%BC%B8%E7%BC%A9%E7%BB%84%E5%88%97%E8%A1%A8) + +## 查询伸缩组详情接口 + +### 接口描述 +可查询单个伸缩组的详细信息。 + +### 请求示例 +```go +req := &as.GetAsGroupRequest{ + // 必填,待查询的伸缩组ID + GroupId: "asg-wqksXo95", +} +resp, err := asClient.GetAsGroup(req) +``` + +## 查询伸缩组下节点列表 + +### 接口描述 +可查询指定伸缩组下节点的详细信息。 + +### 请求示例 +```go +req := &as.ListAsNodeRequest{ + // 必填,伸缩组ID + GroupId: "asg-wqksXo95", + // 可选,批量获取列表的查询的起始位置,是一个由系统生成的字符串 + Marker: "marker", + // 可选,每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 + MaxKeys: 100, +} +resp, err := asClient.ListAsNode(req) +``` + +## 伸缩组扩容 + +### 接口描述 +在指定伸缩组下添加节点。 + +### 请求示例 +```go +req := &as.IncreaseAsGroupRequest{ + // 必填,伸缩组ID + GroupId: "asg-Hhm2ucIK", + // 必填,扩容可指定可用区(扩容时会与伸缩组配置的可用区取交集) + Zone: []string{"zoneB"}, + // 扩容节点数量 + NodeCount: 1, + // 扩容时的可用区选择策略 + // Priority - 以单独可用区进行创建 + // Balanced - 在选定可用区中均衡创建 + ExpansionStrategy:"Priority" +} +err := asClient.IncreaseAsGroup(req) +``` + +## 伸缩组缩容 + +### 接口描述 +用于伸缩组下节点的缩容。 + +### 请求示例 +```go +req := &as.DecreaseAsGroupRequest{ + // 必填,伸缩组ID + GroupId: "asg-Hhm2ucIK", + // 必填,手动缩容指定的实例短Id + Nodes: []string{"i-z0PXqFD3"}, +} +err := asClient.DecreaseAsGroup(req) +``` + + +## 客户端异常 + +客户端异常表示客户端尝试向AS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当AS服务端出现异常时,AS服务端会返回给用户相应的错误信息,以便定位问题 + diff --git a/bce-sdk-go/doc/BBC.md b/bce-sdk-go/doc/BBC.md new file mode 100644 index 0000000..38f2f7e --- /dev/null +++ b/bce-sdk-go/doc/BBC.md @@ -0,0 +1,1529 @@ +# BBC服务 + +# 概述 + +本文档主要介绍BBC GO SDK的使用。在使用本文档前,您需要先了解BBC的一些基本知识。若您还不了解BBC,可以 +参考[产品描述](https://cloud.baidu.com/doc/BBC/s/ojwvxu4di)和 +[操作指南](https://cloud.baidu.com/doc/BBC/s/fjwvxu86k)。 + +# 初始化 + +## 确认Endpoint +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于 +[BBC访问域名](https://cloud.baidu.com/doc/BBC/s/3jwvxu9iz#%E6%9C%8D%E5%8A%A1%E5%9F%9F%E5%90%8D)的部分, +理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +由于BBC维修任务并不按照区域区分,在访问维修任务相关接口时,Endpoint统一使用北京区域的BBC访问域名。 + +## 获取密钥 + +要使用百度云BBC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。 +AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BBC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BBC Client + +BBC Client是BBC服务的客户端,为开发者与BBC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BBC Client + +通过AK/SK方式访问BBC,用户可以参考如下代码新建一个Bbc Client: + +```go +import "github.com/baidubce/bce-sdk-go/services/bbc" + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BBClient + bbcClient, err := bbc.NewClient(AK, SK, ENDPOINT) +} +``` +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Access Key Secret”, +获取方式请参考[获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb)。 +第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BBC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bbc.bj.baidubce.com`。 + +## 配置HTTPS协议访问BBC + +BBC支持HTTPS传输协议,您可以通过在创建BBC Client对象时指定的Endpoint中指明HTTPS的方式,在BBC GO SDK中使用HTTPS访问BBC服务: + +```go +import "github.com/baidubce/bce-sdk-go/services/bbc" + +ENDPOINT := "https://bbc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +bbcClient, _ := bbc.NewClient(AK, SK, ENDPOINT) +``` +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +import "github.com/baidubce/bce-sdk-go/services/bbc" + +AK, SK := , +ENDPOINT := "bbc.bj.baidubce.com" +client, _ := bbc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bbc" + +AK, SK := , +ENDPOINT := "bbc.bj.baidubce.com" +client, _ := bbc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BBC时,创建的BBC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者. 后者为使用STS鉴权时使用 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +# 主要接口 + +云服务器BCC(Baidu Cloud Compute)是处理能力可弹性伸缩的计算服务。 +管理方式比物理服务器更简单高效,可根据您的业务需要创建、释放任意多台云服务器实例,提升运维效率。 + +## 实例 + +### 创建实例 + +使用以下代码可以创建一个物理机实例: +```go +createInstanceArgs := &CreateInstanceArgs{ + // 输入你选择的flavor(套餐)ID,通过SDK获取可用flavor id的方法详见套餐章节 + FlavorId: "your-choose-flavor-id", + // 输入你要创建instance使用的镜像ID,通过SDK获取可用镜像ID的方法详见镜像章节 + ImageId: "your-choose-image-id", + // 输入你要创建instance使用的raid ID,通过SDK获取可用raid id的方法详见套餐章节 + RaidId: "your-choose-raid-id", + // 输入待创建物理磁盘的大小,单位为GB,缺省为20 + RootDiskSizeInGb: 20, + // 批量创建(购买)的虚拟机实例个数,必须为大于0的整数,可选参数,缺省为1 + PurchaseCount: 1, + // 可用区,格式为:国家-区域-可用区,如'中国-北京-可用区A'就是'cn-bj-a' + ZoneName: "cn-bj-a", + // 指定子网 ID,必填参数 + SubnetId: "your-choose-subnet-id", + // 设置内网IP(只有智能卡套餐支持自定义内网IP) + InternalIps []string internalIps + // 指定安全组id,可选参数 + SecurityGroupId: "your-choose-security-group-id" + // 设置创建BCC使用的企业安全组,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupId: "enterpriseSecurityGroupId" + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken: "random-uuid", + //创建实例支持幂等的token,永久有效 + RequestToken string "requestToken" + // 选择付费方式 + Billing: Billing{ + PaymentTiming: PaymentTimingPostPaid, + Reservation: Reservation{ + Length: 1, + TimeUnit: "Month", + }, + }, + // 指定使用的部署集id,可选参数,通过SDK获取可用部署集id的方法详见部署集章节 + DeploySetId: "your-choose-raid-id", + // 设置实例管理员密码(8-16位字符,英文,数字和符号必须同时存在,符号仅限!@#$%^*()) + AdminPass: "your-admin-pass", + // 实例名称 + Name: "your-choose-instance-name", + // 实例主机名,可选参数,若不选则主机名和实例名称保持一致(实例名称不包含中文名时) + // 仅支持小写字母、数字以及- . 特殊字符,不可连续使用特殊符号,不支持特殊符号开头或结尾,长度2-64 + Hostname: "your-choose-instance-hostname", + // 支持幂等的token + RequestToken: "requestToken", + // 指定是否开启numa true为开启,false为关闭 + EnableNuma: true, + // 指定系统盘文件系统,当前合法值:xfs,ext4 + RootPartitionType: "your-choose-rootPartitionType", + // 指定数据盘文件系统,当前合法值:xfs,ext4 + DataPartitionType: "your-choose-dataPartitionType", + // 指定实例绑定标签 + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagVaule", + }, + }, +} +if res, err := bbcClient.CreateInstance(createInstanceArgs); err != nil { + fmt.Println("create instance failed: ", err) +} else { + fmt.Println("create instance success, instanceId: ", res.InstanceIds[0]) +} +``` + +> **注意:** +>付费方式(PaymentTiming)可选: +>- 后付费: PaymentTimingPostPaid +>- 预付费: PaymentTimingPrePaid + +### 查询实例列表 +使用以下代码查询所有BBC实例的列表及详情信息: +```go +listArgs := &ListInstancesArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker: "your-marker", + // 设置返回数据大小,缺省为1000 + MaxKeys: 100, + // 通过internal Ip过滤BBC列表 + InternalIp: "your-choose-internal-ip", +} +if res, err := bbcClient.ListInstances(listArgs); err != nil { + fmt.Println("list instances failed: ", err) +} else { + fmt.Println("list instances success, result: ", res) +} +``` +### 查询实例详情 +使用以下代码可以查询指定BBC实例的详细信息: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +//设置是否展示部署集字段 +isDeploySet := "your-isDeploySet" +if res, err := bbcClient.GetInstanceDetail(instanceId); err != nil { + fmt.Println("get instance detail failed: ", err) +} else { + fmt.Println("get instance detail success, result: ", res) +} +``` +### 查询带部署集相关字段实例详情 +查询带部署集相关字段实例详情: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +//设置是否展示部署集字段true or false +isDeploySet := "your-isDeploySet" + +if res, err := bccClient.GetInstanceDetailWithDeploySet(instanceId,isDeploySet); err != nil { + fmt.Println("get instance detail failed: ", err) +} else { + fmt.Println("get instance detail success, result: ", res) +} +``` + +### 启动实例 +使用以下代码可以启动指定BBC实例,实例状态必须为 Stopped,调用此接口才可以成功返回,否则提示409错误: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.StartInstance(instanceId); err != nil { + fmt.Println("start instance failed: ", err) +} else { + fmt.Println("start instance success.") +} +``` + +### 停止实例 +使用以下代码可以停止指定BBC实例,只有状态为 Running 的实例才可以进行此操作,否则提示 409 错误: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 是否强制停止实例,为True代表强制停止 +forceStop := true +if err := bbcClient.StopInstance(instanceId, forceStop); err != nil { + fmt.Println("stop instance failed: ", err) +} else { + fmt.Println("stop instance success.") +} +``` + +### 重启实例 +使用以下代码可以重启指定BBC实例,只有状态为 Running 的实例才可以进行此操作,否则提示 409 错误: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 是否强制停止实例,为True代表强制停止 +forceStop := true +if err := bbcClient.RebootInstance(instanceId, forceStop); err != nil { + fmt.Println("reboot instance failed: ", err) +} else { + fmt.Println("reboot instance success.") +} +``` + +### 修改实例名称 +使用以下代码可以修改指定BBC实例的名称: +```go +modifyInstanceNameArgs := &ModifyInstanceNameArgs{ + Name: "new_bbc_name", +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.ModifyInstanceName(instanceId, modifyInstanceNameArgs); err != nil { + fmt.Println("modify instance name failed: ", err) +} else { + fmt.Println("modify instance name success.") +} +``` + +### 修改实例描述 +使用以下代码可以修改指定BBC实例的描述: +```go +modifyInstanceDescArgs := &ModifyInstanceDescArgs{ + Description: "new_bbc_description", +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.ModifyInstanceDesc(instanceId, modifyInstanceDescArgs); err != nil { + fmt.Println("modify instance desc failed: ", err) +} else { + fmt.Println("modify instance desc success.") +} +``` + +### 实例变更子网 + +如下代码可以变更实例的子网 +```go +instanceChangeVpcArgs := &api.InstanceChangeSubnetArgs{ + InstanceId: instanceId, + SubnetId: subnetId, + InternalIp: internalIp, + Reboot: false, +} +err := bbcClient.InstanceChangeSubnet(args) +if err != nil { + fmt.Println("change instance subnet failed:", err) +} else { + fmt.Println("change instance subnet success") +} +``` + +> **提示:** +> - 变更子网后默认自动重启,用户选择是否执行该操作。 +> - 变更子网的范围目前仅支持在同AZ下变更子网,不支持跨AZ或跨VPC变更子网。 + +### 修改实例VPC +使用以下代码可以修改指定BBC实例所在的VPC: +```go +instanceChangeVpcArgs := &InstanceChangeVpcArgs{ + InstanceId: "your-choose-instance-id", + SubnetId: "new_subnet_id_in_vpc", + InternalIp: internalIp, + Reboot: true, +} +if err := bbcClient.InstanceChangeVpc(instanceChangeVpcArgs); err != nil { + fmt.Println("modify instance vpc failed: ", err) +} else { + fmt.Println("modify instance vpc success.") +} +``` + +> **提示:** +> - 变更VPC后默认自动重启,用户选择是否执行该操作。 +> - 变更VPC后仅保留主网卡主IP(在新子网中自动分配),实例上的辅助IP和安全组等信息不跟随主体迁移。 + + +### 重装实例 +使用以下代码可以使用镜像重建指定BBC实例: +```go +rebuildArgs := &RebuildInstanceArgs{ + // 设置使用的镜像id + ImageId: "your-choose-image-id", + // 设置管理员密码 + AdminPass: "your-new-admin-pass", + // 是否保留数据。当该值为true时,raidId和sysRootSize字段不生效 + IsPreserveData: false, + // 此参数在isPreserveData为false时为必填,在isPreserveData为true时不生效 + RaidId: "your_raid_id", + // 系统盘根分区大小,默认为20G,取值范围为20-100。此参数在isPreserveData为true时不生效 + SysRootSize: 20, + // 指定系统盘文件系统,当前合法值:xfs,ext4 + RootPartitionType: "your-choose-rootPartitionType", + // 指定数据盘文件系统,当前合法值:xfs,ext4 + DataPartitionType: "your-choose-dataPartitionType", +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 设置是否保留数据 +isPreserveData = false +if err := bbcClient.RebuildInstance(instanceId, isPreserveData, rebuildArgs); err != nil { + fmt.Println("rebuild instance failed: ", err) +} else { + fmt.Println("rebuild instance success.") +} +``` +> **注意:** +>IsPreserveData表示是否保留数据: +>- 当IsPreserveData设置为 false 时,RaidId 和 SysRootSize 是必填参数 +>- 当IsPreserveData设置为 true 时,RaidId、SysRootSize 和 DataPartitionType 参数不生效 + +### 批量重装实例 +使用以下代码可以使用镜像批量重建指定BBC实例: +```go +rebuildArgs := &BatchRebuildInstanceArgs{ + // 设置重装BBC的实例ID + InstanceIds: []string{"your-choose-instance-id"}, + // 设置使用的镜像id + ImageId: "your-choose-image-id", + // 设置管理员密码 + AdminPass: "your-new-admin-pass", + // 是否保留数据。当该值为true时,raidId和sysRootSize字段不生效 + IsPreserveData: false, + // 此参数在isPreserveData为false时为必填,在isPreserveData为true时不生效 + RaidId: "your_raid_id", + // 系统盘根分区大小,默认为20G,取值范围为20-100。此参数在isPreserveData为true时不生效 + SysRootSize: 20, + // 指定系统盘文件系统,当前合法值:xfs,ext4 + RootPartitionType: "your-choose-rootPartitionType", + // 指定数据盘文件系统,当前合法值:xfs,ext4 + DataPartitionType: "your-choose-dataPartitionType", +} +if res, err := bbcClient.BatchRebuildInstance(rebuildArgs); err != nil { + fmt.Println("batch rebuild instance failed: ", err) +} else { + fmt.Println("batch rebuild instance success, result: %s", res) +} +``` +> **注意:** +>IsPreserveData表示是否保留数据: +>- 当IsPreserveData设置为 false 时,RaidId 和 SysRootSize 是必填参数 +>- 当IsPreserveData设置为 true 时,RaidId、SysRootSize 和 DataPartitionType 参数不生效 + +### 实例续费 + +对BBC实例的续费操作,可以延长过期时长,或者从回收站恢复预付费BBC实例,以下代码可以对实例进行续费 +```go +args := &PurchaseReservedArgs { + Billing: Billing{ + PaymentTiming: PaymentTimingPrePaid, + Reservation: Reservation{ + Length: 1, + TimeUnit: "Month", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bbcClient.InstancePurchaseReserved(instnaceId, args); err != nil { + fmt.Println("Renew Instance failed: ", err) +} else { + fmt.Println("Renew Instance success.") +} +``` + +> **提示:** +> - 续费时若实例已欠费停机,续费成功后有个BBC实例启动的过程。 +> - 该接口是一个异步接口。 + +### 释放实例 +对于后付费Postpaid以及预付费Prepaid过期的BBC实例,可以使用以下代码将其释放: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.DeleteInstance(instanceId); err != nil { + fmt.Println("release instance failed: ", err) +} else { + fmt.Println("release instance success.") +} +``` + +### 释放实例(包含预付费实例) +不区分后付费还是预付费实例,释放bbc以及关联的eip,可以使用以下代码将其释放: +```go + args := &DeleteInstanceIngorePaymentArgs{ + InstanceId: "instanceid", + RelatedReleaseFlag: relatedReleaseFlag, //true or false + //设置是否立即释放,默认false,实例进入回收站,关联eip资源解绑;为true时,实例和设置了关联释放的eip资源,一起立即释放 + DeleteImmediate: false, + } + if res, err := BBC_CLIENT.DeleteInstanceIngorePayment(args); err != nil { + fmt.Println("delete instance failed: ", err) + } else { + fmt.Printf("delelte instance success, result: %s", res) + } +``` + +### 批量释放或进入回收站实例(不包含预付费实例) +释放或者进入回收站bbc实例,可以使用以下代码将其释放: +```go + instanceIds := []string{"instanceId"} + queryArgs := &DeleteInstanceArgs{ + BbcRecycleFlag: bbcRecycleFlag, // true or false, true recycled the bbc + InstanceIds: instanceIds, + } + if err := BBC_CLIENT.DeleteInstances(queryArgs); err != nil { + fmt.Println("delete instances failed: ", err) + } else { + fmt.Println("delete instances success") + } +``` + +### 查询回收站实例列表 +使用以下代码查询所有BBC回收站实例的列表及详情信息 +```go + queryArgs := &ListRecycledInstancesArgs{ + Marker: "your marker", + PaymentTiming: "your paymentTiming", + RecycleBegin: "RecycleBegin", // recycled begin time ,eg: 2020-11-23T17:18:24Z + RecycleEnd: "RecycleEnd", + MaxKeys: 10, + InstanceId: "InstanceId", + Name: "InstanceName", + } + if res, err := BBC_CLIENT.ListRecycledInstances(queryArgs); err != nil { + fmt.Println("list recycled bbc failed: ", err) + } else { + fmt.Println("list recycled bbc success, result: ", res) + } +``` + +### 释放回收站实例 +回收站实例7天后自动释放,清理回收站资源,可以使用以下代码将其释放: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.DeleteRecycledInstance(instanceId); err != nil { + fmt.Println("release instance failed: ", err) +} else { + fmt.Println("release instance success.") +} +``` + +### 后付费回收站bbc实例恢复计费 +使用以下代码可以恢复后付费回收站bbc实例,再次使用。 备注: 预付费回收站实例使用预付费续费接口即可 +```go + instanceIds := []string{"instanceId"} + queryArgs := &RecoveryInstancesArgs{ + InstanceIds: instanceIds, + } + if err := BBC_CLIENT.RecoveryInstances(queryArgs); err != nil { + fmt.Println("recovery instance failed: ", err) + } else { + fmt.Println("recovery instance success") + } +``` + +### 修改实例密码 +使用以下代码可以修改指定BBC实例的管理员密码: +```go +modifyInstancePasswordArgs := &ModifyInstancePasswordArgs{ + AdminPass: "your_new_password", +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bbcClient.ModifyInstancePassword(instanceId, modifyInstancePasswordArgs); err != nil { + fmt.Println("modify instance password failed: ", err) +} else { + fmt.Println("modify instance password success.") +} +``` + +### 查询实例VNC地址 + +如下代码可以查询实例的VNC地址 +```go +result, err := client.GetInstanceVNC(instanceId) +if err != nil { + fmt.Println("get instance VNC url failed:", err) +} else { + fmt.Println("get instance VNC url success: ", result) +} +``` + +> **提示:** +> - VNC地址一次使用后即失效 +> - URL地址有效期为10分钟 + +> **注意:** +>BBC 实例密码要求: +>- 8-16位字符,英文,数字和符号必须同时存在,符号仅限!@#$%^*() + +### 查询实例VPC/Subnet信息 +使用以下代码可以通过BBC实例id查询VPC/Subnet信息: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +getVpcSubnetArgs := &GetVpcSubnetArgs{ + BbcIds: []string{instanceId}, +} +if res, err := bbcClient.GetVpcSubnet(getVpcSubnetArgs); err != nil { + fmt.Println("get vpc subnet failed: ", err) +} else { + fmt.Println("get vpc subnet success. res: ", res) +} +``` + +## 智能卡实例绑定安全组 + +使用以下代码可以将智能卡实例绑定安全组: + +```go +args := &BindSecurityGroupsArgs{ + InstanceIds: ["instanceId"], + SecurityGroups: ["SecurityGroup"], +} +if err := bbcClient.BindSecurityGroups(args); err != nil { + fmt.Println("BindSecurityGroups failed: ", err) +} else { + fmt.Println("BindSecurityGroups success.") +} +``` +## 智能卡实例解绑安全组 + +使用以下代码可以将智能卡实例解绑安全组: + +```go +args := &UnBindSecurityGroupsArgs{ + InstanceId: "", + SecurityGroupId: "", +} +if err := bbcClient.UnBindSecurityGroups(args); err != nil { + fmt.Println("UnBindSecurityGroups failed: ", err) +} else { + fmt.Println("UnBindSecurityGroups success.") +} +``` + +## 批量增加辅助IP +使用以下代码对实例增加辅助IP: + +```go +batchAddIpArgs := &BatchAddIpArgs{ + // 实例ID + InstanceId string "instanceId" + // 辅助IP,和SecondaryPrivateIpAddressCount不可同时使用 + PrivateIps []string "privateIps" + // 自动分配IP数量,和PrivateIps不可同时使用 + SecondaryPrivateIpAddressCount int 1 + // 启用后主网卡新增ip地址为ipv6,请确认子网已分配IPv6网段,默认false + AllocateMultiIpv6Addr false + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClietnToken string "clientToken" +} + +if res, err := bbcClient.BatchAddIP(batchAddIpArgs); err != nil { + fmt.Println("BatchAddIP failed: ", err) +} else { + fmt.Println("BatchAddIP success, result: ", res) +} +``` + +### 批量删除指定实例的ip +```go +privateIps := []string{"192.168.1.25"} +instanceId := "your-choose-instance-id" +// 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 +clientToken := "clientToken" +batchDelIpArgs := &BatchDelIpArgs{ + InstanceId: instanceId, + PrivateIps: privateIps, + ClientToken: clientToken, +} +if err := bbcClient.BatchDelIP(batchDelIpArgs); err != nil { + fmt.Println("delete ips failed: ", err) +} else { + fmt.Println("delete ips success.") +} +``` + +### 开通自动续费(包含关联产品) +自动续费仅限预付费产品 + +```go +bbcCreateAutoRenewArgs := &BbcCreateAutoRenewArgs{ + // 实例ID + InstanceId: instanceId, + // 续费单位,month,year + RenewTimeUnit: "month", + // 续费时长,单位:month,支持1, 2, 3, 4, 5, 6, 7, 8, 9;单位:year,支持1, 2, 3 + RenewTime: 1, +} + +if err := bbcClient.BatchCreateAutoRenewRules(bbcCreateAutoRenewArgs); err != nil { + fmt.Println("BatchCreateAutoRenewRules failed: ", err) +} else { + fmt.Println("BatchCreateAutoRenewRules success.") +} +``` + +### 关闭自动续费(包含关联产品) +自动续费仅限预付费产品 + +```go +bbcDeleteAutoRenewArgs := &BbcDeleteAutoRenewArgs{ + // 实例ID + InstanceId: instanceId, +} + +if err := bbcClient.BatchDeleteAutoRenewRules(bbcCreateAutoRenewArgs); err != nil { + fmt.Println("BatchDeleteAutoRenewRules failed: ", err) +} else { + fmt.Println("BatchDeleteAutoRenewRules success.") +} +``` + +### 查询实例绑定的弹性网卡列表 +使用以下代码可以查询实例绑定的弹性网卡列表 +```GO +// 设置你要操作的instanceId +instanceId := "" +if res, err := bbcClient.GetInstanceEni(instanceId); err != nil { + fmt.Println("Get specific instance eni failed: ", err) +} else { + fmt.Println("Get specific instance eni success, result: ", res) +} +``` + +## 标签 +### 实例解绑标签 +通过以下代码解绑实例已有的标签 +```go +unbindTagsArgs := &UnbindTagsArgs{ + // 设置您要解绑的标签 + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := BBC_CLIENT.UnbindTags(instanceId, unbindTagsArgs); err != nil { + fmt.Println("unbind instance tags failed: ", err) +} else { + fmt.Println("unbind instance tags success.") +} +``` + +## 套餐 +### 查询套餐列表 +使用以下代码查询所有BBC套餐的列表及详情信息 +```go +if res, err := bbcClient.ListFlavors(); err != nil { + fmt.Println("List flavors failed: ", err) +} else { + fmt.Println("List flavors success, result: ", res) +} +``` + +### 查询套餐详情 +使用以下代码可以查询指定套餐的详细信息 + +```go +// 设置你要操作的flavorId +flavorId := "your-choose-flavor-id" +if res, err := bbcClient.GetFlavorDetail(testFlavorId); err != nil { + fmt.Println("Get flavor failed: ", err) +} else { + fmt.Println("Get flavor success, result: ", res) +} +``` +### 查询RAID详情 +使用以下代码可以查询指定套餐的RAID方式及磁盘大小 + +```go +// 设置你要操作的flavorId +flavorId := "your-choose-flavor-id" +if res, err := bbcClient.GetFlavorRaid(testFlavorId); err != nil { + fmt.Println("Get raid failed: ", err) +} else { + fmt.Println("Get raid success, result: ", res) +} +``` + +### 查询套餐支持的可用区 +使用以下代码可以查询指定套餐支持的可用区列表 + +```go +// 设置你要操作的flavorId +flavorId := "" +queryArgs := &ListFlavorZonesArgs{ + FlavorId: flavorId, +} +if res, err := bbcClient.ListFlavorZones(queryArgs); err != nil { + fmt.Println("Get flavor zoneName failed: ", err) +} else { + fmt.Println("Get flavor zoneName success, result: ", res) +} +``` + +### 查询可用区支持的套餐 +使用以下代码可以查询指定可用区支持的套餐列表 + +```go +// 设置你要操作的zoneName,可以使用cn-bj-a 等 +zoneName := "" +queryArgs := &ListZoneFlavorsArgs{ + ZoneName: zoneName, +} +if res, err := bbcClient.ListZoneFlavors(queryArgs); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) +} else { + fmt.Println("Get the specific zone flavor success, result: ", res) +} + +``` + +## 镜像 +### 通过实例创建自定义镜像 +- 用于创建自定义镜像,默认每个账号配额20个,创建后的镜像可用于创建实例 +- 只有 Running 或 Stopped 状态的实例才可以执行成功 +使用以下代码可以从指定的实例创建镜像 +```go +// 用于创建镜像的实例ID +instanceId := "i-3EavdPl8" +// 设置创建镜像的名称 +imageName := "testCreateImage" +queryArgs := &CreateImageArgs{ + ImageName: testImageName, + InstanceId: testInstanceId, +} +if res, err := bbcClient.CreateImageFromInstanceId(queryArgs); err != nil { + fmt.Println("Create image failed: ", err) +} else { + fmt.Println("Create image success, result: ", res) +} +``` +### 查询镜像列表 +- 用于查询用户所有的镜像信息 +- 查询的镜像信息中包括系统镜像、自定义镜像和服务集成镜像 +- 支持按 imageType 来过滤查询,此参数非必需,未设置时默认为 All,即查询所有类型的镜像 +使用以下代码可以查询镜像列表 +```go +// 指定要查询何种类型的镜像 +// All(所有) +// System(系统镜像/公共镜像) +// Custom(自定义镜像) +// Integration(服务集成镜像) +// Sharing(共享镜像) +imageType := "All" +// 批量获取列表的查询的起始位置 +marker := "your-marker" +// 每页包含的最大数量 +maxKeys := 100 +queryArgs := &ListImageArgs{ + Marker: marker, + MaxKeys: maxKeys, + ImageType: imageType, +} +if res, err := bbcClient.ListImage(queryArgs); err != nil { + fmt.Println("List image failed: ", err) +} else { + fmt.Println("List image success, result: ", res) +} +``` +### 查询镜像详情 +- 用于根据指定镜像ID查询单个镜像的详细信息 +使用以下代码可以查询镜像详情 +```go +// 待查询镜像ID +image_id :="your-choose-image-id" +if res, err := bbcClient.GetImageDetail(testImageId); err != nil { + fmt.Println("Get image failed: ", err) +} else { + fmt.Println("Get image success, result: ", res.Result) +} +``` +### 删除自定义镜像 +- 用于删除用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能删除 +- 镜像删除后无法恢复,不能再用于创建、重置实例 +使用以下代码可以删除指定镜像 + +```go +// 待删除镜像ID +imageId := "your-choose-image-id" +if err := bbcClient.DeleteImage(testImageId); err != nil { + fmt.Println("Delete image failed: ", err) +} +``` + +### 获取套餐的公共镜像 +- 如果不传套餐id,获取所有套餐的镜像列表 +使用以下代码可以获取指定套餐可选的公共镜像列表 + +```go +// 物理机套餐Id列表 +flavorIds := []string{""} +queryArgs := &GetFlavorImageArgs{ + FlavorIds: flavorIds, +} +if res, err := bbcClient.GetCommonImage(queryArgs); err != nil { + fmt.Println("Get specific flavor common image failed: ", err) +} else { + fmt.Println("Get specific flavor common image success, result: ", res) +} +``` + +### 获取套餐的自定义镜像 +- 如果不传套餐id,获取所有套餐的镜像列表 +使用以下代码可以获取指定套餐可选的自定义镜像列表 + +```go +// 物理机套餐Id列表 +flavorIds := []string{"BBC-S3-02"} +queryArgs := &GetFlavorImageArgs{ + FlavorIds: flavorIds, +} +if res, err := bbcClient.GetCustomImage(queryArgs); err != nil { + fmt.Println("Get specific flavor common image failed: ", err) +} else { + fmt.Println("Get specific flavor common image success, result: ", res) +} +``` + +### 共享自定义镜像 + +- 该接口用于共享用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能共享。 +- imageId 所指定的镜像不存在,提示404错误。 +- 镜像共享后,被共享的用户可以使用此镜像创建、重置实例。 +- 请求参数中的account和accountId均为可选参数,但不能全部为空,当两个参数同时出现时,服务端会自动去重。 + +```go +// 待共享的用户id +accountId := "your-accountId" +//待共享的用户名 +account := "your-account" +// 待共享的镜像ID +imageId := "your-imageId" + +args := &api.SharedUser{ + AccountId: accountId, + Account: account, +} +if err := bbcClient.ShareImage(imageId,args); err != nil { + fmt.Println("ShareImage failed: ", err) +} else { + fmt.Println("ShareImage success") +} +``` + +### 取消共享自定义镜像 + +- 该接口用于取消共享用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能共享。 +- imageId 所指定的镜像不存在,提示404错误。 +- 镜像取消共享后,被取消共享的用户不能再使用此镜像创建、重置实例。 +- 请求参数中的account和accountId均为可选参数,但不能全部为空,当两个参数同时出现时,服务端会自动去重。 + +```go +// 待共享的用户id +accountId := "your-accountId" +//待共享的用户名 +account := "your-account" +// 待共享的镜像ID +imageId := "your-imageId" + +args := &api.SharedUser{ + AccountId: accountId, + Account: account, +} +if err := bbcClient.UnShareImage(imageId,args); err != nil { + fmt.Println("UnShareImage failed: ", err) +} else { + fmt.Println("UnShareImage success") +} +``` + +### 跨区域复制自定义镜像 +- 用于用户跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制 +- regions如北京"bj",广州"gz",苏州"su",可多选: +```go + args := RemoteCopyImageArgs{ + Name: "test2", + DestRegion: []string{"gz"}, + } + err := bbcClient.RemoteCopyImage(imageId, args) + if err != nil { + fmt.Println("remote copy image failed:", err) + } else { + fmt.Println("remote copy image success") + } +``` + +### 跨区域复制自定义镜像并返回目的region的镜像镜像id +- 用于用户跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制 +- regions如北京"bj",广州"gz",苏州"su",可多选: +```go +args := &api.RemoteCopyImageArgs{ + Name: "test2", + DestRegion: []string{"gz"}, + } +result, err := bbcClient.RemoteCopyImageReturnImageIds(imageId, args) + if err != nil { + fmt.Println("remote copy image failed:", err) + } else { + fmt.Println("remote copy image success") + } +``` + +### 取消跨区域复制自定义镜像 + +用于取消跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制: + +```go +err := bbcClient.CancelRemoteCopyImage(imageId) +if err != nil { + fmt.Println("cancel remote copy image failed:", err) +} else { + fmt.Println("cancel remote copy image success") +} +``` + +### 查询自定义镜像已共享的用户 + +- 该接口用于查询自定义镜像已共享的用户列表 +- imageId 待查询的自定义镜像Id + +```go +// 待查询的自定义镜像ID +imageId := "your-imageId" + +if users, err := bbcClient.GetImageSharedUser(imageId); err != nil { + fmt.Println("GetImageSharedUser failed: ", err) +} else { + fmt.Println("GetImageSharedUser success: ", users) +} +``` + +## 操作日志 +### 查询操作日志 +通过以下代码查询指定操作日志 + +```go +// 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +marker := "your-marker" +// 每页包含的最大数量,最大数量通常不超过1000。缺省值为100 +maxKeys := 100 +// 需查询物理机操作的起始时间(UTC时间),格式 yyyy-MM-dd'T'HH:mm:ss'Z' ,为空则查询当日操作日志 +startTime := "" +// 需查询物理机操作的终止时间(UTC时间),格式 yyyy-MM-dd'T'HH:mm:ss'Z' ,为空则查询当日操作日志 +endTime := "" +queryArgs := &GetOperationLogArgs{ + Marker: marker, + MaxKeys: maxKeys, + StartTime: startTime, + EndTime: endTime, +} +if res, err := bbcClient.ListImage(queryArgs); err != nil { + fmt.Println("Get Operation Log failed: ", err) +} else { + fmt.Println("Get Operation Log success, result: ", res) +} +``` + +## 部署集 +### 创建部署集 +通过以下代码根据指定的部署集策略和并发度创建部署集 +```go +// 设置创建部署集的名称 +deploySetName := "your-deploy-set-name" +// 设置创建的部署集的描述信息 +deployDesc := "your-deploy-set-desc" +// 设置部署集并发度,范围 [1,5] +concurrency := 1 +// 设置创建部署集的策略,BBC实例策略只支持:"tor_ha" +strategy := "tor_ha" +queryArgs := &CreateDeploySetArgs{ + Strategy: strategy, + Concurrency: concurrency, + Name: deploySetName, + Desc: deployDesc, +} +if res, err := bbcClient.CreateDeploySet(queryArgs); err != nil { + fmt.Println("Create deploy set failed: ", err) +} else { + fmt.Println("Create deploy set success, result: ", res) +} +``` + +### 查询部署集列表 +使用以下代码查询所有部署集实例的列表及详情信息 + +```go +if res, err := bbcClient.ListDeploySets(); err != nil { + fmt.Println("List deploy sets failed: ", err) +} else { + fmt.Println("List deploy sets success, result: ", res) +} +``` + +### 查询部署集列表 +使用以下代码分页过滤查询所有部署集实例的列表及详情页面 + +```go +queryArgs := &ListDeploySetsArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker: "your-marker", + // 设置返回数据大小,缺省为1000 + MaxKeys: 100, + // 通过internal Ip过滤维修任务列表 + Strategy: "your-choose-strategy", +} +if res, err := bbcClient.ListDeploySetsPage(queryArgs); err != nil { + fmt.Println("List deploy sets failed: ", err) +} else { + fmt.Println("List deploy sets success, result: ", res) +} +``` + +### 查询部署集详情 +使用以下代码可以查询指定套餐的详细信息 + +```go +// 设置你要查询的deploySetID +deploySetID := "your-choose-deploy-set-id" +if res, err := bbcClient.GetDeploySet(deploySetID); err != nil { + fmt.Println("Get deploy set failed: ", err) +} else { + fmt.Println("Get deploy set success, result: ", res) +} +``` +### 删除指定的部署集 +使用以下代码删除用户自己的指定的部署集 + +```go +// 设置你要删除的deploySetID +deploySetID := "your-choose-deploy-set-id" +if err := bbcClient.DeleteDeploySet(deploySetID); err != nil { + fmt.Println("Delete deploy set failed: ", err) +} +``` + +##标签 +### 实例绑定标签 +通过以下代码绑定实例想要绑定的标签 +```go +bindTagsArgs := &BindTagsArgs{ + // 设置您要绑定的标签 + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := BBC_CLIENT.BindTags(instanceId, bindTagsArgs); err != nil { + fmt.Println("bind instance tags failed: ", err) +} else { + fmt.Println("bind instance tags success.") +} +``` +### 实例解绑标签 +通过以下代码解绑实例已有的标签 +```go +unbindTagsArgs := &UnbindTagsArgs{ + // 设置您要解绑的标签 + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := BBC_CLIENT.UnbindTags(instanceId, unbindTagsArgs); err != nil { + fmt.Println("unbind instance tags failed: ", err) +} else { + fmt.Println("unbind instance tags success.") +} +``` +## 查询物理机套餐库存 +查询物理机套餐库存 +```go +// 套餐id +flavorId := "flavorId" +// 可用区名称 +zoneName := "zoneName" + +args := &api.CreateInstanceStockArgs{ + FlavorId: flavorId, + ZoneName: zoneName, +} +if res, err := bbcClient.GetInstanceCreateStock(args); err != nil { + fmt.Println("GetInstanceCreateStock failed: ", err) +} else { + fmt.Println("GetInstanceCreateStock success: ", res) +} +``` + + +## 查询物理机套餐价格 +套餐询价接口 +```go +// 套餐id +flavorId := "flavorId" +// 购买数量 +purchaseCount := "purchaseCount" +//价格相关参数 +billing: = Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ + Length: 4, + }, + }, + +args := &api.InstancePirceArgs{ + FlavorId: flavorId, + PurchaseCount: purchaseCount, + Billing: billing, +} +if res, err := bbcClient.GetInstancePirce(args); err != nil { + fmt.Println("GetInstancePirce failed: ", err) +} else { + fmt.Println("GetInstancePirce success: ", res) +} +``` + + +## 物理机额外套餐查询 +物理机套餐参数查询接口 +```go +// 物理机id +instanceIds := []string{"your-instanceId"} + +args := &api.SimpleFlavorArgs{ + InstanceIds: instanceIds, +} +if res, err := bbcClient.GetSimpleFlavor(args); err != nil { + fmt.Println("GetSimpleFlavor failed: ", err) +} else { + fmt.Println("GetSimpleFlavor success: ", res) +} +``` + + +### 查询BBC维修任务列表 +使用以下代码查询所有未关闭的故障bbc维修任务的列表及详情信息: +```go +listArgs := &ListRepairTaskArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker: "your-marker", + // 设置返回数据大小,缺省为1000 + MaxKeys: 100, + // 通过internal Ip过滤维修任务列表 + InstanceId: "your-choose-instance-id", + // 通过故障名称过滤维修任务列表 + ErrResult: "your-choose-errResult", +} +if res, err := bbcClient.ListRepairTasks(listArgs); err != nil { + fmt.Println("list tasks failed: ", err) +} else { + fmt.Println("list tasks success, result: ", res) +} +``` + +### 查询关闭的维修任务列表 +使用以下代码查询关闭的任务列表 +```go +args := &bbc.ListClosedRepairTaskArgs { + // 返回数据大小 + MaxKeys: 100, + } +if res, err := bbcClient.ListClosedRepairTasks(args); err != nil{ + fmt.Println("Get the closed repaired tasks failed: ", err) +} else { + fmt.Println("Get the closed repaired tasks success: ", res) +} +``` + +### 查询维修任务详情 +使用以下代码可以查询指定BBC维修任务的详细信息: +```go +// 设置你要查询的taskId +taskId := "your-choose-task-id" +if res, err := bbcClient.GetRepairTaskDetail(taskId); err != nil { + fmt.Println("get task detail failed: ", err) +} else { + fmt.Println("get task detail success, result: ", res) +} +``` + + +### 维修任务授权维修 +使用以下代码可以授权指定BBC维修任务,实例状态必须为 unauthorized 或 ignored,调用此接口才可以成功返回,否则提示409错误: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId : "your-choose-task-id", +} +if err := bbcClient.AuthorizeRepairTask(taskIdArgs); err != nil { + fmt.Println("authorize task failed: ", err) +} else { + fmt.Println("authorize task success.") +} +``` + +### 维修任务授权维修 +使用以下代码可以授权指定BBC维修任务,实例状态必须为 unauthorized 或 ignored,调用此接口才可以成功返回,否则提示409错误: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId : "your-choose-task-id", +} +if err := bbcClient.AuthorizeRepairTask(taskIdArgs); err != nil { + fmt.Println("authorize task failed: ", err) +} else { + fmt.Println("authorize task success.") +} +``` + + +### 维修任务暂不授权维修 +使用以下代码可以暂不授权指定BBC维修任务,实例状态必须为 unauthorized,调用此接口才可以成功返回,否则提示409错误: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId :"your-choose-task-id", +} +if err := bbcClient.UnAuthorizeRepairTask(taskIdArgs); err != nil { + fmt.Println("unauthorize task failed: ", err) +} else { + fmt.Println("unauthorize task success.") +} +``` + + +### 维修任务确认恢复 +使用以下代码可以确认指定BBC维修任务已恢复,实例状态必须为 processing,调用此接口才可以成功返回,否则提示409错误: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId :"your-choose-task-id", +} +if err := bbcClient.ConfirmRepairTask(taskIdArgs); err != nil { + fmt.Println("confirm task failed: ", err) +} else { + fmt.Println("confirm task success.") +} +``` + + +### 维修任务确认未恢复 +使用以下代码可以确认指定BBC维修任务未恢复,实例状态必须为 processing,调用此接口才可以成功返回,否则提示409错误: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId :"your-choose-task-id", + // 设置你要报修的新故障 + newErrResult :"your-new-errResult", +} +if err := bbcClient.DisconfirmTaskArgs(taskIdArgs); err != nil { + fmt.Println("disconfirm task failed: ", err) +} else { + fmt.Println("disconfirm task success.") +} +``` + + +### 维修任务操作详情 +使用以下代码可以查看指定BBC维修任务操作详情: +```go +taskIdArgs := &TaskIdArgs{ + // 设置你要操作的taskId + taskId :"your-choose-task-id", +} +if err := bbcClient.GetRepairTaskRecord(taskIdArgs); err != nil { + fmt.Println("get task record failed: ", err) +} else { + fmt.Println("get task record success.") +} +``` + +### 查询BBC已完成的维修任务列表 +使用以下代码查询所有已关闭的故障bbc维修任务的列表及详情信息: +```go +listArgs := &ListRepairTaskArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker: "your-marker", + // 设置返回数据大小,缺省为1000 + MaxKeys: 100, + // 通过internal Ip过滤维修任务列表 + InstanceId: "your-choose-instance-id", + // 通过故障名称过滤维修任务列表 + ErrResult: "your-choose-errResult", + // 通过任务id过滤维修任务列表 + TaskId: "your-choose-task-id", +} +if res, err := bbcClient.ListRepairTasks(listArgs); err != nil { + fmt.Println("list tasks failed: ", err) +} else { + fmt.Println("list tasks success, result: ", res) +} +``` + +### 获取维修平台预授权规则列表 +通过以下代码获取维修平台预授权规则列表 +```go +args := &ListRuleArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker: "your-marker", + // 设置返回数据大小,缺省为1000 + MaxKeys: 100, + // 通过rule name筛选规则列表 + RuleName: "your-choose-rule-name", + // 通过rule id筛选规则列表 + RuleId: "your-choose-rule-id", +} +if res, err := bbcClient.ListRule(args); err != nil { + fmt.Println("list rules failed: ", err) +} else { + fmt.Println("list rules success, result: ", res) +} +``` + +### 获取维修平台预授权规则详情 +通过以下代码获取维修平台预授权规则详情 +```go +// 设置查询rule id +ruleId := "your-choose-rule-id" +if res, err := bbcClient.GetRuleDetail(ruleId); err != nil { + fmt.Println("get rule failed: ", err) +} else { + fmt.Println("get rule success, result: ", res) +} +``` + +### 创建维修平台预授权规则 +通过以下代码创建维修平台预授权规则 +```go +args := &CreateRuleArgs{ + // 设置规则名称 + RuleName: "your-choose-rule-name", + // 设置授权上线 + Limit: 2, + // 规则是否启用,1表示启用,0表示禁用 + Enabled: 1, + // 设置规则关联标签,tagKey:tagValue + TagStr: "tagKey:tagValue", + // 设置备注 + Extra: "extra", +} +if res, err := bbcClient.CreateRule(args); err != nil { + fmt.Println("create rule failed: ", err) +} else { + fmt.Println("create rule success, result: ", res) +} +``` + +### 删除维修平台预授权规则 +- 规则Enabled时不能删除 + +通过以下代码删除维修平台预授权规则 +```go +args := &DeleteRuleArgs{ + // 设置要删除的rule id + RuleId: "your-choose-rule-id", +} +if err := bbcClient.DeleteRule(args); err != nil { + fmt.Println("delete rule failed: ", err) +} else { + fmt.Println("delete rule success") +} +``` + +### 禁用维修平台预授权规则 +通过以下代码禁用维修平台预授权规则 +```go +args := &DisableRuleArgs{ + // 设置要禁用的rule id + RuleId: "your-choose-rule-id", +} +if err := bbcClient.CreateRule(args); err != nil { + fmt.Println("disable rule failed: ", err) +} else { + fmt.Println("disable rule success") +} +``` + +### 获取bbc本地盘信息列表 +通过以下代码可以分页获取bbc本地盘信息列表 +```go +queryArgs := &ListCDSVolumeArgs{ + MaxKeys: 100, + InstanceId: "InstanceId", + Marker: "VolumeId", + ZoneName: "zoneName", +} +if res, err := BBC_CLIENT.ListCDSVolume(queryArgs); err != nil { + fmt.Println("list volume failed: ", err) +} else { + fmt.Println("list volume success, result: ", res) +} +``` + +### 启用维修平台预授权规则 +通过以下代码启用维修平台预授权规则 +```go +args := &EnableRuleArgs{ + // 设置要启用的rule id + RuleId: "your-choose-rule-id", +} +if err := bbcClient.EnableRule(args); err != nil { + fmt.Println("enable rule failed: ", err) +} else { + fmt.Println("enable rule success") +} +``` + +### 获取基于部署集粒度的bbc资源余量 +通过以下代码可以分页获取基于部署集粒度的bbc资源余量。由于bbc现只支持单部署集创建,要求部署集参数传一个。 +```go +queryArgs := &GetBbcStockArgs{ + Flavor: "BBC-S3-02", + DeploySetIds: []string{"dset-0RHZYUfF"}, + } +if res, err := BBC_CLIENT.GetBbcStockWithDeploySet(queryArgs); err != nil { + fmt.Println("get bbc stock failed: ", err) +} else { + fmt.Println("list volume success, result: ", res) +} +``` diff --git a/bce-sdk-go/doc/BCC.md b/bce-sdk-go/doc/BCC.md new file mode 100644 index 0000000..79135a4 --- /dev/null +++ b/bce-sdk-go/doc/BCC.md @@ -0,0 +1,3747 @@ +# BCC服务 + +# 概述 + +本文档主要介绍BCC GO SDK的使用。在使用本文档前,您需要先了解BCC的一些基本知识。若您还不了解BCC,可以参考[产品描述](https://cloud.baidu.com/doc/BCC/s/Jjwvymo32)和[入门指南](https://cloud.baidu.com/doc/BCC/s/ojwvymvfe)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BCC访问域名](https://cloud.baidu.com/doc/BCC/s/0jwvyo603)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云BCC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BCC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BCC Client + +BCC Client是BCC服务的客户端,为开发者与BCC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BCC Client + +通过AK/SK方式访问BCC,用户可以参考如下代码新建一个Bcc Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bcc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BCCClient + bccClient, err := bcc.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BCC/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BCC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建BCC Client + +**申请STS token** + +BCC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BCC,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建BCC Client** + +申请好STS后,可将STS Token配置到BCC Client中,从而实现通过STS Token创建BCC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BCC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/bcc" //导入BCC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BCC服务的Client对象,Endpoint使用默认值 + bccClient, err := bcc.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create bcc client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + bccClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BCC Client时,无论对应BCC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BCC + +BCC支持HTTPS传输协议,您可以通过在创建BCC Client对象时指定的Endpoint中指明HTTPS的方式,在BCC GO SDK中使用HTTPS访问BCC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcc" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +bccClient, _ := bcc.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BCC Client + +如果用户需要配置BCC Client的一些细节的参数,可以在创建BCC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BCC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcc" + +//创建BCC Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := bcc.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcc" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := bcc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcc" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := bcc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BCC时,创建的BCC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BCC Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +BCC实例是一个虚拟的计算环境,包含CPU、内存等最基础的计算组件,是云服务器呈献给每个用户的实际操作实体。BCC实例是云服务器最为核心的概念,支持IP绑定,镜像和快照等功能,诸如CDS磁盘、SCS简单缓存服务只有挂载在BCC实例后才可使用。 + +- 每个用户最多可同时拥有20个BCC实例,若需要更多的配额,请发工单申请。 + +## 实例管理 + +### 创建实例 + +使用以下代码可以创建BCC实例,包括专属实例、普通型Ⅰ 型实例、普通型Ⅱ型实例、存储优化型BCC、计算优化型BCC +```go + +IsOpenHostnameDomain := true +AutoSeqSuffix := true +RelationTag := false +CdsAutoRenew := true +args := &api.CreateInstanceArgsV2{ + // 选择实例创建镜像ID + ImageId: "m-DpgNg8lO", + // 选择付款方式,可以选择预付费或后付费 + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + // 选择实例类型,可以选择N1, N2, N3等 + InstanceType: api.InstanceTypeN1, + // 选择1核CPU + CpuCount: 1, + // 选择1GB内存 + MemoryCapacityInGB: 1, + // 选择40GB磁盘空间 + RootDiskSizeInGb: 40, + // 选择待创建的实例系统盘介质为HP1 + RootDiskStorageType: api.StorageTypeCloudHP1, + // 临时盘数据盘大小 + EphemeralDisks []EphemeralDisk "ephemeralDisks" + // 选择创建100GB大小SSD类型CDS磁盘并挂载到实例上 + CreateCdsList: []api.CreateCdsModel{ + { + StorageType: api.StorageTypeSSD, + CdsSizeInGB: 100, + }, + }, + // 设置管理员密码 + AdminPass: "123qaz!@#", + // 设置实例名称 + Name: "terraform_sdkTest", + // 实例主机名,可选参数,若不选则主机名和实例名称保持一致(实例名称不包含中文名时) + // 仅支持小写字母、数字以及- . 特殊字符,不可连续使用特殊符号,不支持特殊符号开头或结尾,长度2-64 + Hostname: "your-choose-instance-hostname", + // 设置是否自动生成hostname和name有序后缀 是:true 否:false + AutoSeqSuffix: &AutoSeqSuffix, + // 设置是否开启hostname domain 是:true 否:false + IsOpenHostnameDomain: &IsOpenHostnameDomain, + // 设置创建BCC使用的网络带宽大小 + NetWorkCapacityInMbps int networkCapacityInMbps + // 设置需要创建BCC使用的DCC服务器id + DedicateHostId string "dedicatedHostId" + // 设置待查询的竞价实例的购买个数 + PurchaseCount int purchaseCount + // 设置可用区 + ZoneName string "zoneName" + // 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定, + // 同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 + // 设置创建BCC使用的子网 + SubnetId string "subnetId" + // 设置创建BCC使用的安全组 + SecurityGroupId string "securityGroupId" + // 设置创建BCC使用的企业安全组,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupId string "enterpriseSecurityGroupId" + // 设置创建BCC使用的安全组列表 + SecurityGroupIds []string securityGroupIds + // 设置创建BCC使用的企业安全组列表,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupIds []string enterpriseSecurityGroupIds + // 设置需要创建GPU卡信息 + GpuCard string "gpuCard" + // 设置需要创建FPGA卡信息 + FpgaCard string "fpgaCard" + // 设置GPU卡或FPGA卡数量 + CardCount string "cardCount" + // 设置按月付费或者按年付费 月是"month",年是"year" + AutoRenewTimeUnit string "autoRenewTimeUnit" + // 设置自动续费的时间 按月是1-9 按年是 1-3 + AutoRenewTime int autoRenewTime + // cds是否自动续费 是:true 否:false + CdsAutoRenew bool &CdsAutoRenew + // 待创建实例指定的标签是否需要和已有标签键进行关联,默认为false。注意值为true时要保证该标签键已存在 + RelationTag bool &RelationTag + // 待创建的标签列表 + Tags []model.TagModel tags + // 指定实例所在的部署集id + DeployId string "deployId" + // 指定实例所在的部署集id 列表 + DeployIdList []string "deployIdList" + // 设置释放保护 默认0不启用,1启用 + DetetionProtection int "deletionProtection" + // 设置要绑定的密钥对ID + KeypairId string "keypairId" + // 设置要绑定的自动快照策略ID + AspId string "aspId" + // 公网带宽计费方式,若不指定internetChargeType,默认付费方式同BCC,预付费默认为包年包月按带宽,后付费默认为按使用带宽计费。(BANDWIDTH_PREPAID:预付费按带宽结算;TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费;BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费) + InternetChargeType string "internetChargeType" + // 设置内网IP + InternalIps []string internalIps + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken string "random-uuid" + //创建实例支持幂等的token + RequestToken string "requestToken" + // 设置要绑定的资源组id + ResGroupId string "resGroupId" +} + +// 若要生成预付费实例,可以按以下配置生成一个月的预付费实例 +args.Billing = api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + } + } + +// 若要设置自动续费,可以按以下参数设置一年内自动续费 +args.AutoRenewTimeUnit = "year" +args.AutoRenewTime = 1 + +// 若要创建公网EIP,可以设置以下参数 +args.networkCapacityInMbps = 1 + +// 若要一次创建多个相同配置的实例,可以设置以下参数 +args.PurchaseCount = 2 + +result, err := client.CreateInstanceV2(args) +if err != nil { + fmt.Println("create instance failed:", err) +} else { + fmt.Println("create instance success: ", result) +} +``` + +> **提示:** +> 1. 创建BCC请求是一个异步请求,返回200表明订单生成,后续可以通过查询返回的实例id信息了解BCC虚机的创建进度。 +> 2. 本接口用于创建一个或多个同配虚拟机实例。 +> 3. 创建实例需要实名认证,没有通过实名认证的可以前往百度开放云官网控制台中的安全认证下的实名认证中进行认证。 +> 4. 创建计费方式为后付费的实例需要账户现金余额+通用代金券大于100;预付费方式的实例则需要账户现金余额大于等于实例费用。 +> 5. 支持批量创建,且如果创建过程中有一个实例创建失败,所有实例将全部回滚,均创建失败,如果创建时包含CDS,CDS也会回滚。 +> 6. 缺省情形下,一个实例最多只能挂载5个云磁盘。 +> 7. 创建CDS磁盘和临时数据盘时,磁盘容量大小限制为5的倍数。 +> 8. 创建实例支持创建和添加临时数据盘,但不支持单独创建或添加临时数据盘。 +> 9. 临时数据盘不支持挂载、卸载、删除。 +> 10. 普通实例的临时数据盘最大不能超过500G。 +> 11. 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定,同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 +> 12. 指定公网IP带宽创建,计费方式为按照带宽计费。 +> 13. 创建接口为异步创建,可通过查询实例详情接口查询实例状态 +> 14. 可通过该接口指定专属服务器创建实例,专属实例不参与计费。专属实例只能通过ephemeralDisks创建临时盘并指定磁盘类型。 +> 15. 每个实例最多只能购买一块临时数据盘。 +> 16. 实例的临时数据盘默认只有hp1类型。 +> 17. 通过instanceType字段指定需要创建的虚机类型,目前API支持创建的虚机类型参见下述InstanceType。参数(instanceType,cpuCount,memoryCapacityInGB)可以确定需要的机型以及配置。 +> 18. 创建存储优化型实例必须购买临时数据盘,通过ephemeralDisks指定临时盘数据盘大小,默认nvme类型数据盘,无需指定。 +> 19. 创建请求详细使用请参考BCC API 文档[创建实例](https://cloud.baidu.com/doc/BCC/s/yjwvyoe0s) +> 20. 创建FPGA BCC虚机需要使用指定的(CPU、内存、本地数据盘、FPGA卡类型以及专用镜像), 详细请参考BCC API 文档[FPGA型BCC可选规格配置](https://cloud.baidu.com/doc/BCC/s/6jwvyo0q2#fpga%E5%9E%8Bbcc%E5%8F%AF%E9%80%89%E8%A7%84%E6%A0%BC%E9%85%8D%E7%BD%AE) +> 21. 创建GPU BCC虚机需要使用指定的(CPU、内存、本地数据盘、GPU卡类型), 详细请参考BCC API 文档[GPU型BCC可选规格配置](https://cloud.baidu.com/doc/BCC/s/6jwvyo0q2#gpu%E5%9E%8Bbcc%E5%8F%AF%E9%80%89%E8%A7%84%E6%A0%BC%E9%85%8D%E7%BD%AE) + +### 创建实例(通过指定实例套餐规格) +使用以下代码可以创建BCC实例,包括普通型BCC、存储优化型BCC、计算优化型BCC、大数据机型BCC、GPU机型BCC、FPGA机型BCC: + +```go + +IsOpenHostnameDomain := true +AutoSeqSuffix := true +RelationTag := false +CdsAutoRenew := true +createInstanceBySpecArgs := &api.CreateInstanceBySpecArgsV2{ + // 选择实例创建镜像ID + ImageId: "m-1PyVLtic", + // 选择创建BCC的套餐规格 + Spec: "bcc.g2.c2m8", + // 选择40GB磁盘空间 + RootDiskSizeInGb: 40, + // 选择待创建的实例系统盘介质为HP1 + RootDiskStorageType: api.StorageTypeCloudHP1, + // 选择创建100GB大小SSD类型CDS磁盘并挂载到实例上 + CreateCdsList: []api.CreateCdsModel{ + { + StorageType: api.StorageTypeSSD, + CdsSizeInGB: 100, + }, + }, + // 选择付款方式,可以选择预付费或后付费 + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + Name: "sdkTest", + // 实例主机名,可选参数,若不选则主机名和实例名称保持一致(实例名称不包含中文名时) + // 仅支持小写字母、数字以及- . 特殊字符,不可连续使用特殊符号,不支持特殊符号开头或结尾,长度2-64 + Hostname: "your-choose-instance-hostname", + // 设置是否自动生成hostname和name有序后缀 是:true 否:false + AutoSeqSuffix: &AutoSeqSuffix, + // 设置是否开启hostname domain 是:true 否:false + IsOpenHostnameDomain: &IsOpenHostnameDomain, + AdminPass: "123qaz!@#", + // 临时盘数据盘大小 + EphemeralDisks []EphemeralDisk "ephemeralDisks" + // 设置创建BCC使用的网络带宽大小 + NetWorkCapacityInMbps int networkCapacityInMbps + // 设置待查询的竞价实例的购买个数 + PurchaseCount int purchaseCount + // 开启部分交付,并设置最小交付虚机个数 + PurchaseMinCount int purchaseMinCount + // 设置可用区 + ZoneName string "zoneName" + // 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定, + // 同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 + // 设置创建BCC使用的子网 + SubnetId string "subnetId" + // 设置创建BCC使用的安全组 + SecurityGroupId string "securityGroupId" + // 设置创建BCC使用的企业安全组,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupId string "enterpriseSecurityGroupId" + // 设置创建BCC使用的安全组列表 + SecurityGroupIds []string securityGroupIds + // 设置创建BCC使用的企业安全组列表,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupIds []string enterpriseSecurityGroupIds + // 设置按月付费或者按年付费 月是"month",年是"year" + AutoRenewTimeUnit string "autoRenewTimeUnit" + // 设置自动续费的时间 按月是1-9 按年是 1-3 + AutoRenewTime int autoRenewTime + // cds是否自动续费 是:true 否:false + CdsAutoRenew bool &CdsAutoRenew + // 指定实例所在的部署集id 列表 + DeployIdList []string "deployIdList" + // 设置释放保护 默认0不启用,1启用 + DetetionProtection int "deletionProtection" + // 待创建实例指定的标签是否需要和已有标签键进行关联,默认为false。注意值为true时要保证该标签键已存在 + RelationTag bool &RelationTag + // 待创建的标签列表 + Tags []model.TagModel tags + // 设置要绑定的密钥对ID + KeypairId string "keypairId" + // 设置要绑定的自动快照策略ID + AspId string "aspId" + // 公网带宽计费方式,若不指定internetChargeType,默认付费方式同BCC,预付费默认为包年包月按带宽,后付费默认为按使用带宽计费。(BANDWIDTH_PREPAID:预付费按带宽结算;TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费;BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费) + InternetChargeType string "internetChargeType" + // 设置内网IP + InternalIps []string internalIps + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken string "random-uuid" + // 创建实例支持幂等的token,成功后永久有效 + RequestToken string "requestToken" + // 设置要绑定的资源组id + ResGroupId string "resGroupId" +} +result, err := client.CreateInstanceBySpecV2(args) +if err != nil { + fmt.Println("create instance failed:", err) +} else { + fmt.Println("create instance success: ", result) +} +``` +> **提示:** +> 1. 创建BCC请求是一个异步请求,返回200表明订单生成,后续可以通过查询返回的实例id信息了解BCC虚机的创建进度。 +> 2. 本接口用于创建一个或多个同配虚拟机实例。 +> 3. 创建实例需要实名认证,没有通过实名认证的可以前往百度开放云官网控制台中的安全认证下的实名认证中进行认证。 +> 4. 创建计费方式为后付费的实例需要账户现金余额+通用代金券大于100;预付费方式的实例则需要账户现金余额大于等于实例费用。 +> 5. 支持批量创建,且如果创建过程中有一个实例创建失败,所有实例将全部回滚,均创建失败,如果创建时包含CDS,CDS也会回滚。 +> 6. 缺省情形下,一个实例最多只能挂载5个云磁盘。 +> 7. 创建CDS磁盘和临时数据盘时,磁盘容量大小限制为5的倍数。 +> 8. 创建实例支持创建和添加临时数据盘,但不支持单独创建或添加临时数据盘。 +> 9. 临时数据盘不支持挂载、卸载、删除。 +> 10. 普通实例的临时数据盘最大不能超过500G。 +> 11. 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定,同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 +> 12. 指定公网IP带宽创建,计费方式为按照带宽计费。 +> 13. 创建接口为异步创建,可通过查询实例详情接口查询实例状态 +> 14. 每个实例最多只能购买一块临时数据盘。 +> 15. 实例的临时数据盘默认只有hp1类型。 +> 16. 创建存储优化型实例必须购买临时数据盘,通过ephemeralDisks指定临时盘数据盘大小,默认nvme类型数据盘,无需指定。 + +### 创建实例(V3) +使用以下代码可以创建BCC实例,包括普通型BCC、存储优化型BCC、计算优化型BCC、大数据机型BCC、GPU机型BCC、FPGA机型BCC: + +```go +createInstanceV3Args := &api.CreateInstanceV3Args{ + // 选择创建BCC的套餐规格 + InstanceSpec: "bcc.ic3.c1m1", + // 系统盘类型,大小 + // 选择40GB大小的Cloud_SSD_General类型磁盘作为系统盘 + SystemVolume: api.SystemVolume{ + StorageType: api.StorageTypeV3CloudSSDGeneral, + VolumeSize: 40, + }, + // 本地盘&CDS数据盘 + // 选择创建5GB大小Cloud_Premium类型CDS云磁盘并挂载到实例上 + DataVolumes: []api.DataVolume{ + { + StorageType: api.StorageTypeV3CloudPremium, + VolumeSize: 5, + // 快照ID,当磁盘类型属于CDS云磁盘时,此属性有效 + SnapshotId: "snapshotId", + // 加密密钥,当磁盘类型属于CDS云磁盘时,此属性有效 + EncryptKey: "encryptKey", + }, + }, + // 实例名称 + InstanceName: "sdkTest", + // 实例购买数量 + PurchaseCount: 1, + // 实例主机名,可选参数,若不选则主机名和实例名称保持一致(实例名称不包含中文名时) + // 仅支持小写字母、数字以及- . 特殊字符,不可连续使用特殊符号,不支持特殊符号开头或结尾,长度2-64 + HostName: "hostName", + // 设置是否开启hostname domain 是:true 否:false + AutoSeqSuffix: false, + // 设置是否开启hostname domain 是:true 否:false + HostNameDomain: false, + // 实例管理员密码 + Password: "123qaz!@#", + // 选择付款方式,可以选择预付费:Prepaid,后付费:Postpaid,竞价付费:Spotpaid(选择竞价付费时需配置InstanceMarketOptions参数) + // 选择购买时长1,默认单位:月 + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + }, + }, + // 设置可用区 + ZoneName: "zoneName" + // 指定子网和安全组,要求子网和安全组必须同时指定或同时不指定, + // 同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 + // 设置创建BCC使用的子网 + SubnetId: "subnetId" + // 设置创建BCC使用的安全组 + SecurityGroupIds: []string{ + "securityGroup1", + "securityGroup2", + }, + // 联合购买资源CDS,EIP统一关联标签,默认:false + AssociatedResourceTag: false, + // 待创建的标签列表 + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, + // 设置要绑定的密钥对ID + KeypairId: "keypairId", + // 设置自动续费的时间,单位:月 + // 取值范围:1,2,3,4,5,6,7,8,9,12,24,36 + AutoRenewTime: 1, + // CDS数据盘是否自动续费 是:true 否:false + CdsAutoRenew: true, + // 设置要绑定的自动快照策略ID + AutoSnapshotPolicyId: "autoSnapshotPolicyId", + // 部署集 + DeploymentSetId: "deploymentSetId", + // 镜像id + ImageId: "imageId", + // 竞价实例出价模型 + // SpotOption. 市场价: "market" 自定义:"custom" + // SpotPrice. 竞价实例出价金额,若是自定义出价,且出价金额小于市场价,则不允许创建。当spotOption='custom'时才有效。 + InstanceMarketOptions: api.InstanceMarketOptions{ + SpotOption: "custom", + SpotPrice: "spotPrice", + }, + // 待创建实例是否开启ipv6,只有当镜像和子网都支持ipv6时才可开启,true表示开启,false表示关闭,不传表示自动适配镜像和子网的ipv6支持情况 + Ipv6: false, + // 专属服务器ID + DedicatedHostId: "dedicatedHostId", + // 公网带宽 + // InternetMaxBandwidthOut. 设置创建BCC使用的网络带宽大小,单位为Mbps。必须为0~200之间的整数,为0表示不分配公网IP,默认为0Mbps + // InternetChargeType. 公网带宽计费方式,若不指定internetChargeType,默认付费方式同BCC,预付费默认为包年包月按带宽, + // 后付费默认为按使用带宽计费。(BANDWIDTH_PREPAID:预付费按带宽结算;TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费; + // BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费) + InternetAccessible: api.InternetAccessible{ + InternetMaxBandwidthOut: 5, + InternetChargeType: api.TrafficPostpaidByHour, + }, + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken: "random-uuid", + // 创建实例支持幂等的token,成功后永久有效 + RequestToken: "requestToken", + // 设置要绑定的资源组id + ResGroupId string "resGroupId" +} +result, err := client.CreateInstanceV3(args) +if err != nil { + fmt.Println("create instance failed:", err) +} else { + fmt.Println("create instance success: ", result) +} +``` +> **提示:** +> 1. 创建BCC请求是一个异步请求,返回200表明订单生成,后续可以通过查询返回的实例id信息了解BCC虚机的创建进度。 +> 2. 本接口用于创建一个或多个同配虚拟机实例。 +> 3. 创建实例需要实名认证,没有通过实名认证的可以前往百度开放云官网控制台中的安全认证下的实名认证中进行认证。 +> 4. 创建计费方式为后付费的实例需要账户现金余额+通用代金券大于100;预付费方式的实例则需要账户现金余额大于等于实例费用。 +> 5. 支持批量创建,且如果创建过程中有一个实例创建失败,所有实例将全部回滚,均创建失败,如果创建时包含CDS,CDS也会回滚。 +> 6. 缺省情形下,一个实例最多只能挂载5个云磁盘。 +> 7. 创建CDS磁盘和本地数据盘时,磁盘容量大小限制为5的倍数。 +> 8. 创建实例支持创建和添加本地数据盘,但不支持单独创建或添加临时数据盘。 +> 9. 本地数据盘不支持挂载、卸载、删除。 +> 10. 普通实例的临时数据盘最大不能超过500G。 +> 11. 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定,同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 +> 12. 指定公网IP带宽创建,计费方式为按照带宽计费。 +> 13. 创建接口为异步创建,可通过查询实例详情接口查询实例状态 +> 14. 每个实例最多只能购买一块本地数据盘。 +> 15. 创建存储优化型实例必须购买本地数据盘,通过DataVolumes指定本地数据盘大小,需指定属于本地数据盘的磁盘类型。 + +## 创建竞价实例 +使用以下代码可以创建BCC实例: +```go +createInstanceArgs := &CreateInstanceArgs{ + // 输入你要创建instance使用的镜像ID + ImageId: "your-choose-image-id", + // BCC实例类型 + InstanceType: "InstanceType" + // BCC核数 + CpuCount: CpuCount + // BCC的内存大小GB + MemoryCapacityInGB: MemoryCapacityInGB + // 系统盘大小GB + RootDiskSizeInGb int "rootDiskSizeInGb" + // 设置待查询的竞价实例的系统盘介质 + RootDiskStorageType StorageType "rootDiskStorageType" + // 临时盘数据盘大小 + EphemeralDisks []EphemeralDisk "ephemeralDisks" + // 创建需要创建的CDS磁盘列表 + CreateCdsList []CreateCdsModel "createCdsList" + // 设置创建BCC使用的网络带宽大小 + NetWorkCapacityInMbps int networkCapacityInMbps + // 设置需要创建BCC使用的DCC服务器id + DedicateHostId string "dedicatedHostId" + // 设置待查询的竞价实例的购买个数 + PurchaseCount int purchaseCount + // 实例名称 + Name string "name" + // 实例主机名,可选参数,若不选则主机名和实例名称保持一致(实例名称不包含中文名时) + // 仅支持小写字母、数字以及- . 特殊字符,不可连续使用特殊符号,不支持特殊符号开头或结尾,长度2-64 + Hostname: "your-choose-instance-hostname", + // 设置是否自动生成hostname和name有序后缀 是:true 否:false + AutoSeqSuffix: false, + // 设置是否开启hostname domain 是:true 否:false + IsOpenHostnameDomain: false, + // 设置BCC虚机密码 + AdminPass string "adminPass" + // 设置可用区 + ZoneName string "zoneName" + // 指定子网和安全组创建,要求子网和安全组必须同时指定或同时不指定, + // 同时指定的子网和安全组必须同属于一个VPC,都不指定会使用默认子网和默认安全组。 + // 设置创建BCC使用的子网 + SubnetId string "subnetId" + // // 设置创建BCC使用的安全组 + SecurityGroupId string "securityGroupId" + // 设置创建BCC使用的企业安全组,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupId string "enterpriseSecurityGroupId" + // 设置创建BCC使用的安全组列表 + SecurityGroupIds []string securityGroupIds + // 设置创建BCC使用的企业安全组列表,不允许同时设置企业安全组和普通安全组 + EnterpriseSecurityGroupIds []string enterpriseSecurityGroupIds + // 设置需要创建GPU卡信息 + GpuCard string "gpuCard" + // 设置需要创建FPGA卡信息 + FpgaCard string "fpgaCard" + // 设置GPU卡或FPGA卡数量 + CardCount string "cardCount" + // 设置按月付费或者按年付费 月是"month",年是"year" + AutoRenewTimeUnit string "autoRenewTimeUnit" + // 设置自动续费的时间 按月是1-9 按年是 1-3 + AutoRenewTime int autoRenewTime + // cds是否自动续费 是:true 否:false + CdsAutoRenew bool cdsAutoRenew + // 待创建实例指定的标签是否需要和已有标签键进行关联,默认为false。注意值为true时要保证该标签键已存在 + RelationTag bool relationTag + // 待创建的标签列表 + Tags []model.TagModel tags + // 指定实例所在的部署集id + DeployId string "deployId" + // 设置创建BCC虚机使用的竞价模式:market 或者 custom + BidModel string "bidModel" + // 设置创建BCC虚机使用的竞价金额,只有当bidModel为custom时有效 + BidPrice string "bidPrice" + // 设置要绑定的密钥对ID + KeypairId string "keypairId" + // 设置要绑定的自动快照策略ID + AspId string "aspId" + // 公网带宽计费方式,若不指定internetChargeType,默认付费方式同BCC,预付费默认为包年包月按带宽,后付费默认为按使用带宽计费。(BANDWIDTH_PREPAID:预付费按带宽结算;TRAFFIC_POSTPAID_BY_HOUR:流量按小时后付费;BANDWIDTH_POSTPAID_BY_HOUR:带宽按小时后付费) + InternetChargeType string "internetChargeType" + // 设置内网IP + InternalIps []string internalIps + // 设置创建BCC虚机使用的竞价模式:market 或者 custom + BidModel string "bidModel" + // 设置创建BCC虚机使用的竞价金额,只有当bidModel为custom时有效 + BidPrice string "bidPrice" + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken string "random-uuid" + // 创建实例支持幂等的token,成功后永久有效 + RequestToken string "requestToken" + // 设置要绑定的资源组id + ResGroupId string "resGroupId" +} +if res, err := bccClient.CreateBidInstance(createInstanceBySpecArgs); err != nil { + fmt.Println("create instance failed: ", err) +} else { + fmt.Println("create instance success, instanceId: ", res.InstanceIds[0]) +} +``` +## 取消竞价实例订单 +通过以下代码可以取消竞价实例订单 +```go +cancelBidOrderRequest := &CancelBidOrderRequest{ + // 订单ID + OrderId string "orderId" +} +if err := bccClient.CancelBidOrder(cancelBidOrderRequest); err != nil { + fmt.Println("CancelBidOrderRequest failed: ", err) +} else { + fmt.Println("CancelBidOrderRequest success.") +} +``` +## 查询竞价实例套餐 +通过以下代码可以查询竞价实例套餐 +```go +if res, err := bccClient.ListBidFlavor(); err != nil { + fmt.Println("List bidding instance flavors failed: ", err) +} else { + fmt.Println("List bidding instance flavors success, result: ", res) +} +``` +## 查询竞价实例市场价 +通过以下代码可以查询竞价实例市场价 +```go +createCdsList := []api.CreateCdsModel{{ + // 设置CDS磁盘容量,必须为大于0的整数,单位为GB,大小为0~5120G,可选参数 + CdsSizeInGB: 40, + // 设置CDS磁盘存储类别,默认为高性能型,可选参数 + StorageType: api.StorageTypeHP1, + // 设置快照ID + SnapshotId: "your-snapshot-id", + // 设置加密密钥,true或false + EncryptKey: true, +}} +tagList := []model.TagModel{{ + // 设置要查询的tagKey + TagKey: "your-tag-key", + // 设置要查询的tagValue + TagValue: "your-tag-value", +}} +args := &api.GetBidInstancePriceArgs{ + // 使用 uuid 生成一个长度不超过64位的ASCII字符串 + ClientToken: "random-uuid", + // 设置待查询的虚拟机实例类型,具体可选类型参见InstanceType + InstanceType: "your-choose-instance-type", + // 设置待查询虚拟机实例的CPU核数 + CpuCount: 1, + // 设置待查询虚拟机实例的内存容量 + MemoryCapacityInGB: 2, + // 设置待查询虚拟机实例的系统盘大小,单位GB,默认是40GB,范围为[40, 100]GB,超过40GB按照云磁盘价格收费。注意指定的系统盘大小需要满足所使用镜像最小磁盘空间限制。 + RootDiskSizeInGb: 45, + // 设置待查询虚拟机实例系统盘介质,默认使用SSD型云磁盘,可指定系统盘磁盘类型可参见StorageType + RootDiskStorageType: api.StorageTypeCloudHP1, + // 设置待查询的CDS磁盘列表 + CreateCdsList: createCdsList, + // 设置批量查询(购买)的虚拟机实例个数,必须为大于0的整数,可选参数,缺省为1 + PurchaseCount: 1, + // 设置虚拟机名字,可选参数 + Name: "your-choose-instance-name", + // 设置实例管理员密码(8-16位字符,英文,数字和符号必须同时存在,符号仅限!@#$%^*()),可选参数 + AdminPass: "your-admin-pass", + // 设置待查询实例所要绑定的密钥对ID,可选参数 + KeypairId: "your-keypair-id", + // 设置自动快照策略ID,可选参数 + AspId: "your-asp-id", + // 设置待查询虚拟机实例的镜像ID,可选参数 + ImageId: "your-image-id", + // 设置竞价实例出价模型, 市场价: "market" 自定义:"custom",可选参数 + BidModel: "your-bid-model", + // 设置竞价实例出价金额,若是自定义出价,且出价金额小于市场价,则不允许创建。当bidModel='custom'时才有效,可选参数 + BidPrice: "your-bid-price", + // 设置公网带宽,单位为Mbps。必须为0~200之间的整数,为0表示不分配公网IP,默认为0Mbps,可选参数 + NetWorkCapacityInMbps: 20, + // 设置待查询实例指定的标签是否需要和已有标签键进行关联,默认为false。注意值为true时要保证该标签键已存在,可选参数 + RelationTag: false, + // 设置待查询的标签列表,可选参数 + Tags: tagList, + // 设置securityGroup信息,为空时将使用默认安全组,可选参数 + SecurityGroupId: "your-security-group-id", + // 设置subnet信息,为空时将使用默认子网,可选参数 + SubnetId: "your-subnet-id", + // 设置指定zone信息,默认为空,由系统自动选择,可选参数 + ZoneName: "your-zone-name", + // 设置公网带宽计费方式,可选参数 + InternetChargeType: "your-internet-charge-type", +} +if res, err := bccClient.GetBidInstancePrice(args); err != nil { + fmt.Println("Get bidding instance price failed: ", err) +} else { + fmt.Println("Get bidding instance price success, result: ", res) +} +``` + +##查询实例套餐库存 + +查询实例资源套餐规格对应的库存。 +```go +// 实例类型 +instanceType := "instanceType" +// CPU核数 +cpuCount := cpuCount +// 内存容量(GB) +memoryCapacityInGB := memoryCapacityInGB +// 可用区名称 +zoneName := "zoneNamen" +// GPU卡类型,GPU和VGPU可填 +gpuCard := "gpuCard" +// GPU卡数量,GPU和VGPU可填 +cardCount := "cardCount" +//本地盘信息 +ephemeralDisks := []EphemeralDisks{{ + "storageType": "ssd", + "sizeInGB": sizeInGB, +}} + +args := &api.CreateInstanceStockArgs{ + InstanceType: instanceType, + CpuCount: cpuCount, + MemoryCapacityInGB: memoryCapacityInGB, + ZoneName: zoneName, + GpuCard: gpuCard, + CardCount: cardCount, + EphemeralDisks: ephemeralDisks, +} +if res, err := bccClient.GetInstanceCreateStock(args); err != nil { + fmt.Println("GetInstanceCreateStock failed: ", err) +} else { + fmt.Println("GetInstanceCreateStock success: ", res) +} + +``` +###实例扩缩容库存查询 + +实例变配余量查询 +```go +// 实例id +instanceId := "instanceId" +// CPU核数 +cpuCount := cpuCount +// 内存容量(GB) +memoryCapacityInGB := memoryCapacityInGB + +args := &api.CreateInstanceStockArgs{ + InstanceId: instanceId, + CpuCount: cpuCount, + MemoryCapacityInGB: memoryCapacityInGB, +} +if res, err := bccClient.ResizeInstanceStockArgs(args); err != nil { + fmt.Println("ResizeInstanceStockArgs failed: ", err) +} else { + fmt.Println("ResizeInstanceStockArgs success: ", res) +} + +``` +###查询带部署集相关字段实例详情 + 查询带部署集相关字段实例详情: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +//设置是否展示部署集字段,true or false +isDeploySet := "your-isDeploySet" + +if res, err := bccClient.GetInstanceDetailWithDeploySet(instanceId,isDeploySet); err != nil { +fmt.Println("get instance detail failed: ", err) +} else { +fmt.Println("get instance detail success, result: ", res) +} + +``` + +## 查询带部署集和失败相关字段实例详情 + +查询带部署集和失败相关字段实例详情 + +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +//设置是否展示部署集字段,true or false +isDeploySet := "your-isDeploySet" +//设置是否展示部署集字段,true or false +containsFailed := "your-containsFailed" + +if res, err := bccClient.GetInstanceDetailWithDeploySet(instanceId,isDeploySet,containsFailed); err != nil { +fmt.Println("get instance detail failed: ", err) +} else { +fmt.Println("get instance detail success, result: ", res) +} +``` +## 查询部署集详情 + +通过以下代码利用部署集ID查询部署集详情 + +```go +// 设置你要查询的deploySetID +deploySetID := "your-choose-deploy-set-id" +if res, err := bccClient.GetDeploySet(deploySetID); err != nil { + fmt.Println("Delete deploy set failed: ", err) +} else { + fmt.Println("Get deploy set success: ", res) +} +``` + +## 查询可关机不计费的实例列表 +查询可关机不计费的BCC实例列表: +```go +listInstanceArgs := &ListInstanceArgs{ + // 批量获取列表的查询起始位置,是一个由系统产生的字符串 + Marker string + // 设置返回数据大小,缺省为1000 + MaxKeys int + // 通过internal Ip过滤 + InternalIp string + // 通过DedicatedHostId过滤 + DedicatedHostId string + // 通过ZoneName过滤 + ZoneName string + // 通过KeypairId过滤 + KeypairId string +} + +if res, err := bccClient.GetInstanceNoChargeList(listInstanceArgs); err != nil { + fmt.Println("GetInstanceNoChargeList failed: ", err) +} else { + fmt.Println("GetInstanceNoChargeList success, result: ", res) +} +``` + +### 查询实例列表 + +以下代码可以查询BCC虚机实例列表,支持通过内网ip、专属服务器id、可用区名称进行筛选 +```go +args := &api.ListInstanceArgs{} + +// 若要查询某个内网IP对应的实例列表,可以配置以下参数 +args.InternalIp = "1.1.1.1" + +result, err := client.ListInstances(args) +if err != nil { + fmt.Println("list instance failed:", err) +} else { + fmt.Println("list instance success: ", result) +} +``` +### 查询实例列表V3 +以下代码可以查询BCC虚机实例列表,支持通过实例ID、实例名称、内网ip、实例公网IP、专属服务器id、私有网络名称、 +子网名称、子网ID、是否自动续费、密钥对ID、密钥对名称、部署集ID、部署集名称、资源分组、标签、可用区名称进行筛选 +```go +args := &api.ListServerRequestV3Args{ +Marker: "", +MaxKeys: 3, +} +result, err := BCC_CLIENT.ListServersByMarkerV3(args) +if err != nil { +fmt.Println("list instance failed: ", err) +} else { +fmt.Println("list instance success") +data, e := json.Marshal(result) +if e != nil { +fmt.Println("json marshal failed!") +return +} +fmt.Printf("list instance : %s", data) +} +``` + +### 查询回收站实例列表 + +以下代码可以查询回收站中的BCC虚机实例列表,支持通过虚机id,名字进行筛选 +```go +// 批量获取列表的查询的起始位置,是一个由系统生成的字符串,可选参数 +marker := "your-marker" +// 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000,可选参数 +maxKeys := your-maxKeys +// 设置想要查询的付费类型,可选参数 Prepaid表示预付费,Postpaid表示后付费,不传表示都选 +paymentTiming := "Postpaid" +// 设置想要查询的虚机id,可选参数 +instanceId := "your-choose-instance-id" +// 设置想要查询的虚机名称,可选参数 +name := "your-choose-name" +// 设置想要查询虚机的回收开始时间(北京时间),可选参数 (闭区间) +recycleBegin := "2020-11-19T09:12:35Z" +// 设置想要查询虚机的回收结束时间(北京时间),可选参数 (开区间) +recycleEnd := "2020-11-26T09:12:35Z" +args := &api.ListRecycleInstanceArgs{ + Marker: marker, + MaxKeys: maxKeys, + PaymentTiming: paymentTiming, + InstanceId: instanceId, + Name: name, + RecycleBegin: recycleBegin, + RecycleEnd: recycleEnd, +} +result, err := client.ListRecycleInstances(args) +if err != nil { + fmt.Println("list instance failed:", err) +} else { + fmt.Println("list instance success: ", result) +} +``` + + +### 查询指定实例详情 + +使用以下代码可以查询指定BCC虚机的详细信息 +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +result, err := client.GetInstanceDetail(instanceId) +if err != nil { + fmt.Println("get instance detail failed:", err) +} else + fmt.Println("get instance detail success ", result) +} +``` + +### 启动实例 + +如下代码可以启动一个实例 +```go +err := client.StartInstance(instanceId) +if err != nil { + fmt.Println("start instance failed:", err) +} else { + fmt.Println("start instance success") +} +``` + +> **提示:** +> - 实例状态必须为Stopped,调用此接口才能成功返回,否则返回409错误 +> - 该接口调用后,实例会进入Starting状态 + +### 停止实例 + +如下代码可以停止一个实例 +```go +// 以下代码可以强制停止一个实例 +err := client.StopInstance(instanceId, true) +if err != nil { + fmt.Println("stop instance failed:", err) +} else { + fmt.Println("stop instance success") +} +``` + +## 停止实例(支持强制停止&关机不计费) +使用以下代码停止实例: + +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 设置是否强制停止,强制停止等同于断电处理,可能丢失实例操作系统中未写入磁盘的数据 +forceStop := false +// 设置是否关机不计费,TRUE为关机不计费,FALSE为关机计费。注意:只有白名单用户才可以实行关机不计费 +stopWithNoCharge := false + +if err := bccClient.StopInstanceWithNoCharge(instanceId, forceStop, stopWithNoCharge); err != nil { + fmt.Println("Stop instance failed: ", err) +} else { + fmt.Println("Stop instance success.") +} +``` + +> **提示:** +> - 系统后台会在实例实际 Stop 成功后进入“已停止”状态。 +> - 只有状态为 Running 的实例才可以进行此操作,否则提示 409 错误。 +> - 实例支持强制停止,强制停止等同于断电处理,可能丢失实例操作系统中未写入磁盘的数据。 + +### 重启实例 + +如下代码可以重启实例 +```go +// 以下代码可以强制重启一个实例 +err := client.RebootInstance(instanceId, true) +if err != nil { + fmt.Println("reboot instance failed:", err) +} else { + fmt.Println("reboot instance success") +} +``` + +> **提示:** +> - 只有状态为 Running 的实例才可以进行此操作,否则提示 409 错误。 +> - 接口调用成功后实例进入 Starting 状态。 +> - 支持强制重启,强制重启等同于传统服务器的断电重启,可能丢失实例操作系统中未写入磁盘的数据。 + +### 修改实例密码 + +如下代码可以修改实例密码 +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +args := &api.ChangeInstancePassArgs{ + AdminPass: "321zaq#@!", +} +err := client.ChangeInstancePass(instanceId, args) +if err != nil { + fmt.Println("change instance password failed:", err) +} else { + fmt.Println("change instance password success") +} +``` + +> **提示:** +> 只有 Running 和 Stopped 状态的实例才可以用调用接口,否则提示 409 错误。 + +### 修改实例属性 + +如下代码可以修改实例属性 +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +args := &api.ModifyInstanceAttributeArgs{ + Name: "newInstanceName", + NetEthQueueCount: "new eth queue count", +} +err := client.ModifyInstanceAttribute(instanceId, args) +if err != nil { + fmt.Println("modify instance failed:", err) +} else { + fmt.Println("modify instance success") +} +``` + +> **提示:** +> - 目前该接口仅支持修改实例名称 + +### 修改实例描述 + +如下代码可以修改实例描述 +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +args := &api.ModifyInstanceDescArgs{ + Description: "new Instance description", +} +err := client.ModifyInstanceDesc(instanceId, args) +if err != nil { + fmt.Println("modify instance failed:", err) +} else { + fmt.Println("modify instance success") +} +``` + +### 修改实例主机名 + +如下代码可以修改实例主机名 +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +args := &api.ModifyInstanceHostnameArgs{ + // 设置想要修改的新主机名 + Hostname: "new Instance hostname", + // 设置是否开启domain,可选参数 true表示开启 false和null 表示关闭 + IsOpenHostnameDomain: true, + // 设置是否自动重启,可选参数 true表示重启,false和null表示不重启 + Reboot: true, +} +err := client.ModifyInstanceHostname(instanceId, args) +if err != nil { + fmt.Println("modify instance hostname failed:", err) +} else { + fmt.Println("modify instance hostname success") +} +``` + +### 重装实例 + +如下代码可以重装实例 +```go +IsOpenHostEye := true +IsPreserveData := true +args := &api.RebuildInstanceArgs{ + ImageId: "m-***", + AdminPass: "***", + // 设置要绑定的密钥对ID + KeypairId: "keypairId", + // 是否开启hosteye服务,选填 + IsOpenHostEye: &IsOpenHostEye, + // 以下参数机型相关,使用部分ebc套餐时选填 + IsPreserveData: &IsPreserveData, + // 此参数在isPreserveData为false时为必填,在isPreserveData为true时不生效 + RaidId: "your_raid_id", + // 系统盘根分区大小,默认为20G,取值范围为20-100。此参数在isPreserveData为true时不生效 + SysRootSize: 20, + // 指定系统盘文件系统,当前合法值:xfs,ext4 + RootPartitionType: "your-choose-rootPartitionType", + // 指定数据盘文件系统,当前合法值:xfs,ext4 + DataPartitionType: "your-choose-dataPartitionType", +} +err := client.RebuildInstance(instanceId, args) +if err != nil { + fmt.Println("rebuild instance failed:", err) +} else { + fmt.Println("rebuild instance success") +} +``` + +## 重装实例(批量) +使用以下代码重装实例: + +```go +IsOpenHostEye := true +IsPreserveData := true +rebuildBatchInstanceArgs := &api.RebuildBatchInstanceArgs{ + // 输入你要重装instance使用的镜像ID + ImageId: "imageId", + // 设置BCC虚机密码 + AdminPass: "***", + // 设置要绑定的密钥对ID + KeypairId: "keypairId", + // 实例ID集合 + InstanceIds: []string{"instanceIds"}, + // 是否开启hosteye服务,选填 + IsOpenHostEye: &IsOpenHostEye, + // 以下参数机型相关,使用部分ebc套餐时选填 + IsPreserveData: &IsPreserveData, + // 此参数在isPreserveData为false时为必填,在isPreserveData为true时不生效 + RaidId: "your_raid_id", + // 系统盘根分区大小,默认为20G,取值范围为20-100。此参数在isPreserveData为true时不生效 + SysRootSize: 20, + // 指定系统盘文件系统,当前合法值:xfs,ext4 + RootPartitionType: "your-choose-rootPartitionType", + // 指定数据盘文件系统,当前合法值:xfs,ext4 + DataPartitionType: "your-choose-dataPartitionType", +} + +if err := bccClient.BatchRebuildInstances(rebuildBatchInstanceArgs); err != nil { + fmt.Println("rebuild batch instance failed: ", err) +} else { + fmt.Println("rebuild batch instance success.") +} +``` + +> **提示:** +> - 实例重装后,基于原系统盘的快照会自动删除,基于原系统盘的自定义镜像会保留。 + +### 释放实例 + +如下代码可以释放实例 +```go +err := client.DeleteInstance(instanceId) +if err != nil { + fmt.Println("delete instance failed:", err) +} else { + fmt.Println("delete instance success") +} +``` + +### 释放实例(POST) +使用以下代码释放实例: +```go +deleteInstanceWithRelateResourceArgs := &api.DeleteInstanceWithRelateResourceArgs{ + // 设置释放的时候是否关联释放当前时刻,实例挂载的eip+数据盘 false代表否 true代表是 + // (只有该字段为true时 deleteCdsSnapshotFlag字段才会有效,若该字段为false,deleteCdsSnapshotFlag字段的值无效) + RelatedReleaseFlag: true, + //设置是否释放弹性网卡 false代表否 true代表是,默认false + DeleteRelatedEnisFlag: true, + // 设置是否释放云磁盘快照 false代表否 true代表是 + DeleteCdsSnapshotFlag: true, + // 设置是否进入回收站 true表示进入回收站, false和null表示不进入回收站 + BccRecycleFlag: true, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.DeleteInstanceWithRelateResource(instanceId, deleteInstanceWithRelateResourceArgs); err != nil { + fmt.Println("release instance failed: ", err) +} else { + fmt.Println("release instance success.") +} +``` +### 批量释放实例(POST) +使用以下代码批量释放实例: +```go +deleteInstanceWithRelateResourceArgs := &api.BatchDeleteInstanceWithRelateResourceArgs{ + // 设置释放的时候是否关联释放当前时刻,实例挂载的eip+数据盘 false代表否 true代表是 + // (只有该字段为true时 deleteCdsSnapshotFlag字段才会有效,若该字段为false,deleteCdsSnapshotFlag字段的值无效) + RelatedReleaseFlag: true, + //设置是否释放弹性网卡 false代表否 true代表是,默认false + DeleteRelatedEnisFlag: true, + // 设置是否释放云磁盘快照 false代表否 true代表是 + DeleteCdsSnapshotFlag: true, + // 设置是否进入回收站 true表示进入回收站, false和null表示不进入回收站 + BccRecycleFlag: true, + // 批量释放的实例id + InstanceIds: []string{"i-tzKEY***"}, +} +if err := bccClient.BatchDeleteInstanceWithRelateResource(deleteInstanceWithRelateResourceArgs); err != nil { + fmt.Println("release instance failed: ", err) +} else { + fmt.Println("release instance success.") +} +``` + +### 释放实例(包含预付费实例) +不区分后付费还是预付费实例,释放bcc以及关联的资源,可以使用以下代码将其释放: +```go + args := &api.DeleteInstanceIngorePaymentArgs{ + InstanceId: "instanceid", + //设置是否释放eip和cds false代表eip和cds与实例解绑,实例进回收站;true代表eip解绑,cds与实例绑定进回收站 + RelatedReleaseFlag: true, + //设置是否释放弹性网卡 false代表否 true代表是,默认false + DeleteRelatedEnisFlag:true, + //设置是否释放云磁盘快照 false代表否 true代表是,默认false,释放预付费bcc时DeleteCdsSnapshotFlag和RelatedReleaseFlag存在绑定关系, + RelatedReleaseFlag为true时,DeleteCdsSnapshotFlag必须为true + // 选择DeleteCdsSnapshotFlag=true即会释放虚机绑定的各种快照 + // 释放后付费bcc时,DeleteCdsSnapshotFlag和RelatedReleaseFlag之间逻辑和之前逻辑保持一致 + DeleteCdsSnapshotFlag:true, + //设置是否立即释放,默认false,保持释放进入回收站逻辑;为true时,实例和设置了关联释放的cds资源,一起立即释放 + DeleteImmediate: false, + + } + if res, err := BCC_CLIENT.DeleteInstanceIngorePayment(args); err != nil { + fmt.Println("delete instance failed: ", err) + } else { + fmt.Printf("delete instance success, result: %s", res.String()) + } + +``` + +### 释放回收站实例 +回收站实例7天后自动释放,清理回收站资源,可以使用以下代码将其释放: +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +if err := bccClient.DeleteRecycledInstance(instanceId); err != nil { + fmt.Println("release instance failed: ", err) +} else { + fmt.Println("release instance success.") +} +``` + +> **提示:** +> - 释放单个云服务器实例,释放后实例所使用的物理资源都被收回,相关数据全部丢失且不可恢复。 +> - 只有付费类型为Postpaid或者付费类型为Prepaid且已过期的实例才可以释放。 +> - 实例释放后,已挂载的CDS磁盘自动卸载,,基于此CDS磁盘的快照会保留。 +> - 实例释放后,基于原系统盘的快照会自动删除,基于原系统盘的自定义镜像会保留。 + +### 定时释放 (限定后付费实例) + +后付费实例定时释放,到达预设时间后自动释放bcc,自动释放时间可查询实例详情ReleaseTime。设定空字符串可以取消定时释放。请谨慎使用该功能,避免遗忘定时设置 +```go +err := bccClient.AutoReleaseInstance(instanceId, "2021-05-01T07:58:09Z") +if err != nil { + fmt.Println("set instance autoRelease failed:", err) +} else { + fmt.Println("set instance autoRelease success") +} +``` + +> **提示:** +> - 只有付费类型为Postpaid的后付费实例允许设定自动释放 +> - 本实例系统盘快照及实例快照都会被释放 +> - 本实例已挂载CDS云磁盘都会被自动卸载,不会被释放 +> - 实例释放后不可恢复 +> - 关联的网卡资源会被自动卸载,且被释放 + +### 释放保护 +使用以下代码可以为BCC实例设置释放保护,实例当前设置可查询实例详情DeletionProtection,默认0不保护,1释放保护中(创建和查询入口限v2版本使用): + +```go +args := &api.DeletionProtectionArgs { +// 释放保护状态 0不启用,1启用 + DeletionProtection : 0, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.ModifyDeletionProtection(instanceId, args); err != nil { + fmt.Println("modifyDeletionProtection failed: ", err) +} else { + fmt.Println("modifyDeletionProtection success.") +} +``` + +> **提示:** +> - 后付费和预付费均可开启释放保护 +> - 已开启释放保护的实例将无法通过控制台或API释放,只有在关闭的情况下才能被手动释放。定时释放,欠费释放以及实例过期释放不受释放保护属性的影响 +> - 实例释放保护默认不开启 + +### 变配实例 +使用以下代码可以选择CPU,MemoryCapacityInGB,EphemeralDisks变配指定BCC实例: + +```go +resizeInstanceArgs := &ResizeInstanceArgs{ + // BCC核数 + CpuCount: CpuCount + // BCC的内存大小GB + MemoryCapacityInGB: MemoryCapacityInGB + // 临时盘数据盘大小 + EphemeralDisks []EphemeralDisk "ephemeralDisks" +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.ResizeInstance(instanceId, resizeInstanceArgs); err != nil { + fmt.Println("resize instance failed: ", err) +} else { + fmt.Println("resize instance success.") +} +``` +### 变配实例(通过实例套餐规格) +使用以下代码可以选择CPU,MemoryCapacityInGB,EphemeralDisks变配指定BCC实例: + +```go +resizeInstanceArgs := &ResizeInstanceArgs{ + // 实例套餐规格 + Spec string "spec" +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.ResizeInstanceBySpec(instanceId, resizeInstanceArgs); err != nil { + fmt.Println("resize instance failed: ", err) +} else { + fmt.Println("resize instance success.") +} +``` + +### 绑定安全组 +使用以下代码绑定安全组: + +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 设置BCC绑定的安全组 +SecurityGroupId := "SecurityGroupId" + +if err := bccClient.BindSecurityGroup(instanceId, SecurityGroupId); err != nil { + fmt.Println("Bind Security Group failed: ", err) +} else { + fmt.Println("Bind Security Group success.") +} +``` +### 解绑安全组 +使用以下代码解绑安全组: + +```go +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" +// 设置BCC解绑的安全组 +SecurityGroupId := "SecurityGroupId" + +if err := bccClient.UnBindSecurityGroup(instanceId, SecurityGroupId); err != nil { + fmt.Println("UnBind Security Group failed: ", err) +} else { + fmt.Println("UnBind Security Group success.") +} +``` + +> **提示:** +> - 每个实例至少关联一个安全组,默认关联默认安全组。 +> - 如果实例仅属于一个安全组,尝试移出时,请求会报 403 错。 + +### 实例扩缩容 + +```go +args := &api.ResizeInstanceArgs{ + CpuCount: 2, + MemoryCapacityInGB: 4, + LiveResize: true, +} +err := client.ResizeInstance(instanceId, args) +if err != nil { + fmt.Println("resize instance failed:", err) +} else { + fmt.Println("resize instance success") +``` + +> **提示:** +> - 实例计费方式为预付费时,不能进行缩容操作 +> - 实例计费方式为后付费时,可弹性扩缩容 +> - 只有实例Running或Stopped状态时可以进行扩缩容操作 +> - 实例扩缩容之后会重启一次,可选择热升级进行扩缩容,不影响业务中断,热升级的限制请参考文档[热升级的限制](https://cloud.baidu.com/doc/BCC/s/gjwvyohty#%E5%9C%A8%E7%BA%BF%E6%94%B9%E9%85%8D%EF%BC%88%E7%83%AD%E5%8D%87%E7%BA%A7%EF%BC%89%E7%9A%84%E9%99%90%E5%88%B6%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F) +> - 异步接口,可通过查询实例详情接口查看扩缩容状态是否完成 +> - 专属实例可以通过指定的cpu、内存以及临时盘大小,专属实例临时盘大小只支持扩容而不支持缩容,具体请参考API文档 [实例扩缩容](https://cloud.baidu.com/doc/BCC/s/1jwvyoc9l) +### 查询实例VNC地址 + +如下代码可以查询实例的VNC地址 +```go +result, err := client.GetInstanceVNC(instanceId) +if err != nil { + fmt.Println("get instance VNC url failed:", err) +} else { + fmt.Println("get instance VNC url success: ", result) +} +``` + +> **提示:** +> - VNC地址一次使用后即失效 +> - URL地址有效期为10分钟 + +### 实例续费 + +对BCC虚机的续费操作,可以延长过期时长,以下代码可以对实例及关联产品进行续费 +```go +args := &api.PurchaseReservedArgs{ + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, + // 设置实例关联续费标识,默认为空字符串。 + RelatedRenewFlag: "CDS", +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +result, err := bccClient.InstancePurchaseReserved(instanceId, args) +if err != nil { + fmt.Println("Purchase Reserved Instance failed: ", err) +} else { + fmt.Println("Purchase Reserved Instance success: ", result) +} +``` +> **注意:** +> +> 关联续费产品(relatedRenewFlag)可选: +> +> - CDS 只对BCC实例关联的预付费CDS进行续费 +> - EIP 只对BCC实例关联的预付费EIP进行续费 +> - MKT 只对BCC实例关联的预付费MKT进行续费 +> - CDS_EIP 只对BCC实例关联的预付费CDS、EIP进行续费 +> - CDS_MKT 只对BCC实例关联的预付费CDS、MKT进行续费 +> - EIP_MKT 只对BCC实例关联的预付费EIP、MKT进行续费 +> - CDS_EIP_MKT 只对BCC实例关联的预付费CDS、EIP、MKT进行续费 + +> **提示:** +> - BCC虚机实例扩缩容期间不能进行续费操作。 +> - 续费时若实例已欠费停机,续费成功后有个BCC虚机实例启动的过程。 +> - 该接口是一个异步接口。 +> - 专属实例不支持续费。 + +### 实例变更子网 + +如下代码可以变更实例的子网 +```go +args := &api.InstanceChangeSubnetArgs{ + InstanceId: instanceId, + SubnetId: subnetId, + InternalIp: internalIp, + Reboot: false, +} +err := client.InstanceChangeSubnet(args) +if err != nil { + fmt.Println("change instance subnet failed:", err) +} else { + fmt.Println("change instance subnet success") +} +``` + +> **提示:** +> - 变更子网后默认自动重启,用户选择是否执行该操作。 +> - 变更子网的范围目前仅支持在同AZ下变更子网,不支持跨AZ或跨VPC变更子网,如果从普通子网变更至NAT专属子网请先手动解绑EIP。 + +### 实例变更VPC + +如下代码可以变更实例的VPC +```go +args := &api.InstanceChangeVpcArgs{ + InstanceId: instanceId, + SubnetId: subnetId, + InternalIp: internalIp, + Reboot: true, + SecurityGroupIds: securityGroupId, + EnterpriseSecurityGroupIds: enterpriseSecurityGroupId +} +err := client.InstanceChangeVpc(args) +if err != nil { + fmt.Println("change instance vpc failed:", err) +} else { + fmt.Println("change instance vpc success") +} +``` + +> **提示:** +> - 变更VPC后默认自动重启,用户选择是否执行该操作。 +> - 变更VPC后仅保留主网卡主IP(在新子网中自动分配),实例上的辅助IP、弹性网卡和安全组等信息不跟随主体迁移。 +> - 安全组和企业安全组不能同时指定。 + +### 向指定实例批量添加指定ip + +```go +batchAddIpArgs := &api.BatchAddIpArgs{ + // 实例ID + InstanceId: "instanceId", + // 辅助IP,和SecondaryPrivateIpAddressCount不可同时使用 + PrivateIps: []string{"privateIps"}, + // 自动分配IP数量,和PrivateIps不可同时使用 + SecondaryPrivateIpAddressCount: 1, + // 是否是创建ipv6,ipv6必须是true,默认为false,创建ipv4 + AllocateMultiIpv6Addr: true, + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClientToken: "clientToken", +} + +if res, err := bccClient.BatchAddIP(batchAddIpArgs); err != nil { + fmt.Println("BatchAddIP failed: ", err) +} else { + fmt.Println("BatchAddIP success, result: ", res) +} +``` + +### 批量删除指定实例的ip + +```go +privateIps := []string{"192.168.1.25"} +instanceId := "your-choose-instance-id" +// 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 +clientToken := "clientToken" +batchDelIpArgs := &api.BatchDelIpArgs{ + InstanceId: instanceId, + PrivateIps: privateIps, + ClientToken: clientToken, +} +if err := client.BatchDelIP(batchDelIpArgs); err != nil { + fmt.Println("delete ips failed: ", err) +} else { + fmt.Println("delete ips success.") +} +``` + +### 开通自动续费(包含关联产品) +自动续费仅限预付费产品 + +```go +bccCreateAutoRenewArgs := &api.BccCreateAutoRenewArgs{ + // 实例ID + InstanceId: instanceId, + // 续费单位,month,year + RenewTimeUnit: "month", + // 续费时长,单位:month,支持1, 2, 3, 4, 5, 6, 7, 8, 9;单位:year,支持1, 2, 3 + RenewTime: 1, +} + +if err := bccClient.BatchCreateAutoRenewRules(bccCreateAutoRenewArgs); err != nil { + fmt.Println("BatchCreateAutoRenewRules failed: ", err) +} else { + fmt.Println("BatchCreateAutoRenewRules success.") +} +``` + +### 关闭自动续费(包含关联产品) +自动续费仅限预付费产品 + +```go +bccDeleteAutoRenewArgs := &api.BccDeleteAutoRenewArgs{ + // 实例ID + InstanceId: instanceId, +} + +if err := bccClient.BatchDeleteAutoRenewRules(bccDeleteAutoRenewArgs); err != nil { + fmt.Println("BatchDeleteAutoRenewRules failed: ", err) +} else { + fmt.Println("BatchDeleteAutoRenewRules success.") +} +``` + +### 后付费资源从回收站恢复计费 +仅限后付费产品,预付费资源走续费接口 + +```go +args := &api.RecoveryInstanceArgs{ + InstanceIds: []api.RecoveryInstanceModel{ + { + InstanceId: instanceId, + }, + }, +} +if err := BCC_CLIENT.RecoveryInstance(args); err != nil { + fmt.Println("recovery instance failed: ", err) +} else { + fmt.Println("recovery instance success") +} + +``` + +### 计费变更-转预付费 +使用以下代码对实例计费变更-转预付费: + +```go +changeToPrepaidRequest := &ChangeToPrepaidRequest{ + // 设置将要变更预付费的时长,单位为月 + Duration int "duration" + // 设置是否变更关联的数据盘,TRUE表示变更,FLASE表示不变更 + RelationCds bool "relationCds" +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.ChangeToPrepaid(instanceId, changeToPrepaidRequest); err != nil { + fmt.Println("ChangeToPrepaid failed: ", err) +} else { + fmt.Println("ChangeToPrepaid success.") +} +``` + +### 实例绑定标签 +使用以下代码对实例绑定标签: + +```go +bindTagsRequest := &api.BindTagsRequest{ + // 设置想要绑定的标签 + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.BindInstanceToTags(instanceId, bindTagsRequest); err != nil { + fmt.Println("BindInstanceToTags failed: ", err) +} else { + fmt.Println("BindInstanceToTags success.") +} +``` + +### 实例解绑标签 +使用以下代码对实例解绑标签: + +```go +unBindTagsRequest := &api.UnBindTagsRequest{ + // 设置想要解绑的标签 + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, +} +// 设置你要操作的instanceId +instanceId := "your-choose-instance-id" + +if err := bccClient.UnBindInstanceToTags(instanceId, unBindTagsRequest); err != nil { + fmt.Println("UnBindInstanceToTags failed: ", err) +} else { + fmt.Println("UnBindInstanceToTags success.") +} +``` + +### 查询可以变配的实例规格 +使用以下代码可以查询可以变配的实例规格 +```go + listAvailableResizeSpecsArgs := &api.ListAvailableResizeSpecsArgs{ + Spec: "bcc.ic5.c1m1", + Zone: "cn-bj-a", + } + res, _ := BCC_CLIENT.ListAvailableResizeSpecs(listAvailableResizeSpecsArgs) + fmt.Println(res) +``` + +### 批量转预付费 +使用以下代码可以将实例批量转预付费,若需要对实例关联的cds变更,则必须在CdsList中传入所有需要转为预付费的cdsId,若所有cds均变更计费传入all。 +```go + batchChangeInstanceToPrepayArgs := &api.BatchChangeInstanceToPrepayArgs{ + Config: []api.PrepayConfig{ + { + InstanceId: "i-fYumyWOx", + Duration: 1, + CdsList: []string{ + "v-tP2MJ3YI", + "v-9eLRwv8n", + } + }, + { + InstanceId: "i-Q2pAyWOx", + Duration: 1, + CdsList: []string{ + "all", + } + }, + }, + } + result, err := BCC_CLIENT.BatchChangeInstanceToPrepay(batchChangeInstanceToPrepayArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +``` + +### 批量转后付费 +使用以下代码可以将实例批量转后付费, 若需要对实例关联的cds变更,则必须在CdsList中传入所有需要转为后付费的cdsId,若所有cds均变更计费传入all。 +```go + batchChangeInstanceToPostArgs := &api.BatchChangeInstanceToPostpayArgs{ + Config: []api.PostpayConfig{ + { + InstanceId: "i-IWxCA6HH", + CdsList: []string{ + "v-buE5Cm3m", + "v-rEcWwELY", + } + }, + { + InstanceId: "i-Q2pAyWOx", + CdsList: []string{ + "all", + } + }, + }, + } + result, err := BCC_CLIENT.BatchChangeInstanceToPostpay(batchChangeInstanceToPostArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +``` + +### 获取实例角色列表 +使用以下代码可以获取实例角色列表 +```go + res, _ := BCC_CLIENT.ListInstanceRoles() + fmt.Println(res) +``` + + +### 绑定角色 +使用以下代码可以为BCC实例绑定角色 +``` + bindInstanceRoleArgs := &api.BindInstanceRoleArgs{ + RoleName: "Test_BCC", + Instances: []api.Instances{ + { + InstanceId: BCC_TestBccId, + }, + }, + } + + result, _ := BCC_CLIENT.BindInstanceRole(bindInstanceRoleArgs) + fmt.Println(result) +``` + +### 解绑角色 +使用以下代码可以为BCC实例解绑角色 +``` + unbindInstanceRoleArgs := &api.UnBindInstanceRoleArgs{ + RoleName: "Test_BCC", + Instances: []api.Instances{ + { + InstanceId: BCC_TestBccId, + }, + }, + } + + result, _ := BCC_CLIENT.UnBindInstanceRole(unbindInstanceRoleArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +``` + +### 添加Ipv6 +使用以下代码可以为BCC实例添加Ipv6 +```go + addIpv6Args := &api.AddIpv6Args{ + InstanceId: BCC_TestBccId, + Reboot: true, + Ipv6Address: "2400:da00:e003:0:41c:4100:0:2", + } + + result, _ := BCC_CLIENT.AddIpv6(addIpv6Args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +``` + +### 释放Ipv6 +使用以下代码可以为BCC实例释放Ipv6 +``` go +deleteIpv6Args := &api.DeleteIpv6Args{ + InstanceId: BCC_TestBccId, + Reboot: true, + } + + err := BCC_CLIENT.DeleteIpv6(deleteIpv6Args) + fmt.Println(err) +``` + +### 根据实例ID批量查询实例列表 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &api.ListInstanceByInstanceIdArgs{ + // 待查询的实例id列表 + InstanceIds: []string{ + "i-c2sXa***", + "i-kcLZB***", + }, +} +result, err := BCC_CLIENT.ListInstanceByInstanceIds(args) +if err != nil { + fmt.Println("list instance failed:", err) +} else { + fmt.Println("list instance success: ", result) +} +``` + +### 批量查询实例是否删除完成 +以下代码可以根据实例ID批量查询实例是否删除完成 +```go + bccId := "bccId" + args := &api.GetInstanceDeleteProgressArgs{ + InstanceIds: []string{ + bccId, + }, + } + + res, err := BCC_CLIENT.GetInstanceDeleteProgress(args) + if err != nil { + fmt.Println(err) + } + fmt.Println(res) +``` + +### 查询实例绑定的弹性网卡列表 + +使用以下代码可以查询实例绑定的弹性网卡列表 + +```go +// 设置你要操作的instanceId +instanceId := "instanceId" +if res, err := BCC_CLIENT.ListInstanceEnis(instanceId); err != nil { + fmt.Println("Get specific instance eni failed: ", err) +} else { + fmt.Println("Get specific instance eni success, result: ", res) +} +``` + +## 磁盘管理 + +### 查询可用区的磁盘信息 +使用以下代码可以查询指定可用区的磁盘信息 +```GO +// 设置你要操作的zoneName +zoneName := "cn-bj-a" +if res, err := bccClient.GetAvailableDiskInfo(zoneName); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) +} else { + fmt.Println("Get the specific zone flavor success, result: ", res) +} +``` + +### 创建CDS磁盘 + +支持新建空白CDS磁盘或者从CDS数据盘快照创建CDS磁盘,参考以下代码可以创建CDS磁盘: + +```go +// 新建CDS磁盘 +args := &api.CreateCDSVolumeArgs{ + // 创建一个CDS磁盘,若要同时创建多个相同配置的磁盘,可以修改此参数 + PurchaseCount: 1, + // 磁盘空间大小 + CdsSizeInGB: 40, + // 设置磁盘存储介质 + StorageType: api.StorageTypeSSD, + // 设置磁盘付费模式为后付费 + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + // 设置磁盘名称 + Name: "sdkCreate", + // 设置磁盘描述 + Description: "sdk test", + // 快照ID + SnapshotId string "snapshotId" + // 可用区 + ZoneName string "zoneName" + // 设置磁盘加密密钥 + EncryptKey string "encryptKey" +} +result, err := client.CreateCDSVolume(args) +if err != nil { + fmt.Println("create CDS volume failed:", err) +} else { + fmt.Println("create CDS volume success: ", result) +} +``` + +> **提示:** +> - 创建CDS磁盘接口为异步接口,可通过[查询磁盘详情](#查询磁盘详情)接口查询磁盘状态,详细接口使用请参考BCC API 文档[查询磁盘详情](https://cloud.baidu.com/doc/BCC/s/1jwvyo4ly) + + +### 释放预付费CDS磁盘 +支持释放预付费的磁盘,参考以下代码可以释放磁盘: +```go +args := &api.VolumePrepayDeleteRequestArgs{ + VolumeId: "v-tVDW1NkK", + RelatedReleaseFlag: false, + } + result, err := BCC_CLIENT.DeletePrepayVolume(args) + if err != nil { + fmt.Println("delete volume failed: ", err) + } else { + fmt.Println("delete volume success") + data, e := json.Marshal(result) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("delete volume : %s", data) + } +``` + +### 创建CDS磁盘(V3) + +支持新建空白CDS磁盘或者从CDS数据盘快照创建CDS磁盘,参考以下代码可以创建CDS磁盘: + +```go +// 新建CDS磁盘 +args := &api.CreateCDSVolumeV3Args{ + // 创建一个CDS磁盘,若要同时创建多个相同配置的磁盘,可以修改此参数 + PurchaseCount: 1, + // 磁盘空间大小 + VolumeSize: 50, + // 设置磁盘存储介质 + StorageType: api.StorageTypeV3CloudSSDGeneral, + // 设置磁盘付费模式为后付费 + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + // 设置磁盘名称 + VolumeName: "sdkCreate", + // 设置磁盘描述 + Description: "sdk test", + // 快照ID + SnapshotId string "snapshotId", + // 可用区 + ZoneName string "zoneName", + // 设置磁盘加密密钥 + EncryptKey string "encryptKey", + // 自动续费的时间单位,按月付费或者按年付费 月是"month",年是"year" + RenewTimeUnit string "renewTimeUnit" + // 自动续费的时间 按月是1-9 按年是 1-3 + RenewTime int "renewTime" + // 设置自动快照策略id + AutoSnapshotPolicyId string "autoSnapshotPolicyId", +} +result, err := client.CreateCDSVolumeV3(args) +if err != nil { + fmt.Println("create CDS volume failed:", err) +} else { + fmt.Println("create CDS volume success: ", result) +} +``` + +> **提示:** +> - 创建CDS磁盘接口为异步接口,可通过[查询磁盘详情](#查询磁盘详情)接口查询磁盘状态 + +### 查询磁盘列表 + +以下代码可以查询所有的磁盘列表,支持分页查询以及通过次磁盘所挂载的BCC实例id进行过滤筛选: + +```go +args := &api.ListCDSVolumeArgs{} + +// 设置查询绑定了特定实例的CDS磁盘 +args.InstanceId = instanceId + +result, err := client.ListCDSVolume(args) +if err != nil { + fmt.Println("list CDS volume failed:", err) +} else { + fmt.Println("list CDS volume success: ", result) +} +``` + +### 查询磁盘列表(V3) + +以下代码可以查询所有的磁盘列表,支持分页查询以及通过次磁盘所挂载的BCC实例id进行过滤筛选: + +```go +args := &api.ListCDSVolumeArgs{} + +// 设置查询绑定了特定实例的CDS磁盘 +args.InstanceId = instanceId + +result, err := client.ListCDSVolumeV3(args) +if err != nil { + fmt.Println("list CDS volume failed:", err) +} else { + fmt.Println("list CDS volume success: ", result) +} +``` + +### 查询磁盘详情 + +通过磁盘id可以获取对应磁盘的详细信息,以下代码可以查询磁盘详情: + +```go +result, err := client.GetCDSVolumeDetail(volumeId) +if err != nil { + fmt.Println("get CDS volume detail failed:", err) +} else { + fmt.Println("get CDS volume detail success: ", result) +} +``` + +### 查询磁盘详情(V3) + +通过磁盘id可以获取对应磁盘的详细信息,以下代码可以查询磁盘详情: + +```go +result, err := client.GetCDSVolumeDetailV3(volumeId) +if err != nil { + fmt.Println("get CDS volume detail failed:", err) +} else { + fmt.Println("get CDS volume detail success: ", result.Volume) +} +``` + +### 挂载CDS磁盘 + +可以将未挂载的磁盘挂载在对应的BCC虚机下,以下代码将一个CDS挂载在对应的BCC虚机下: + +```go +args := &api.AttachVolumeArgs{ + InstanceId: instanceId, +} +result, err := client.AttachCDSVolume(volumeId, args) +if err != nil { + fmt.Println("attach CDS volume to instance failed:", err) +} else { + fmt.Println("attach CDS volume to instance success: ", result) +} +``` + +> **提示:** +> - CDS磁盘需要挂载在与其处在相同zone下的虚机实例上,否则将返回403错误。 +> - 只有磁盘状态为 Available 且实例状态为 Running 或 Stopped 时才允许挂载,否则调用此接口将返回 409 错误。 + +### 卸载CDS磁盘 + +可以将已挂载的磁盘从对应的BCC虚机上卸载下来,以下代码卸载CDS磁盘: + +```go +args := &api.DetachVolumeArgs{ + InstanceId: instanceId, +} +err := client.DetachCDSVolume(volumeId, args) +if err != nil { + fmt.Println("detach CDS volume from instance failed:", err) +} else { + fmt.Println("detach CDS volume from instance success") +} +``` + +> **提示:** +> - 只有实例状态为 Running 或 Stopped 时,磁盘才可以执行此操作,否则将提示 409 错误。 +> - 如果 volumeId 的磁盘不挂载在 instanceId 的实例上,该操作失败,提示 404 错误。 + +### 释放CDS磁盘 + +用于释放未挂载的CDS磁盘,可指定是否删除磁盘关联的快照,缺省情况下,该磁盘的所有快照将保留,但会删除与磁盘的关联关系: + +```go +err = client.DeleteCDSVolume(volumeId) +if err != nil { + fmt.Println("delete CDS volume failed:", err) +} else { + fmt.Println("delete CDS volume success") +} +``` + +> **提示:** +> - 已挂载的CDS磁盘不能释放,系统盘不能释放。 +> - 磁盘释放后不可恢复。缺省情况下,该磁盘的所有快照将保留,但会删除与磁盘的关联关系。 +> - 只有磁盘状态为 Available 或 Expired 或 Error 时才可以执行此操作,否则将提示 409 错误。 + +## 释放磁盘(POST) +使用以下代码可以释放磁盘: +```go +deleteCDSVolumeArgs := &DeleteCDSVolumeArgs{ + AutoSnapshot: "on", // 是否删除磁盘关联的自动快照,取值为"on"时,会删除磁盘关联的手动快照 + ManualSnapshot: "on", // 是否删除磁盘关联的手动快照,取值为"on"时,会删除磁盘关联的自动快照 + Recycle: "off", // 是否将磁盘回收至回收站,为off时直接释放,不进入回收站 +} +// 设置你要操作的volumeId +volumeId := "your-choose-volume-id" +if err := bccClient.DeleteCDSVolumeNew(volumeId, deleteCDSVolumeArgs); err != nil { + fmt.Println("DeleteCDSVolumeNew failed: ", err) +} else { + fmt.Println("DeleteCDSVolumeNew success") +} +``` + +### 磁盘重命名 + +如下代码可以给一个CDS磁盘重命名 +```go +args := &api.RenameCSDVolumeArgs{ + Name: "testVolume", +} +err := client.RenameCDSVolume(volumeId, args) +if err != nil { + fmt.Println("rename CDS volume failed", err) +} else { + fmt.Println("rename CDS volume success") +} +``` + +### 修改磁盘属性 + +可以使用以下代码修改指定磁盘名称、描述信息: + +```go +args := &api.ModifyCSDVolumeArgs{ + CdsName: "aaa", + Desc: "desc", +} +err := client.ModifyCDSVolume(volumeId, args) +if err != nil { + fmt.Println("modify CDS volume failed: ", err) +} else { + fmt.Println("modify CDS volume success") +} +``` + +### 磁盘计费变更 + +可以使用以下代码变更磁盘计费方式,仅支持后付费转预付费、预付费转后付费两种方式。变更为预付费需要指定购买时长。 + +```go +args := &api.ModifyChargeTypeCSDVolumeArgs{ + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + } + } +} +err := client.ModifyChargeTypeCDSVolume(volumeId, args) +if err != nil { + fmt.Println("modify CDS volume charge type failed:", err) +} else { + fmt.Println("modify CDS volume charge type success") +} +``` + +## 磁盘开通自动续费 +通过以下代码可以开通磁盘自动续费 +```go +// 设置要开通自动续费的磁盘id +volumeId := "your-volume-id" +// 设置按月付费或者按年付费 月是"month",年是"year" +renewTimeUnit := "your-renew-time-unit" +// 设置自动续费的时间 按月是1-9 按年是 1-3 +renewTime := 3 +args := &api.AutoRenewCDSVolumeArgs{ + VolumeId: volumeId, + RenewTimeUnit: renewTimeUnit, + RenewTime: renewTime, +} +if err := bccClient.AutoRenewCDSVolume(args); err != nil { + fmt.Println("Auto renew cds volume failed: ", err) +} else { + fmt.Println("Auto renew cds volume success") +} +``` +>- 注意数据盘才能进行自动续费操作 +>- 注意过期状态磁盘不能进行自动续费操作 +>- 异步接口,可通过查询磁盘详情接口查询磁盘到期时间 + +## 磁盘取消自动续费 +通过以下代码可以取消磁盘自动续费 +```go +// 设置要取消自动续费的磁盘id +volumeId := "your-volume-id" +args := &api.CancelAutoRenewCDSVolumeArgs{ + VolumeId: volumeId, +} +if err := bccClient.CancelAutoRenewCDSVolume(args); err != nil { + fmt.Println("Cancel auto renew cds volume failed: ", err) +} else { + fmt.Println("Cancel auto renew cds volume success") +} +``` +>- 注意数据盘才能取消进行自动续费操作 +>- 异步接口,可通过查询磁盘详情接口查询磁盘到期时间 + +### 磁盘扩容 + +使用以下代码可以对磁盘进行扩大容量操作: + +```go +args := &api.ResizeCSDVolumeArgs{ // 请求扩容参数 + NewCdsSizeInGB: 60, // 扩容大小,单位为GB + NewVolumeType: api.StorageTypeEnhancedPl1, // 扩容类型 +} + +err := client.ResizeCDSVolume(volumeId, args) +if err != nil { + fmt.Println("resize CDS volume failed:", err) +} else { + fmt.Println("resize CDS volume success") +} +``` + +> **提示:** +> - 磁盘只能进行扩大容量,不支持缩小容量。 +> - 只有Available状态的磁盘,才能进行扩容操作 +> - 磁盘扩容是一个异步接口,可通过[查询磁盘详情](#查询磁盘详情)接口查询磁盘扩容状态。 + +### 回滚磁盘 + +可以使用指定磁盘自身的快照回滚磁盘内容,使用以下代码可以对磁盘进行回滚: + +```go +args := &api.RollbackCSDVolumeArgs{ + SnapshotId: snapshotId +} +err := client.RollbackCDSVolume(volumeId, args) +if err != nil { + fmt.Println("rollback CDS volume failed:", err) +} else { + fmt.Println("rollback CDS volume success") +} +``` + +> **提示:** +> - 磁盘状态必须为 Available 才可以执行回滚磁盘操作。 +> - 指定快照id必须是由该磁盘id创建的快照。 +> - 若是回滚系统盘,实例状态必须为 Running 或 Stopped 才可以执行此操作。 +> - 回滚系统盘快照,自本次快照以来的系统盘数据将全部丢失,不可恢复。 + +### 磁盘续费 + +对磁盘的续费操作,可以延长过期时长,以下代码可以对磁盘进行续费: + +```go +args := &api.PurchaseReservedCSDVolumeArgs{ + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + } + } +} +err := client.PurchaseReservedCDSVolume(volumeId, args) +if err != nil { + fmt.Println("purchase reserve CDS volume failed:", err) +} else { + fmt.Println("purchase reserve CDS volume success") +} +``` + +### 释放CDS磁盘(新) + +如下代码可以释放一个CDS磁盘及相关联的快照 +```go +args := &api.DeleteCDSVolumeArgs{ + // 删除与磁盘关联的手动快照 + ManualSnapshot: "on", + // 删除与磁盘关联的自动快照 + AutoSnapshot: "on", + // 是否将磁盘回收至回收站,为off时直接释放,不进入回收站 + Recycle: "off", +} +err := client.DeleteCDSVolumeNew(volumeId, args) +if err != nil { + fmt.Println("create instance failed:", err) +} else { + fmt.Println("create instance ", ) +} +``` + +> **提示:** +> - 该接口用于释放未挂载的CDS磁盘,系统盘不能释放。 +> - 与老接口功能上的区别在于,可以控制是否删除与磁盘关联的快照。 + +### 查询可用区的磁盘信息 +使用以下代码可以查询指定可用区的磁盘信息 +```GO +// 可用区名称;当传入zoneName为空串或为非法zone时,会返回全部可用区的可购买磁盘信息。 +zoneName := "cn-bj-a" +if res, err := bccClient.GetAvailableDiskInfo(zoneName); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) +} else { + fmt.Println("Get the specific zone flavor success, result: ", res) +} +``` + +### 磁盘绑定标签 +使用以下代码可以给指定磁盘绑定标签 +```go +tagArgs := &api.TagVolumeArgs{ + RelationTag: false, // 是否为关联资源绑定标签 + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, +} + +BCC_CLIENT.TagVolume("v-SKy***", tagArgs) +``` + +### 磁盘解绑标签 +使用以下代码可以给指定磁盘绑定标签 +```go +tagArgs := &api.TagVolumeArgs{ + RelationTag: false, // 是否为关联资源解绑标签 + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, +} + + BCC_CLIENT.UntagVolume("v-Crt***", tagArgs) +``` + +## 镜像管理 + +### 创建自定义镜像 +- 该接口用于创建自定义镜像,默认配额20个每账号,创建后的镜像可用于创建实例。 +- 支持通过实例创建和通过快照创建两种方式。 +- 当通过实例创建时,只有 Running 或 Stopped 状态的实例才可以执行成功,否则会提示 409 错误。 +- 仅限通过系统盘快照创建自定义镜像。 +- 当通过快照创建时,只有 Available 状态的快照才可以执行成功,否则会提示 409 错误 +```go +// 待创建的自定义镜像名称,支持大小写字母、数字、中文以及-_ /.特殊字符,必须以字母开头,长度1-65。 +imageName := "your-imageName" +// 当从实例创建镜像时,此参数是指用于创建镜像的实例ID。可选参数,与 snapshotId 不同时存在,同时存在时只取instanceId +instanceId := "your-instanceId" +// 当从快照创建镜像时,此参数是指用于创建镜像的快照ID。可选参数,与 instanceId 不同时存在,同时存在时只取instanceId +snapshotId := "your-snapshotId" +// 是否关联CDS,默认为false +relateCds := true or false +args := &api.CreateImageArgs{ + ImageName: imageName, + InstanceId: instanceId, + SnapshotId: snapshotId, + RelateCds: relateCds, +} +if res, err := bccClient.CreateImage(args); err != nil { + fmt.Println("create image failed: ", err) +} else { + fmt.Println("create image success:", res) +} +``` + +> 注意,创建自定义镜像,默认配额20个每账号。 + +### 查询镜像列表 +- 使用以下代码可以查询有权限的镜像列表。 +- 查询的镜像信息中包括系统镜像、自定义镜像和服务集成镜像。 +- 支持按 imageType 来过滤查询,此参数非必需,缺省为 All,即查询所有类型的镜像。 +- 支持按 imageName 来过滤查询自定义镜像,返回名称中包含该字符串的镜像。此参数非必需,设定ImageName时,必须同时指定ImageType为'Custom'。 +```go +// 批量获取列表的查询的起始位置,是一个由系统生成的字符串,可选参数 +marker := "your-marker" +// 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000,可选参数 +maxKeys := your-maxKeys +// 指定要查询何种类型的镜像,包括All(所有),System(系统镜像/公共镜像),Custom(自定义镜像),Integration(服务集成镜像),Sharing(共享镜像),GpuBccSystem(GPU专用公共镜像),GpuBccCustom(GPU专用自定义镜像),FpgaBccSystem(FPGA专用公共镜像),FpgaBccCustom(FPGA专用自定义镜像),缺省值为All +imageType := "your-imageType" + +args := &api.ListImageArgs{ + Marker: marker, + MaxKeys: maxKeys, + ImageType: imageType, +} + +// 指定过滤查询的自定义镜像名称 +imageName := "your-imageName" + +args := &api.ListImageArgs{ + Marker: marker, + MaxKeys: maxKeys, + ImageType: "Custom", + ImageName: imageName, +} + +if res, err := bccClient.ListImage(args); err != nil { + fmt.Println("get image list failed: ", err) +} else { + fmt.Println("get image list success,res: ", res) +} +``` + +> 具体的镜像类型可详细参考BCC API文档[查询镜像列表](https://cloud.baidu.com/doc/BCC/s/Ajwvynu5r) + +### 查询镜像详情 + +以下代码可以查询镜像详细信息: + +```go +result, err := client.GetImageDetail(imageId) +if err != nil { + fmt.Println("get image detail failed:", err) +} else { + fmt.Println("get image detail success: ", result.Image) +} +``` + +### 删除自定义镜像 +- 该接口用于删除用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能删除。 +- imageId 所指定的镜像不存在,提示404错误。 +- 镜像删除后无法恢复,不能再用于创建、重置实例。 + +以下代码可以删除一个自定义镜像: + +```go +err := client.DeleteImage(imageId) +if err != nil { + fmt.Println("delete image failed:", err) +} else { + fmt.Println("delete image success") +} +``` + +### 跨区域复制自定义镜像 +- 用于用户跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制 +- regions如北京"bj",广州"gz",苏州"su",可多选: +```go + args := &api.RemoteCopyImageArgs{ + Name: "test2", + DestRegion: []string{"gz"}, + } + err := client.RemoteCopyImage(imageId, args) + if err != nil { + fmt.Println("remote copy image failed:", err) + } else { + fmt.Println("remote copy image success") + } +``` + +### 跨区域复制自定义镜像并返回目的region的镜像镜像id +- 用于用户跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制 +- regions如北京"bj",广州"gz",苏州"su",可多选: +```go +args := &api.RemoteCopyImageArgs{ + Name: "test2", + DestRegion: []string{"gz"}, + } +result, err := client.RemoteCopyImageReturnImageIds(imageId, args) + if err != nil { + fmt.Println("remote copy image failed:", err) + } else { + fmt.Println("remote copy image success") + } +``` + +### 取消跨区域复制自定义镜像 + +用于取消跨区域复制自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能复制: + +```go +err := client.CancelRemoteCopyImage(imageId) +if err != nil { + fmt.Println("cancel remote copy image failed:", err) +} else { + fmt.Println("cancel remote copy image success") +} +``` + +### 共享自定义镜像 + +- 该接口用于共享用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能共享。 +- imageId 所指定的镜像不存在,提示404错误。 +- 镜像共享后,被共享的用户可以使用此镜像创建、重置实例。 +- 请求参数中的account和accountId均为可选参数,但不能全部为空,当两个参数同时出现时,服务端会自动去重。 + +```go +// 待共享的用户id +accountId := "your-accountId" +//待共享的用户名 +account := "your-account" +// 待共享的镜像ID +imageId := "your-imageId" + +args := &api.SharedUser{ + AccountId: accountId, + Account: account, +} +if err := bccClient.ShareImage(imageId,args); err != nil { + fmt.Println("ShareImage failed: ", err) +} else { + fmt.Println("ShareImage success") +} +``` + +### 取消共享自定义镜像 + +- 该接口用于取消共享用户自己的指定的自定义镜像,仅限自定义镜像,系统镜像和服务集成镜像不能共享。 +- imageId 所指定的镜像不存在,提示404错误。 +- 镜像取消共享后,被取消共享的用户不能再使用此镜像创建、重置实例。 +- 请求参数中的account和accountId均为可选参数,但不能全部为空,当两个参数同时出现时,服务端会自动去重。 + +```go +// 待共享的用户id +accountId := "your-accountId" +//待共享的用户名 +account := "your-account" +// 待共享的镜像ID +imageId := "your-imageId" + +args := &api.SharedUser{ + AccountId: accountId, + Account: account, +} +if err := bccClient.UnShareImage(imageId,args); err != nil { + fmt.Println("UnShareImage failed: ", err) +} else { + fmt.Println("UnShareImage success") +} +``` + +### 查询镜像已共享用户列表 +- mageId 所指定的镜像不存在,提示404错误。 +用于查询镜像已共享的用户列表: + +```go +result, err := client.GetImageSharedUser(imageId) +if err != nil { + fmt.Println("get image shared user list failed: ", err) +} else { + fmt.Println("get image shared user list success: ", result) +} +``` + +### 根据实例ID批量查询OS信息 + +如下代码可以根据实例的ID来查询相应OS的信息 +```go +args := &api.GetImageOsArgs{ + InstanceIds: []string{instanceId}, +} +result, err := client.GetImageOS(args) +if err != nil { + fmt.Println("get image os failed:", err) +} else { + fmt.Println("get image os success: ", result) +} +``` + +### 镜像链绑定标签 +使用以下代码可以给指定镜像绑定标签 +```go + args := &api.BindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, + } + + BCC_CLIENT.BindImageToTags("your-image-id", args) +``` + +### 镜像解绑标签 +使用以下代码可以给指定镜像绑定标签 +```go + args := &api.BindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, + } + + BCC_CLIENT.UnBindImageToTags("your-image-id", args) +``` + +### 导入镜像 +使用以下代码可以导入个镜像 +```go + args := &api.ImportCustomImageArgs{ + OsName: "Centos", + OsArch: "32", + OsType: "linux", + OsVersion: "6.5", + Name: "import_image_test", + BosURL: "http://cloud.baidu.com/testurl", + } + + result, _ := BCC_CLIENT.ImportCustomImage(args) + fmt.Println(result) +``` + +## 快照管理 + +### 创建快照 + +如下代码可以创建一个快照 +```go +args := &api.CreateSnapshotArgs{ + VolumeId: volumeId, + SnapshotName: "sdk", + Description: "create by sdk", +} +result, err := client.CreateSnapshot(args) +if err != nil { + fmt.Println("create snapshot failed:", err) +} else { + fmt.Println("create snapshot success: ", result) +} +``` + +### 查询快照列表 + +如下代码可以查询当前账户下所有快照的列表 +```go +args := &api.ListSnapshotArgs{} +result, err := client.ListSnapshot(args) +if err != nil { + fmt.Println("list all snapshot failed:", err) +} else { + fmt.Println("list all snapshot success: ", result) +} +``` + +### 查询快照详情 + +如下代码可以查询特定快照的详细信息 +```go +result, err := client.GetSnapshotDetail(snapshotId) +if err != nil { + fmt.Println("get snapshot detail failed:", err) +} else { + fmt.Println("get snapshot detail success: ", result) +} +``` + +### 删除快照 + +如下代码可以删除一个快照 +```go +err := client.DeleteSnapshot(snapshotId) +if err != nil { + fmt.Println("delete snapshot failed:", err) +} else { + fmt.Println("delete snapshot success") +} +``` +### 查询快照链列表 +通过以下代码可以查询快照链列表 +```go +// 设置排序属性:chainId(快照链id,默认值),chainSize(快照链大小),volumeSize(磁盘大小)。可选参数 +orderBy := "your-order-by" +// 设置排序方式:asc(正序,默认值), desc(倒序)。可选参数 +order := "your-order" +// 设置每页容量,默认值为1000,可选参数 +pageSize := 100 +// 设置页数,默认为1,可选参数 +pageNo := 1 +// 设置磁盘ID,该字段非空则只返回这个磁盘的快照链信息,可选 +volumeId := "your-volume-id" +args := &api.ListSnapshotChainArgs{ + OrderBy: orderBy, + Order: order, + PageSize: pageSize, + PageNo: pageNo, + VolumeId: volumeId, +} +if res, err := bccClient.ListSnapshotChain(args); err != nil { + fmt.Println("get snapshot chain list failed: ", err) +} else { + fmt.Println("get snapshot chain list success, SnapshotId: ", res.Snapshot.Id) +} +``` + + +### 快照链绑定标签 +使用以下代码可以给指定快照链绑定标签 +```go + tagArgs := &api.TagSnapshotChain{ + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, + } + + BCC_CLIENT.TagVolume("your-chain-id", tagArgs) +``` + +### 快照链解绑标签 +使用以下代码可以给指定快照链绑定标签 +```go + tagArgs := &api.TagVolumeArgs{ + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, + } + + BCC_CLIENT.UntagSnapshotChain("your-chain-id", tagArgs) +``` + +### 跨区域复制快照 +使用以下代码可以将一份磁盘快照从一个地域复制到其他地域 +```go +args := &api.RemoteCopySnapshotArgs{ + ClientToken: "ClientTokenForTest", + DestRegionInfos: []api.DestRegionInfo{ + { + Name: "NewSnapshotNameInBj", // 快照名称 + DestRegion: "bj", // 目标地域 + }, + { + Name: "NewSnapshotNameInFwh", // 快照名称 + DestRegion: "fwh", // 目标地域 + }, + }, + } + result, _ := BCC_CLIENT.CreateRemoteCopySnapshot("s-S9Hd*****", args) + fmt.Println(result) +``` + +## 自动快照策略管理 + +### 创建自动快照策略 + +如下代码可以创建一个自动快照策略 +```go +args := &api.CreateASPArgs{ + Name: "sdkCreate", + // 设置一天中做快照的时间点,取值为0~23,0为午夜12点 + // 例如设置做快照的时间点为下午两点: + TimePoints: []string{"14"}, + // 设置一周中做快照的时间,取值为0~6,0代表周日,1~6代表周一到周六 + // 例如设置做快照的时间为礼拜五: + RepeatWeekdays: []string{"5"}, + // 设置自动快照保留天数,取-1则永久保留 + RetentionDays: "7", +} +result, err := client.CreateAutoSnapshotPolicy(args) +if err != nil { + fmt.Println("create auto snapshot policy failed:", err) +} else { + fmt.Println("create auto snapshot policy success: ", result) +} +``` + +### 绑定自动快照策略 + +如下代码可以将自动快照策略绑定到某个CDS磁盘 +```go +args := &api.AttachASPArgs{ + // 设置需要绑定的磁盘ID列表 + VolumeIds: []string{volumeId}, +} +err := client.AttachAutoSnapshotPolicy(aspId, args) +if err != nil { + fmt.Println("attach auto snapshot policy with CDS volume failed:", err) +} else { + fmt.Println("attach auto snapshot policy with CDS volume success") +} +``` + +### 解绑自动快照策略 + +如下代码可以将自动快照策略与特定CDS磁盘解除绑定 +```go +args := &api.DetachASPArgs{ + // 设置需要解绑的磁盘ID列表 + VolumeIds: []string{volumeId}, +} +err := client.DetachAutoSnapshotPolicy(aspId, args) +if err != nil { + fmt.Println("detach auto snapshot policy from CDS volume failed:", err) +} else { + fmt.Println("detach auto snapshot policy from CDS volume success") +} +``` + +### 删除自动快照策略 + +如下代码可以删除自动快照策略 +```go +err := client.DeleteAutoSnapshotPolicy(aspId) +if err != nil { + fmt.Println("delete auto snapshot policy failed:", err) +} else { + fmt.Println("delete auto snapshot policy success") +} +``` + +### 查询自动快照策略列表 + +如下代码可以查询到当前账户下当前区域所有自动快照策略的列表 +```go +args := &api.ListASPArgs{} +result, err := client.ListAutoSnapshotPolicy(args) +if err != nil { + fmt.Println("list all auto snapshot policy failed:", err) +} else { + fmt.Println("list all auto snapshot policy success: ", result) +} +``` + +### 查询自动快照策略详情 + +如下代码可以查询到特定自动快照策略的详细信息 +```go +result, err := client.GetAutoSnapshotPolicy(aspId) +if err != nil { + fmt.Println("get auto snapshot policy detail failed:", err) +} else { + fmt.Println("get auto snapshot policy detail success", result) +} +``` + +### 自动快照策略变更 + +如下代码可以更新一个自动快照策略 +```go +args := &api.UpdateASPArgs{ + Name: "testUpdate", + TimePoints: []string{"10"}, + RepeatWeekdays: []string{"0", "1", "7"}, + RetentionDays: "2", + AspId: aspId, +} +err := client.UpdateAutoSnapshotPolicy(args) +if err != nil { + fmt.Println("update auto snapshot policy failed:", err) +} else { + fmt.Println("update auto snapshot policy success") +} +``` + + +## 安全组管理 + +### 查询安全组列表 + + 以下代码可以查询安全组列表: + +```go +args := &api.ListSecurityGroupArgs{} + +// 设置筛选的实例Bcc实例id +args.InstanceId = instanceId + +// 设置筛选的安全组绑定的VPC实例ID +args.VpcId = vpcId + +result, err := client.ListSecurityGroup(args) +if err != nil { + fmt.Println("list all security group failed:", err) +} else { + fmt.Println("list all security group success: ", result) +} +``` + +### 创建安全组 + +以下代码可以创建一个安全组: + +```go +args := &api.CreateSecurityGroupArgs{ + // 设置安全组名称 + Name: "sdk-create", + // 设置安全组规则 + Rules: []api.SecurityGroupRuleModel{ + { + // 设置安全组规则备注 + Remark: "备注", + // 设置协议类型 + Protocol: "tcp", + // 设置端口范围,默认空时为1-65535,可以指定80等单个端口 + PortRange: "1-65535", + // 设置入站出站,取值ingress/egress + Direction: "ingress", + // 设置源IP地址,与sourceGroupId不能同时设置 + SourceIp: "", + }, + }, +} +result, err := client.CreateSecurityGroup(args) +if err != nil { + fmt.Println("create security group failed:", err) +} else { + fmt.Println("create security group success: ", result) +} +``` + +> 同一安全组中的规则以remark、protocol、direction、portRange、sourceIp|destIp、sourceGroupId|destGroupId唯一性索引,重复记录报409错误。 +> protocol的取值(tcp|udp|icmp),默认值为空,代表all。 +> 具体的创建安全组规则接口描述BCC API 文档[创建安全组](https://cloud.baidu.com/doc/BCC/s/0jwvynwij)。 + +### 删除安全组 + +以下代码可以删除指定的安全组: + +```go +err := client.DeleteSecurityGroup(securityGroupId) +if err != nil { + fmt.Println("delete security group failed:", err) +} else { + fmt.Println("delete security group success") +} +``` + +### 授权安全组规则 + +使用以下代码可以在指定安全组中添加授权安全组规则: + +```go +args := &api.AuthorizeSecurityGroupArgs{ + Rule: &api.SecurityGroupRuleModel{ + Remark: "备注", + Protocol: "udp", + PortRange: "1-65535", + Direction: "ingress", + }, +} +err := client.AuthorizeSecurityGroupRule(securityGroupId, args) +if err != nil { + fmt.Println("authorize security group new rule failed:", err) +} else { + fmt.Println("authorize security group new rule success") +} +``` + +> - 同一安全组中的规则以remark、protocol、direction、portRange、sourceIp|destIp、sourceGroupId|destGroupId六元组作为唯一性索引,若安全组中已存在相同的规则将报409错误。 +> - 具体的接口描述BCC API 文档[授权安全组规则](https://cloud.baidu.com/doc/BCC/s/pjwvynxvl)。 + +### 撤销安全组规则 + +使用以下代码可以在指定安全组中撤销指定安全组规则授权: + +```go +args := &api.RevokeSecurityGroupArgs{ + Rule: &api.SecurityGroupRuleModel{ + Remark: "备注", + Protocol: "udp", + PortRange: "1-65535", + Direction: "ingress", + SourceIp: "", + }, +} +err := client.RevokeSecurityGroupRule(securityGroupId, args) +if err != nil { + fmt.Println("revoke security group rule failed:", err) +} else { + fmt.Println("revoke security group rule success") +} +``` + +> - 同一安全组中的规则以remark、protocol、direction、portRange、sourceIp|destIp、sourceGroupId|destGroupId六元组作为唯一性索引,若安全组中不存在对应的规则将报404错误。 +> - 具体的接口描述BCC API 文档[撤销安全组规则](https://cloud.baidu.com/doc/BCC/s/yjwvynxk0)。 + +### 更新普通安全组规则 + +使用以下代码可以在指定普通安全组中更新安全组规则: + +```go +args := &api.UpdateSecurityGroupRuleArgs{ + SecurityGroupRuleId: SecurityGroupRuleId, + Remark: Remark, +} +err := client.UpdateSecurityGroupRule(args) +if err != nil { + fmt.Println("update security group new rule failed:", err) +} else { + fmt.Println("update security group new rule success") +} +``` + +### 删除普通安全组规则 + +使用以下代码可以在指定普通安全组中删除指定安全组规则: + +```go +args := &api.DeleteSecurityGroupRuleArgs{ + SecurityGroupRuleId: SecurityGroupRuleId, +} +err := client.DeleteSecurityGroupRule(args) +if err != nil { + fmt.Println("delete security group rule failed:", err) +} else { + fmt.Println("delete security group rule success") +} +``` + +## 部署集 +### 创建部署集 + +通过以下代码可以根据指定的部署集策略创建部署集 + +```go +// 设置创建部署集的名称 +deploySetName := "your-deploy-set-name" +// 设置创建的部署集的描述信息 +deployDesc := "your-deploy-set-desc" +// 设置创建部署集的策略,BBC实例策略只支持:"tor_ha" +strategy := "tor_ha" +queryArgs := &CreateDeploySetArgs{ + Strategy: strategy, + Name: deploySetName, + Desc: deployDesc, +} +if res, err := bccClient.CreateDeploySet(queryArgs); err != nil { + fmt.Println("Create deploy set failed: ", err) +} else { + fmt.Println("Create deploy set success, result: ", res) +} +``` + +### 查询部署集列表 + +通过以下代码可以查询所有部署集实例的列表及详情信息 + +```go +if res, err := bccClient.ListDeploySets(); err != nil { + fmt.Println("List deploy sets failed: ", err) +} else { + fmt.Println("List deploy sets success, result: ", res) +} +``` +### 修改部署集属性 + +使用以下代码可以修改指定部署集的属性值 + +```go + // 设置创建部署集的名称 + testDeploySetName := "testName" + // 设置创建的部署集的描述信息 + testDeployDesc := "goDesc" + queryArgs := &api.ModifyDeploySetArgs{ + Name: testDeploySetName, + Desc: testDeployDesc, + } + BCC_TestDeploySetId = "DeploySetId" + rep, err := BCC_CLIENT.ModifyDeploySet(BCC_TestDeploySetId, queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +``` + +### 删除指定的部署集 + +使用以下代码删除用户自己的指定的部署集 + +```go +// 设置你要删除的deploySetID +deploySetID := "your-choose-deploy-set-id" +if err := bccClient.DeleteDeploySet(deploySetID); err != nil { + fmt.Println("Delete deploy set failed: ", err) +} +``` + +### 绑定指定的部署集 + +使用以下代码白能搞定用户自己指定的部署集 + +```go + queryArgs := &api.UpdateInstanceDeployArgs{ + // 设置你要绑定的InstanceId + InstanceId: "InstanceId", + // 设置你要绑定的DeploySetIds + DeploySetIds: []string{"DeploySetId"}, + } + rep, err := BCC_CLIENT.UpdateInstanceDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +``` + +### 解绑指定的部署集 + +使用以下代码解绑用户自己指定的部署集 + +```go + queryArgs := &api.DelInstanceDeployArgs{ + // 设置你要解绑的DeploySetId + DeploySetId: "DeploySetId", + // 设置你要解绑的InstanceIds + InstanceIds: []string{"InstanceId"}, + } + rep, err := BCC_CLIENT.DelInstanceDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +``` + +## 密钥对接口 +### 创建密钥对 + +该接口用于创建密钥对,创建后的密钥对可植入实例以实现远程登录虚机。 + +```go +// 待创建的密钥对名称 +name := "your-keypair-name" +// 待创建的密钥对的描述,可选参数 +description := "your-keypair-desc" +args := &api.CreateKeypairArgs{ + Name: name, + Description: description, +} +if res, err := bccClient.CreateKeypair(args); err != nil { + fmt.Println("create keypair failed: ", err) +} else { + fmt.Println("create keypair success,res: ", res) +} +``` + +### 导入密钥对 + +该接口用于用户自行导入创建密钥对。 + +```go +// 待创建的密钥对名称 +name := "your-keypair-name" +// 待创建的密钥对的描述,可选参数 +description := "your-keypair-desc" +publicKey := "your-publickey" +args := &api.ImportKeypairArgs{ + Name: name, + Description: description, + PublicKey: publicKey, +} +if res, err := bccClient.ImportKeypair(args); err != nil { + fmt.Println("create keypair failed: ", err) +} else { + fmt.Println("create keypair success,res: ", res) +} +``` + +### 绑定密钥对 + +该接口用于将所选密钥对(限单个)绑定到所选虚机(支持多台)。 目前一台虚机只能绑定一个密钥对,若操作的虚机已经绑定密钥对,则此操作将替换该虚机原有的密钥对。此操作仅适用于linux系统的虚机,且所选虚机必须处于运行中或关机状态。 + +```go +instanceIds := []string{"your-instanceId"} +keypairId:= "your-keypair-id" +args := &api.AttackKeypairArgs{ + InstanceIds: instanceIds, + KeypairId: keypairId, +} +if err := bccClient.AttachKeypair(args); err != nil { + fmt.Println("attach keypair failed: ", err) +} else { + fmt.Println("attach keypair success") +} +``` + +### 解绑密钥对 + +该接口用于将所选虚机与它们各自绑定的密钥对解绑。 目前一台虚机只能绑定一个密钥对,此操作将使所选虚机与其通过百度云控制台操作所绑定的密钥对解绑,若该虚机不存在这种密钥对,则不进行处理。 此操作仅适用于linux系统的虚机,且所选虚机必须处于运行中或关机状态。 +注: 1)用户所选虚机可能并未绑定任何密钥对,则此操作对该虚机无任何作用; 2)用户可能对所选虚机手动绑定了密钥对,则此操作对其手动绑定的密钥对不产生任何影响; 3)用户如若此前通过百度云控制台操作,为所选虚机绑定过密钥对,且该密钥对状态正常,则此操作将从该虚机中删除该密钥对 +```go +instanceIds := []string{"your-instanceId"} +keypairId:= "your-keypair-id" +args := &api.DetachKeypairArgs{ + InstanceIds: instanceIds, + KeypairId: keypairId, +} +if err := bccClient.DetachKeypair(args); err != nil { + fmt.Println("detach keypair failed: ", err) +} else { + fmt.Println("detach keypair success") +} +``` + +### 删除密钥对 + +该接口用于删除密钥对,已经绑定虚机的密钥对无法被删除。 +```go +keypairId:= "your-keypair-id" +args := &api.DetachKeypairArgs{ + KeypairId: keypairId, +} +if err := bccClient.DeleteKeypair(args); err != nil { + fmt.Println("detach keypair failed: ", err) +} else { + fmt.Println("detach keypair success") +} +``` + +### 查询密钥对详情 + +该接口用于查询单个密钥对的详细信息。 +```go +keypairId:= "your-keypair-id" +if res, err := bccClient.GetKeypairDetail(keypairId); err != nil { + fmt.Println("get keypair failed: ", err) +} else { + fmt.Println("get keypair success,res: ", res) +} +``` + +### 查询密钥对列表 + +该接口用于查询密钥对列表。 +```go +// 批量获取列表的查询的起始位置,是一个由系统生成的字符串,可选参数 +marker := "your-marker" +// 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000,可选参数 +maxKeys := your-maxKeys +// 根据name过滤keypair列表,返回精确匹配结果 +name := "your-keyPairName" + +args := &api.ListKeypairArgs{ + Marker marker, + MaxKeys maxKeys, + Name name, +} +if res, err := bccClient.ListKeypair(args); err != nil { + fmt.Println("get keypair list failed: ", err) +} else { + fmt.Println("get keypair list success,res: ", res) +} +``` + +### 重命名密钥对 + +该接口用于重命名密钥对。 +```go +name := "your-keypair-name" +keypairId:= "your-keypair-id" +args := &api.RenameKeypairArgs{ + Name: name, + KeypairId: keypairId, +} +if err := bccClient.RenameKeypair(args); err != nil { + fmt.Println("update keypair name failed: ", err) +} else { + fmt.Println("update keypair name success") +} +``` + +### 更改密钥对描述 + +该接口用于更改密钥对描述,若用户提供的新的描述内容为空,则删除所操作虚机的描述。 +```go +description := "your-keypair-desc" +keypairId:= "your-keypair-id" +args := &api.KeypairUpdateDescArgs{ + Description: description, + KeypairId: keypairId, +} +if err := bccClient.UpdateKeypairDescription(args); err != nil { + fmt.Println("create keypair failed: ", err) +} else { + fmt.Println("update keypair desc success") +} +``` + +## 其他接口 +### 查询实例套餐规格 + +如下代码可以查询当前可以创建的实例的套餐的规格 +```go +result, err := client.ListSpec() +if err != nil { + fmt.Println("list specs failed: ", err) +``` + +### 查询实例套餐规格(新) + +通过以下代码可以查询实例资源套餐规格列表信息 +```go +// 设置可用区名称,可选 +zoneName := "your-zone-name" +args := &api.ListFlavorSpecArgs{ + ZoneName: zoneName, +} +if res, err := bccClient.ListFlavorSpec(args); err != nil { + fmt.Println("Get specified flavor list failed: ", err) +} else { + fmt.Println("Get specified flavor list success, result: ", res) +} +``` +>- 创建虚机时建议使用参数(spec)指定需要的机型以及配置。 + +### 查询实例套餐价格 +通过以下代码可以查询实例资源套餐规格对应的价格 +```go +// 设置实例规格族 +specId := "your-spec-id" +// 设置实例套餐规格 +spec := "your-spec" +// 设置付费方式,包括Postpaid(后付费),Prepaid(预付费)两种 +paymentTiming := "your-payment-timing" +// 设置可用区名称 +zoneName := "your-zone-name" +// 设置所要购买的实例数量,缺省值为1,可选参数 +purchaseCount := 1 +// 设置时长,[1,2,3,4,5,6,7,8,9,12,24,36],单位:月 +reservationLength := 9 +args := &api.GetPriceBySpecArgs{ + SpecId: specId, + Spec: spec, + PaymentTiming: paymentTiming, + ZoneName: zoneName, + PurchaseCount: purchaseCount, + PurchaseLength: reservationLength, +} +if res, err := bccClient.GetPriceBySpec(args); err != nil { + fmt.Println("Get specified instance's price failed: ", err) +} else { + fmt.Println("Get specified intstance's price success, result: ", res) +} +``` + +### 查询机型的可用区 + +使用以下代码可以查询指定机型支持的可用区列表 +```go +args := &api.ListTypeZonesArgs{ + InstanceType: "", + ProductType: "", + Spec: "bcc.g3.c2m12", + SpecId: "", +} +if res, err := BCC_CLIENT.ListTypeZones(args); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) +} else { + fmt.Println("Get the specific zone flavor success, result: ", res) +} +``` + +### 查询可用区列表 + +如下代码可以所有的可用区的列表 +```go +result, err := client.ListZone() +if err != nil { + fmt.Println("list zone failed: ", err) +} else { + fmt.Println("list zone success: ", result) +} +``` + +### 查询机型的可用区 + +使用以下代码可以查询指定机型支持的可用区列表 +```go +args := &api.ListTypeZonesArgs{ + // 选择实例类型,可以选择N1, N2, N3等 + InstanceType: "", + // 产品类型, 可以选择Prepaid,Postpaid + ProductType: "", + // 实例套餐规格,可以选择bcc.g3.c2m12等 + Spec: "", + // 实例套餐规格族,可以选择g3,ic4等 + SpecId: "", +} +if res, err := BCC_CLIENT.ListTypeZones(args); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) +} else { + fmt.Println("Get the specific zone flavor success, result: ", res) +} +``` + +### 查询bcc、bbc套餐库存 +同时查询bcc、bbc套餐的最大库存。 +只查询用户在console界面上可见的套餐库存。 +查询时需要用户开启查询库存白名单。 +```go +if res, err := BCC_CLIENT.GetAllStocks(); err != nil { + fmt.Println("get all stocks failed: ", err) +} else { + fmt.Println("get all stocks success, result: ", res) +} +``` + +### 部署集粒度实例套餐库存 +查询部署集粒度的实例套餐库存 +```go +// 套餐规格(不需要传本地盘参数,自动解析) +spec := "bcc.l3.c32m128" +// 部署集短id,最多传两个短id(目前一个bcc实例最多绑定两个部署集) +deploySetIds := []string{"dset-12345678"} +args := &api.GetStockWithDeploySetArgs{ + Spec: spec, + DeploySetIds: deploySetIds, +} +if res, err := bccClient.GetStockWithDeploySet(args); err != nil { + fmt.Println("GetStockWithDeploySet failed: ", err) +} else { + fmt.Println("GetStockWithDeploySet success: ", res) +} +``` + +### 通过spec查询实例套餐库存 +通过spec查询实例套餐库存 +```go +// 套餐规格(不需要传本地盘参数,自动解析) +spec := "bcc.l3.c32m128" +// 部署集短id,最多传两个短id,可选参数(目前一个bcc实例最多绑定两个部署集) +deploySetIds := []string{"dset-12345678"} +args := &api.GetStockWithSpecArgs{ + Spec: spec, + DeploySetIds: deploySetIds, +} +if res, err := bccClient.GetStockWithSpec(args); err != nil { + fmt.Println("GetStockWithSpec failed: ", err) +} else { + fmt.Println("GetStockWithSpec success: ", res) +} +``` + +### 查询实例套餐库存 +查询实例资源套餐规格对应的库存。 +```go +// 实例类型 +instanceType := "instanceType" +// CPU核数 +cpuCount := cpuCount +// 内存容量(GB) +memoryCapacityInGB := memoryCapacityInGB +// 可用区名称 +zoneName := "zoneNamen" +// GPU卡类型,GPU和VGPU可填 +gpuCard := "gpuCard" +// GPU卡数量,GPU和VGPU可填 +cardCount := "cardCount" +//本地盘信息 +ephemeralDisks := []EphemeralDisks{{ + "storageType": "ssd", + "sizeInGB": sizeInGB, +}} + +args := &api.CreateInstanceStockArgs{ + InstanceType: instanceType, + CpuCount: cpuCount, + MemoryCapacityInGB: memoryCapacityInGB, + ZoneName: zoneName, + GpuCard: gpuCard, + CardCount: cardCount, + EphemeralDisks: ephemeralDisks, +} +if res, err := bccClient.GetInstanceCreateStock(args); err != nil { + fmt.Println("GetInstanceCreateStock failed: ", err) +} else { + fmt.Println("GetInstanceCreateStock success: ", res) +} +``` + + +### 实例扩缩容库存查询 +实例变配余量查询 +```go +// 实例id +instanceId := "instanceId" +// CPU核数 +cpuCount := cpuCount +// 内存容量(GB) +memoryCapacityInGB := memoryCapacityInGB + +args := &api.CreateInstanceStockArgs{ + InstanceId: instanceId, + CpuCount: cpuCount, + MemoryCapacityInGB: memoryCapacityInGB, +} +if res, err := bccClient.ResizeInstanceStockArgs(args); err != nil { + fmt.Println("ResizeInstanceStockArgs failed: ", err) +} else { + fmt.Println("ResizeInstanceStockArgs success: ", res) +} +``` + +## 磁盘专属集群 +### 创建磁盘专属集群 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &CreateVolumeClusterArgs{ + // 创建一个磁盘磁盘专属集群,若要同时创建多个,可以修改此参数 + PurchaseCount: 1, + // 集群大小,支持最小容量:85TB(87040GB),支持最大容量:1015TB(1039360GB),购买步长:10TB + ClusterSizeInGB: 97280, + // 集群名称 + ClusterName: "dbsc", + // 集群磁盘类型:通用型HDD,通用型SSD + StorageType: StorageTypeHdd, + Billing: &Billing{ + // 只支持预付费 + Reservation: &Reservation{ + // 购买时长 + ReservationLength: 6, + ReservationTimeUnit: "MONTH", + }, + }, + // 自动续费时长 + RenewTimeUnit: "MONTH", + RenewTime: 6, +} +result, err := DBSC_CLIENT.CreateVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +clusterId := result.ClusterIds[0] +fmt.Print(clusterId) +``` + +### 磁盘专属集群列表 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &ListVolumeClusterArgs{ +} +result, err := DBSC_CLIENT.ListVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +fmt.Println(result) +``` + +### 磁盘专属集群详情 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +result, err := DBSC_CLIENT.GetVolumeClusterDetail(clusterId) +if err != nil { + fmt.Println(err) +} +fmt.Println(result) +``` + +### 磁盘专属集群扩容 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &ResizeVolumeClusterArgs{ + NewClusterSizeInGB int `json:"newClusterSizeInGB"` +} +err := DBSC_CLIENT.ResizeVolumeCluster(clusterId, args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群续费 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &PurchaseReservedVolumeClusterArgs{ + Billing: &Billing{ + Reservation: &Reservation{ + // 续费时长 + ReservationLength: 6, + ReservationTimeUnit: "MONTH", + }, + }, +} +clusterId := "clusterId" +err := DBSC_CLIENT.PurchaseReservedVolumeCluster(clusterId, args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群自动续费 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &AutoRenewVolumeClusterArgs{ + ClusterId: clusterId, + RenewTime: 6, + RenewTimeUnit: "month", +} +err := DBSC_CLIENT.AutoRenewVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群取消自动续费 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &CancelAutoRenewVolumeClusterArgs{ + ClusterId: clusterId, +} +err := DBSC_CLIENT.CancelAutoRenewVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +``` + + +# 错误处理 + +GO语言以error类型标识错误,BCC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BCC服务返回的错误 + +用户使用SDK调用BCC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// bccClient 为已创建的BCC Client对象 +instanceDetail, err := bccClient.GetInstanceDetail(instanceId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get instance detail success: ", instanceDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BCC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BCC服务端出现异常时,BCC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BCC错误返回](https://cloud.baidu.com/doc/BCC/s/Ojwvyo6nc) + +# 版本变更记录 + +## v0.9.1 [2019-09-26] + +首次发布: + + - 创建、查看、列表、启动、停止、重启、重装、删除实例,修改实例密码、安全组、属性、子网等,为实例续费 + - 创建、查看、列表、挂载、卸载、扩容、回滚、重命名、删除CDS磁盘,为磁盘续费,修改磁盘属性和计费方式等 + - 创建、查看、列表、删除、跨区域复制、取消跨区域复制、共享、取消共享镜像,删除自定义镜像,查询已共享的用户列表及根据实例ID查询OS信息等 + - 创建、查看、列表、删除快照 + - 创建、查看、列表、绑定、解绑、变更、删除自动快照策略 + - 创建、查询、删除安全组,为安全组授权和撤销规则 + - 查询实例套餐规格和查询可用区列表 diff --git a/bce-sdk-go/doc/BCI.md b/bce-sdk-go/doc/BCI.md new file mode 100644 index 0000000..cbfde40 --- /dev/null +++ b/bce-sdk-go/doc/BCI.md @@ -0,0 +1,565 @@ +# BCI服务 + +# 概述 + +本文档主要介绍BCI GO SDK的使用。在使用本文档前,您需要先了解BCI的一些基本知识,并已开通了BCI服务。若您还不了解BCI,可以参考[产品描述](https://cloud.baidu.com/doc/BCI/s/Ujxessavb)和[操作指南](https://cloud.baidu.com/doc/BCI/s/elc8ri0af)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BCI服务域名](https://cloud.baidu.com/doc/BCI/s/Qlf0qoqw1)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”和“华北-保定”四个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BD | bci.bd.baidubce.com | HTTP and HTTPS +GZ | bci.gz.baidubce.com | HTTP and HTTPS +SU | bci.su.baidubce.com | HTTP and HTTPS +BD | bci.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云BCI,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BCI做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BCI Client + +BCI Client是BCI服务的客户端,为开发者与BCI服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BCI Client + +通过AK/SK方式访问BCI,用户可以参考如下代码新建一个BCI Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bci" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BCIClient + bciClient, err := bci.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BCI的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bci.bj.baidubce.com`。 + +### 使用STS创建BCI Client + +**申请STS token** + +BCI可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BCI,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建BCI Client** + +申请好STS后,可将STS Token配置到BCI Client中,从而实现通过STS Token创建BCI Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BCI Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/bci" //导入BCI服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BCI服务的Client对象,Endpoint使用默认值 + bciClient, err := bci.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bci.bj.baidubce.com") + if err != nil { + fmt.Println("create bci client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + bciClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BCI Client时,无论对应BCI服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问BCI + +BCI支持HTTPS传输协议,您可以通过在创建BCI Client对象时指定的Endpoint中指明HTTPS的方式,在BCI GO SDK中使用HTTPS访问BCI服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +ENDPOINT := "https://bci.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +bciClient, _ := bci.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BCI Client + +如果用户需要配置BCI Client的一些细节的参数,可以在创建BCI Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BCI服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +//创建BCI Client对象 +AK, SK := , +ENDPOINT := "bci.bj.baidubce.com" +client, _ := bci.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +AK, SK := , +ENDPOINT := "bci.bj.baidubce.com" +client, _ := bci.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +AK, SK := , +ENDPOINT := "bci.bj.baidubce.com" +client, _ := bci.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BCI时,创建的BCI Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BCI Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +百度智能云容器实例(Baidu Container Instance,即BCI)提供无服务器化的容器资源。您只需提供容器镜像及启动容器所需的配置参数,即可运行容器,而无需关心这些容器如何被调度部署到底层的物理服务器资源中。BCI服务将为您完成IaaS层资源的调度和运维工作,从而简化您对容器的使用流程,降低部署和维护成本。同时BCI只会对您创建容器时申请的资源计费,因此实现真正的按需付费。结合容器本身秒级启动的特性,BCI可以帮助您在百度智能云上构建灵活弹性且易于维护的大规模容器集群。 + +> 注意: +> - BCI使用限制可以参考[BCI使用限制](https://cloud.baidu.com/doc/BCI/s/jjxv2cma2)。 +> - 创建BCI需要实名认证,若未通过实名认证可以前往[百度智能云官网控制台](https://console.bce.baidu.com/qualify/#/qualify/result)中的安全认证下的实名认证中进行认证。 + +## 创建实例 + +使用以下代码可以创建一个BCI实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +args := &CreateInstanceArgs{ + // 保证请求幂等性 + ClientToken: "random-uuid", + // BCI实例名称 + Name: "instanceName", + // 可用区名称 + ZoneName: "zoneC", + // 实例所属于的安全组Id + SecurityGroupIds: []string{"g-59gf44p4jmwe"}, + // 实例所属的子网Id + SubnetIds: []string{"sbn-g463qx0aqu7q"}, + // 实例重启策略 + RestartPolicy: "Always", + // 弹性公网IP + EipIp: "106.13.234.xx", + // 自动创建EIP + AutoCreateEip: false, + // 弹性公网名称 + EipName: "zwj-test-eip", + // EIP线路类型 + EipRouteType: "BGP", + // 公网带宽,单位为Mbps + EipBandwidthInMbps: 1, + // 计费方式 + EipBillingMethod: "ByTraffic", + // 实例所需的 GPU 资源型号 + GPUType: "Nvidia A10 PCIE", + // 程序的缓冲时间,用于处理关闭之前的操作 + TerminationGracePeriodSeconds: 0, + // 主机名称 + HostName: "zwj-go-sdktest", + // 用户标签列表 + Tags: []Tag{ + { + TagKey: "appName", + TagValue: "zwj-test", + }, + }, + // 镜像仓库凭证信息 + ImageRegistryCredentials: []ImageRegistryCredential{ + { + Server: "docker.io/wywcoder", + UserName: "wywcoder", + Password: "Qaz123456", + }, + }, + // 业务容器组 + Containers: []Container{ + { + Name: "container01", + Image: "registry.baidubce.com/bci-zjm-public/ubuntu:18.04", + Memory: 0.5, + CPU: 0.25, + GPU: 0, + WorkingDir: "", + ImagePullPolicy: "IfNotPresent", + Commands: []string{"/bin/sh"}, + Args: []string{"-c", "sleep 30000 && exit 0"}, + VolumeMounts: []VolumeMount{ + { + MountPath: "/usr/local/nfs", + ReadOnly: false, + Name: "nfs", + Type: "NFS", + }, + { + MountPath: "/usr/local/share", + ReadOnly: false, + Name: "emptydir", + Type: "EmptyDir", + }, + { + MountPath: "/config", + ReadOnly: false, + Name: "configfile", + Type: "ConfigFile", + }, + }, + Ports: []Port{ + { + Port: 8099, + Protocol: "TCP", + }, + }, + EnvironmentVars: []Environment{ + { + Key: "java", + Value: "/usr/local/jre", + }, + }, + LivenessProbe: &Probe{ + InitialDelaySeconds: 0, + TimeoutSeconds: 0, + PeriodSeconds: 0, + SuccessThreshold: 0, + FailureThreshold: 0, + TerminationGracePeriodSeconds: 0, + Exec: &ExecAction{ + Command: []string{"echo 0"}, + }, + }, + Stdin: false, + StdinOnce: false, + Tty: false, + SecurityContext: &ContainerSecurityContext{}, + }, + }, + // Init 容器 + InitContainers: []Container{}, + // 数据卷信息 + Volume: &Volume{ + Nfs: []NfsVolume{ + { + Name: "nfs", + Server: "xxx.cfs.gz.baidubce.com", + Path: "/", + }, + }, + EmptyDir: []EmptyDirVolume{ + { + Name: "emptydir", + }, + }, + ConfigFile: []ConfigFileVolume{ + { + Name: "configfile", + ConfigFiles: []ConfigFileDetail{ + { + Path: "podconfig", + File: "filenxx", + }, + }, + }, + }, + }, +} +result, err := client.CreateInstance(args) +if err != nil { + fmt.Printf("CreateInstance error: %+v \n", err) + return +} +fmt.Printf("CreateInstance success, bci instance id: %+v \n", result.InstanceId) +``` + +> 注意: +> - 保证请求幂等性。从您的客户端生成一个参数值,确保不同请求间该参数值唯一。只支持ASCII字符,且不能超过64个字符。 +> - BCI实例名称,即容器组名称;支持长度为2~253个英文小写字母、数字或者连字符(-),不能以连接字符开始或结尾。如果填写大写字母,后台会自动转为小写。 +> - 实例重启策略。取值范围:Always:总是重启,Never:从不重启,OnFailure:失败时重启。默认值:Always。 +> - 自动创建EIP,并绑定到BCI实例上。只有当eipIp为空的情况下,此字段才生效。默认值为:false。 +> - EIP线路类型,包含标准BGP(BGP)和增强BGP(BGP_S),默认标准BGP。当autoCreateEip为true时,此字段才生效。默认值为:BGP。 +> - 公网带宽,单位为Mbps。对于预付费以及按使用带宽计费的后付费EIP,标准型BGP限制为1~500之间的整数,增强型BGP限制为100~5000之间的整数(代表带宽上限);对于按使用流量计费的后付费EIP,标准型BGP限制为1~200之间的整数(代表允许的带宽流量峰值)。如果填写浮点数会向下取整。当autoCreateEip为true时,此字段才生效。默认值为100。 +> - 付款时间,预支付(Prepaid)和后支付(Postpaid)当autoCreateEip为true时,此字段才生效。默认值为Postpaid。 +> - 计费方式,按流量(ByTraffic)、按带宽(ByBandwidth)、按增强95(ByPeak95)(只有共享带宽后付费支持)。当autoCreateEip为true时,此字段才生效。增强型BGP_S不支持按流量计费(ByTraffic),需要按带宽计费(ByBandwidth)。默认值为ByTraffic。 +> - 支持创建 EIP同时开通自动续费单位,取值为 month 获 year (默认 month)。当autoCreateEip为true时,此字段才生效。默认值为month。 +> - 支持创建 EIP同时开通自动续费时间。根据autoRenewTimeUnit的取值有不同的范围,month 为1到9,year 为1到3。当autoCreateEip为true时,此字段才生效。默认值为1。 +> - 实例所需的 GPU 资源型号。目前仅支持:Nvidia A10 PCIE。 + +## 查询实例列表 + +使用以下代码可以查询BCI实例列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +args := &ListInstanceArgs{ + // 查询关键字名称 + KeywordType: "podId", + // 查询关键字值 + keyword: "p-xxx", + // 表示下一个查询开始的marker,marker为空表示没有下一个 + Marker: "", + // 每页包含的最大数量 + MaxKeys: 5, +} +result, err := client.ListInstances(args) +fmt.Printf("ListInstances result: %+v, err: %+v \n", result, err) +``` + +> 注意: +> - 查询关键字名称,取值范围:name、podId。 +> - 表示下一个查询开始的marker,marker为空表示没有下一个。说明:首次查询时无需设置该参数,后续查询的marker从返回结果中获取。 +> - 每页包含的最大数量,最大数量通常不超过1000,缺省值为10。maxKeys的取值范围:[1, 1000]之间的正整数。 + +## 查询实例详情 + +使用以下代码可以查询BCI实例详情。 +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +args := &GetInstanceArgs{ + // BCI实例ID + InstanceId: "p-xxx", +} +result, err := client.GetInstance(args) +fmt.Printf("ListInstances result: %+v, err: %+v \n", result, err) +``` + +## 删除实例 + +使用以下代码可以删除BCI实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +args := &DeleteInstanceArgs{ + // 待删除的BCI实例ID + InstanceId: "p-xxxx", + // 释放关联资源 + RelatedReleaseFlag: true, +} +err := client.DeleteInstance(args) +fmt.Printf("DeleteInstance err: %+v\n", err) +``` + +> 注意: +> - 释放关联资源,目前只有EIP资源,默认值为false。 + +## 批量删除实例 + +使用以下代码可以批量删除BCI实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/bci" + +args := &BatchDeleteInstanceArgs{ + // 待删除的BCI实例ID列表 + InstanceIds: []string{"p-axxx", "p-bxxx"}, + // 释放关联资源 + RelatedReleaseFlag: true, +} +err := client.BatchDeleteInstance(args) +fmt.Printf("BatchDeleteInstance err: %+v\n", err) +``` + +> 注意: +> - 释放关联资源,目前只有EIP资源,默认值为false。 + +# 错误处理 + +GO语言以error类型标识错误,BCI支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BCI服务返回的错误 + +用户使用SDK调用BCI相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` go +// client 为已创建的BCI Client对象 +args := &bci.ListInstanceArgs{} +result, err := client.ListInstances(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BCI发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当BCI服务端出现异常时,BCI服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BCI错误码](https://cloud.baidu.com/doc/BCI/s/Vlf18flyw) + +## SDK日志 + +BCI GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +BCI GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the BCI go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the BCI go sdk") +``` + + +# 版本变更记录 + +首次发布: + + - 支持创建实例、查询实例列表、查询实例详情、删除实例、批量删除实例接口。 \ No newline at end of file diff --git a/bce-sdk-go/doc/BCM.md b/bce-sdk-go/doc/BCM.md new file mode 100644 index 0000000..b5d5ddd --- /dev/null +++ b/bce-sdk-go/doc/BCM.md @@ -0,0 +1,289 @@ +# BCM服务 + +# 概述 + +本文档主要介绍BCM GO SDK的使用。在使用本文档前,您需要先了解 +[BCM的基本知识](https://cloud.baidu.com/doc/BCM/index.html)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BCM域名](https://cloud.baidu.com/doc/BCM/s/5jwvym49g)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云BCM,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BCM做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BCM Client + +BCM Client是BCM控制面服务的客户端,为开发者与BCM控制面服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BCM Client + +通过AK/SK方式访问BCM,用户可以参考如下代码新建一个BCM Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bcm" //导入BCM服务模块 +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BcmClient + bcmClient, err := bcm.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BCM/index.html)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BCM的控制面服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcm.bj.baidubce.com`。 + +### 使用STS创建BCM Client + +**申请STS token** + +BCM可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BCM,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建BCM Client** + +申请好STS后,可将STS Token配置到BCM Client中,从而实现通过STS Token创建BCM Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BCM Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/bcm" //导入BCM服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BCM控制面服务的Client对象,Endpoint使用默认值 + bcmClient, err := bcm.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create bcm client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + bcmClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BCM Client时,无论对应BCM服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BCM + +BCM支持HTTPS传输协议,您可以通过在创建BCM Client对象时指定的Endpoint中指明HTTPS的方式,在BCM GO SDK中使用HTTPS访问BCM服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcm" + +ENDPOINT := "https://bcm.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +bcmClient, _ := bcm.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BCM Client + +如果用户需要配置BCM Client的一些细节的参数,可以在创建BCM Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BCM服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcm" + +//创建BCM Client对象 +AK, SK := , +ENDPOINT := "bcm.bj.baidubce.com +client, _ := bcm.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcm" + +AK, SK := , +ENDPOINT := "bcm.bj.baidubce.com" +client, _ := bcm.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bcm" + +AK, SK := , +ENDPOINT := "bcm.bj.baidubce.com" +client, _ := bcm.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BCM时,创建的BCM Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BCM Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +云监控BCM(Cloud Monitor),是针对于云平台的监控服务,包括云产品监控、站点监控、自定义监控、应用监控、报警管理等产品功能,实时监控您云平台中的各种资源。 + +## 查询数据接口 + +### 接口描述 +获取指定指标的一个或多个统计数据的时间序列数据。可获取云产品监控数据、站点监控数据或您推送的自定义监控数据。 + +### 接口限制 +一次返回的数据点数目不能超过1440个。 + +### 请求示例 + +```go +dimensions := map[string]string{ + "InstanceId": "xxx", +} +req := &bcm.GetMetricDataRequest{ + UserId: "xxx", + Scope: "BCE_BCC", + MetricName: "vCPUUsagePercent", + Dimensions: dimensions, + Statistics: strings.Split(bcm.Average, ","), + PeriodInSecond: 60, + StartTime: time.Now().UTC().Add(-2 * time.Hour).Format("2006-01-02T15:04:05Z"), + EndTime: time.Now().UTC().Add(-1 * time.Hour).Format("2006-01-02T15:04:05Z"), +} +resp, err := bcmClient.GetMetricData(req) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BCM API 文档[查询数据接口](https://cloud.baidu.com/doc/BCM/s/9jwvym3kb) + +### 批量查询数据接口 + +### 接口描述 +可获取批量实例数据的接口,支持多维度多指标查找。可获取云产品监控数据、站点监控数据或您推送的自定义监控数据。 + +### 接口限制 +一个实例的任意一个指标一次返回的数据点数目不能超过1440个。 + +### 请求示例 +```go +dimensions := map[string]string{ + "InstanceId": "xxx", +} +req := &bcm.BatchGetMetricDataRequest{ + UserId: "xxx", + Scope: "BCE_BCC", + MetricNames: []string{"vCPUUsagePercent", "CpuIdlePercent"}, + Dimensions: dimensions, + Statistics: strings.Split(bcm.Average + "," + bcm.Sum, ","), + PeriodInSecond: 60, + StartTime: time.Now().UTC().Add(-2 * time.Hour).Format("2006-01-02T15:04:05Z"), + EndTime: time.Now().UTC().Add(-1 * time.Hour).Format("2006-01-02T15:04:05Z"), +} +resp, err := bcmClient.BatchGetMetricData(req) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BCM API 文档[查询数据接口](https://cloud.baidu.com/doc/BCM/s/9jwvym3kb) + + + +## 客户端异常 + +客户端异常表示客户端尝试向BCM发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BCM服务端出现异常时,BCM服务端会返回给用户相应的错误信息,以便定位问题 + diff --git a/bce-sdk-go/doc/BEC.md b/bce-sdk-go/doc/BEC.md new file mode 100644 index 0000000..fd286f0 --- /dev/null +++ b/bce-sdk-go/doc/BEC.md @@ -0,0 +1,1434 @@ +# BEC服务 + +# 概述 + +本文档主要介绍BEC GO SDK的使用。在使用本文档前,您需要先了解BEC的一些基本知识。若您还不了解BEC,可以参考[产品描述](https://cloud.baidu.com/doc/BEC/s/xk0p0rsgc)和[入门指南](https://cloud.baidu.com/doc/BEC/s/wk0q5mrcc)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BEC访问域名](https://cloud.baidu.com/doc/BEC/s/Tk41077qy)的部分,理解Endpoint相关的概念。 + +## 获取密钥 + +要使用百度云BEC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BEC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建普通型BEC Client + +BEC Client是BEC服务的客户端,为开发者与BEC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建普通型BEC Client + +通过AK/SK方式访问BEC,用户可以参考如下代码新建一个BEC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bec" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BECClient + becClient, err := bec.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb)》。 + +> **注意:**`ENDPOINT`BEC域名为bec.baidubce.com,更多信息参考:https://cloud.baidu.com/doc/BEC/s/Tk41077qy + + +## 配置HTTPS协议访问BEC + +BEC支持HTTPS传输协议,您可以通过在创建BEC Client对象时指定的Endpoint中指明HTTPS的方式,在BEC GO SDK中使用HTTPS访问BEC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bec" + +ENDPOINT := "https://bec.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +becClient, _ := bec.NewClient(AK, SK, ENDPOINT) +``` + +# 主要接口 + +bec服务为用户提供边缘测vm和边缘测容器的计算服务,支持绑定lb,配置cpu,内存等资源,镜像服务等功能。 + +## vm实例管理 + +### 创建虚机实例 + +通过以下代码,可以创建bec虚机服务 +```go +args := &api.CreateVmServiceArgs{ + // 虚机服务名称 + ServiceName: "xxxxxxx@", + // 镜像id + ImageId: "im-dikfttnj-3-u-guangzhou", + // 密码 + AdminPass: "x123xxx@", + // 系统盘配置 + SystemVolume: &api.SystemVolumeConfig{VolumeType: api.DiskTypeNVME}, + // cpu大小,必须大于1 + Cpu: 1, + // memory大小,必须大于1 + Memory: 2, + // 部署区域,支持创建vpc虚机 + DeployInstances: &[]api.DeploymentInstance{api.DeploymentInstance{City: "HANGZHOU", Region: "EAST_CHINA", ServiceProvider: "CHINA_MOBILE", Replicas: 1,NetworkType: "vpc",}}, + // 镜像类型(默认为bcc、仅使用bec虚机自定义镜像时为bec) + ImageType: "bec", + // 密码或密钥配置 + KeyConfig: &api.KeyConfig{Type: "password", AdminPass: "xxxx123@"} +} + +result, err := client.CreateVmService(args) +if err != nil { + fmt.Println("create bec failed:", err) +} else { + fmt.Println("create bec success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateVmService创建虚机实例](https://cloud.baidu.com/doc/BEC/s/Mkbsrt2yv) + +### 更新BEC虚机服务 + +通过以下代码,可以更新BEC虚机服务 +```go +args := &api.UpdateVmServiceArgs{ + UpdateBecVmForm: api.UpdateBecVmForm{ + // 更新类型,包括 password,replicas,resource,serviceName + Type: bec.api.UpdateVmTypeServiceName, + // 虚机名称 + VmName: "vm-xxxxx"}, + // 服务名称 + ServiceName: "xxxxtest-2", + // 部署区域列表 + DeployInstances: &[]bec.api.DeploymentInstance{bec.api.DeploymentInstance{Region: "SOUTH_CHINA", City: "GUANGZHOU", Replicas: 1, ServiceProvider: api.ServiceChinaUnicom,NetworkType: "vpc",}} +} + +err := client.UpdateVmService(serviceId, args) +if err != nil { + fmt.Println("update bec failed:", err) +} else { + fmt.Println("update bec success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[UpdateVmService更新BEC虚机服务](https://cloud.baidu.com/doc/BEC/s/jkbsxwduk) + +### 获取BEC虚机实例详情 + +通过以下代码,可以获取BEC虚机实例详情 +```go + +result, err := client.GetVmServiceDetail(serviceId) +if err != nil { + fmt.Println("get bec failed:", err) +} else { + fmt.Println("get bec success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[GetVmServiceDetail获取BEC虚机实例详情](https://cloud.baidu.com/doc/BEC/s/Ekbst85ty) + +### 获取BEC虚机服务列表 + +通过以下代码,可以获取BEC虚机服务列表 +```go +args := &api.ListVmServiceArgs{ + // 第几页,默认值1,最小值1 + PageNo: 1, + // 每页个数,默认值1000,最小1,最大1000 + pageSize: 100, +} + +result, err := client.GetVmServiceList(args) +if err != nil { + fmt.Println("list bec failed:", err) +} else { + fmt.Println("list bec success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DescribeLoadBalancerDetail获取BEC虚机服务列表](https://cloud.baidu.com/doc/BEC/s/Okbssrahc) + +### 删除BEC虚机服务 + +通过以下代码,可以删除BEC虚机服务 +```go +err := client.DeleteVmService(serviceId) +if err != nil { + fmt.Println("delete bec failed:", err) +} else { + fmt.Println("delete bec success") +} +``` + +### 操作BEC虚机实例 + +通过以下代码,可以操作BEC虚机实例 +```go +err := client.OperateVmDeployment(vmId, action) +if err != nil { + fmt.Println("operate bec failed:", err) +} else { + fmt.Println("operate bec success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[OperateVmDeployment操作BEC虚机实例](https://cloud.baidu.com/doc/BEC/s/Fkbroisf0) + +### 重装BEC虚机实例系统 + +通过以下代码,可以重装BEC虚机实例系统 +```go +args := &api.ReinstallVmInstanceArg{ + // 镜像id + ImageId: "im-dikfxxxx", + // 密码 + AdminPass: "1xxAxxx@", + // 镜像类型(默认为bcc、仅使用bec虚机自定义镜像时为bec) + ImageType: api.ImageTypeBec, + KeyConfig: &api.KeyConfig{Type: "password", AdminPass: "1xxAxxx@"} +} + +err := client.ReinstallVmInstance(vmId, args) +if err != nil { + fmt.Println("reinstall bec failed:", err) +} else { + fmt.Println("reinstall bec success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[ReinstallVmInstance重装BEC虚机实例系统](https://cloud.baidu.com/doc/BEC/s/Ekbrqqv9j) + +### 更新BEC虚机实例 + +通过以下代码,可以更新BEC虚机实例 +```go +args := &api.UpdateVmDeploymentArgs{ + // 更新类型(password,replicas,resource) + Type: "resource", + // cpu大小 + Cpu: 2, + VmName: "xxxxx-test", + // 系统盘配置 + SystemVolume: &api.SystemVolumeConfig{VolumeType: api.DiskTypeNVME, Name: "sys", PvcName: "lvm-xxxxxx-rootfs"} +} + +err := client.UpdateVmDeployment(vmId, args) +if err != nil { + fmt.Println("update bec instance failed:", err) +} else { + fmt.Println("update bec instance bec success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[UpdateVmDeployment更新BEC虚机实例](https://cloud.baidu.com/doc/BEC/s/wkbroqu5a) + +### 获取BEC虚机实例详情 + +通过以下代码,可以获取BEC虚机实例详情 +```go +err := client.GetVirtualMachine(vmId) +if err != nil { + fmt.Println("get bec instance failed:", err) +} else { + fmt.Println("get bec instance bec success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[GetVirtualMachine获取BEC虚机实例详情](https://cloud.baidu.com/doc/BEC/s/Fkbro0yld) + +### 获取BEC虚机实例列表 + +通过以下代码,可以获取BEC虚机实例列表 +```go +args := &api.ListRequest{ + // 查询实例的关键字类型,instanceId或serviceId,缺省为serviceId + KeywordType: "instanceId", + // 查询实例的关键字 + Keyword: "vm-xxx" +} + +err := client.GetVmInstanceList(args) +if err != nil { + fmt.Println("get bec instance list failed:", err) +} else { + fmt.Println("get bec instance list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[GetVmInstanceList更新BEC虚机实例](https://cloud.baidu.com/doc/BEC/s/Qkbrebfbz) + +## 容器相关服务 + +### 创建BEC容器服务 + +通过以下代码,可以创建BEC容器服务 +```go +args := &api.CreateServiceArgs{ + // 服务名称 + ServiceName: "xxxx-1-test", + // 付费方式,只支持后付费postpay + PaymentMethod: "postpay", + // 容器组名称 + ContainerGroupName: "cg1", + // 当needPublicIp为true时,用于设置外网带宽,范围为1Mps到2048Mps,可开通白名单上限增加到5120Mps + Bandwidth: 100, + // 是否购买公网IP,缺省否 + NeedPublicIp: false, + // 容器组信息 + Containers: &[]api.ContainerDetails{ + api.ContainerDetails{ + Name: "container01", + Cpu: 1, + Memory: 2, + ImageAddress: "hub.baidubce.com/public/mysql", + ImageVersion: "5.7", + Commands: []string{"sh", + "-c", + "echo OK!&& sleep 3660"}, + VolumeMounts: &[]api.V1VolumeMount{ + api.V1VolumeMount{ + MountPath: "/temp", + Name: "emptydir01", + }, + },}, + }, + // 部署地域信息 + DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{ + Region: "EAST_CHINA", + Replicas: 1, + City: "SHANGHAI", + ServiceProvider: "CHINA_TELECOM"}, + }, + // 存储卷信息 + Volumes: &api.Volume{ + EmptyDir: &[]api.EmptyDir{ + api.EmptyDir{Name: "emptydir01"}, + }, + }, + // 标签信息 + Tags: &[]api.Tag{ + api.Tag{ + TagKey: "a", + TagValue: "1" + }, + }, +} +err := client.CreateService(args) +if err != nil { + fmt.Println("create bec service failed:", err) +} else { + fmt.Println("create bec service success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateService创建BEC容器服务](https://cloud.baidu.com/doc/BEC/s/Wk3zawt4x) + +### 删除BEC容器服务 + +通过以下代码,可以删除BEC容器服务 +```go +err := client.DeleteService(serviceId) +if err != nil { + fmt.Println("delete bec service failed:", err) +} else { + fmt.Println(""delete bec service success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DeleteService删除BEC容器服务](https://cloud.baidu.com/doc/BEC/s/Uk3zb1nwe) + +### 更新BEC服务 + +通过以下代码,可以更新BEC服务 +```go +getReq := &api.UpdateServiceArgs{ServiceName: "s-f9ngbkbc", Type: api.UpdateServiceTypeReplicas, DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{Region: api.RegionEastChina, Replicas: 1, City: "HANGZHOU", ServiceProvider: api.ServiceChinaMobile}, +}} +res, err := CLIENT.UpdateService("s-f9ngbkbc", getReq) +if err != nil { + fmt.Println("update bec service failed:", err) +} else { + fmt.Println("update bec service success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[更新BEC服务](https://cloud.baidu.com/doc/BEC/s/Tk3zaz9by) + +### 启动BEC服务 + +通过以下代码,可以启动BEC服务 +```go +res, err := CLIENT.ServiceAction("s-xxx", api.ServiceActionStart) +if err != nil { + fmt.Println("start bec service failed:", err) +} else { + fmt.Println("start bec service success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[启动BEC服务](https://cloud.baidu.com/doc/BEC/s/sk3zb12kh) + +### 停止BEC服务 + +通过以下代码,可以停止BEC服务 +```go +res, err := CLIENT.ServiceAction("s-xxx", api.ServiceActionStop) +if err != nil { + fmt.Println("stop bec service failed:", err) +} else { + fmt.Println("stop bec service success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[停止BEC服务](https://cloud.baidu.com/doc/BEC/s/gk412paz1) + + + + +### 获取BEC容器服务详情 + +通过以下代码,可以获取BEC容器服务详情 +```go +result, err := client.GetService(serviceId) +if err != nil { + fmt.Println("get bec pod service failed:", err) +} else { + fmt.Println("get bec pod service success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[GetService获取BEC容器服务详情](https://cloud.baidu.com/doc/BEC/s/Hk413e70s) + +### 查询部署详情 + +通过以下代码,可以查询部署详情 +```go +result, err := CLIENT.GetPodDeployment("sts-xxxx") +if err != nil { + fmt.Println("get bec pod deployment failed:", err) +} else { + fmt.Println("get bec pod deployment success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询部署详情](https://cloud.baidu.com/doc/BEC/s/Lk3fmy6v2) + +### 查询部署资源监控 + +通过以下代码,可以获取查询部署资源监控 +```go +res, err := CLIENT.GetPodDeploymentMetrics("sts-xxxx", api.MetricsTypeMemory, "", 1661270400, 1661356800, 0) +if err != nil { + fmt.Println("get bec pod deployment failed:", err) +} else { + fmt.Println("get bec pod deployment success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询部署资源监控](https://cloud.baidu.com/doc/BEC/s/Wk3h9n5n1) +### 更新部署副本数 + +通过以下代码,可以获取更新部署副本数 +```go +getReq := &api.UpdateDeploymentReplicasRequest{ + Replicas: 2, +} +res, err := CLIENT.UpdatePodDeploymentReplicas("sts-xxxx", getReq) +if err != nil { + fmt.Println("update bec pod deployment replicas failed:", err) +} else { + fmt.Println("update bec pod deployment replicas success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[更新部署副本数](https://cloud.baidu.com/doc/BEC/s/Wk3h9n5n1) + +### 删除部署 + +通过以下代码,可以删除部署 +```go +getReq := &[]string{"sts-xxxx"} +res, err := CLIENT.DeletePodDeployment(getReq) +if err != nil { + fmt.Println("delete bec pod deployment failed:", err) +} else { + fmt.Println("delete bec pod deployment success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除部署](https://cloud.baidu.com/doc/BEC/s/jk3h9bcr5) + +### 查询pod列表 + +通过以下代码,可以查询pod列表 +```go +res, err := CLIENT.GetPodList(1, 100, "", "", "", "", "") +if err != nil { + fmt.Println("get bec pod list failed:", err) +} else { + fmt.Println("get bec pod list success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询pod列表](https://cloud.baidu.com/doc/BEC/s/2k3i3ucrh) + +### 查询pod资源监控 + +通过以下代码,可以查询pod资源监控 +```go +res, err := CLIENT.GetPodMetrics("sts-xxx-0", api.MetricsTypeMemory, "", 1661270400, 1661356800, 0) + +if err != nil { + fmt.Println("get bec pod metrics failed:", err) +} else { + fmt.Println("get bec pod metrics success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询pod资源监控](https://cloud.baidu.com/doc/BEC/s/Rk3ibrrdu) + +### 查询pod详情 + +通过以下代码,可以查询pod详情 +```go +res, err := CLIENT.GetPodDetail("sts-xzzxxxx-0") +if err != nil { + fmt.Println("get bec pod detail failed:", err) +} else { + fmt.Println("get bec pod detail success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询pod详情](https://cloud.baidu.com/doc/BEC/s/Ok3i3vgl7) + +### 重启容器组 + +通过以下代码,可以重启容器组 +```go +err := CLIENT.RestartPod("sts-xxxxx-0") +if err != nil { + fmt.Println("restart bec pod failed:", err) +} else { + fmt.Println("restart bec pod success: ", res) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[重启容器组](https://cloud.baidu.com/doc/BEC/s/Jk3ib7df8) + + +## 负载均衡器 + +### 创建负载均衡 + +通过以下代码,可以创建负载均衡 +```go +args := &api.CreateBlbArgs{ + // 负载均衡名称 + BlbName: "xxxx-test", + // 负载均衡所在区域信息 + Region: api.RegionEastChina, + // 负载均衡所在城市信息 + City: "HANGZHOU", + LbType: "vm", + ServiceProvider: api.ServiceChinaMobile + // 创建applb + NetworkType: "vpc", +} +err := client.CreateBlb(args) +if err != nil { + fmt.Println("remove backend servers failed:", err) +} else { + fmt.Println("remove backend servers success: ") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateBlb创建负载均衡](https://cloud.baidu.com/doc/BEC/s/zkbrnvg6w) + +### 删除负载均衡 + +通过以下代码,可以删除负载均衡 +```go + +err := client.DeleteBlb(BLBID) +if err != nil { + fmt.Println("create TCP Listener failed:", err) +} else { + fmt.Println("create TCP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DeleteBlb删除负载均衡](https://cloud.baidu.com/doc/BEC/s/qkbrnx86o) + +### 查看负载均衡详情 + +通过以下代码,可以查看负载均衡详情 +```go +err := client.GetBlbDetail(BLBID) +if err != nil { + fmt.Println("update TCP Listener failed:", err) +} else { + fmt.Println("update TCP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[GetBlbDetail查看负载均衡详情](https://cloud.baidu.com/doc/BEC/s/ekbrnvz0c) + +### 创建监听设置 + +通过以下代码,可以创建监听设置 +```go +args := &api.BlbMonitorArgs{ + // 转发规则 + LbMode: api.LbModeWrr, + // 负载均衡端口 + FrontendPort: &api.Port{Protocol: api.ProtocolUdp,Port: 80}, + // 后端端口 + BackendPort: 80, + // 健康检查设置 + HealthCheck: &api.HealthCheck{HealthCheckString: "", HealthCheckType: "udp", HealthyThreshold: 1000, + UnhealthyThreshold: 1000, + TimeoutInSeconds: 900, + IntervalInSeconds: 3} +} + +result, err := client.CreateBlbMonitorPort(BLBID, args) +if err != nil { + fmt.Println("describe TCP Listener failed:", err) +} else { + fmt.Println("describe TCP Listener success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateBlbMonitorPort创建监听设置](https://cloud.baidu.com/doc/BEC/s/ikbrnxqnj) + +### 删除监听设置 + +通过以下代码,可以删除监听设置 +```go +// 负载均衡监听端口列表 +args := &[]api.Port{{ + Protocol: api.ProtocolUdp, + Port: 80}, +} +err := client.DeleteBlbMonitorPort(BLBID, args) +if err != nil { + fmt.Println("create UDP Listener failed:", err) +} else { + fmt.Println("create UDP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DeleteBlbMonitorPort删除监听设置](https://cloud.baidu.com/doc/BEC/s/dkbrnxzcw) + +### 创建后端服务器 + +通过以下代码,可以创建后端服务器 +```go +args := &&api.CreateBlbBindingArgs{ + // 创建后端服务器请求 + BindingForms: &[]api.BlbBindingForm{api.BlbBindingForm{DeploymentId: "xxxx", PodWeight: &[]api.Backends{api.Backends{Name: "xxxxxx", Ip: "172.xx.x.xx", Weight: 100}}, +}}} +err := client.CreateBlbBinding(BLBID, args) +if err != nil { + fmt.Println("update UDP Listener failed:", err) +} else { + fmt.Println("update UDP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateBlbBinding创建后端服务器](https://cloud.baidu.com/doc/BEC/s/Ckbrnyb8q) + +### 删除后端服务器 + +通过以下代码,可以删除后端服务器 +```go +args := &api.DeleteBlbBindPodArgs{ + // 删除后端服务器请求 + PodWeightList: &[]api.Backends{ + api.Backends{Name: "vm-xxx", Ip: "172.16.9xxx.xxx", Weight: 10}}, + DeploymentIds: []string{"vmrs-xxxx"}, +} + +result, err := client.DeleteBlbBindPod(BLBID, args) +if err != nil { + fmt.Println("describe UDP Listener failed:", err) +} else { + fmt.Println("describe UDP Listener success: ", result) +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DeleteBlbBindPod删除后端服务器](https://cloud.baidu.com/doc/BEC/s/2kbrnz2wi) + +## 镜像相关 + +### 创建BEC虚机镜像 + +通过以下代码,可以创建BEC虚机镜像 +```go +args := &api.CreateVmImageArgs{ + // 虚机实例id + VmId: "vm-xxxx-1", + // 镜像名称(长度1-65) + Name: "xxxx-test" +} +err := client.CreateVmImage(args) +if err != nil { + fmt.Println("create HTTP Listener failed:", err) +} else { + fmt.Println("create HTTP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[CreateVmImage创建BEC虚机镜像](https://cloud.baidu.com/doc/BEC/s/Uklg7azo0) + +### 批量删除BEC虚机镜像 + +通过以下代码,可以批量删除BEC虚机镜像 +```go +args := []string{"xxxxxx-1", "xxxxxx-2"} +err := client.DeleteVmImage(args) +if err != nil { + fmt.Println("update HTTP Listener failed:", err) +} else { + fmt.Println("update HTTP Listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[DeleteVmImage批量删除BEC虚机镜像](https://cloud.baidu.com/doc/BEC/s/Sklgbqn7g) + +## 部署集相关 +### 创建部署集 + +通过以下代码,可以创建部署集 +```go +getReq := &api.CreateDeploySetArgs{ +Name: "xxx_test", +Desc: "xxx-test", +} +res, err := CLIENT.CreateDeploySet(getReq) +if err != nil { + fmt.Println("create deploy set failed:", err) +} else { + fmt.Println("create deploy set success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建部署集](https://cloud.baidu.com/doc/BEC/s/Sl0t89s48) + +### 修改部署集 + +通过以下代码,可以修改部署集 +```go +getReq := &api.CreateDeploySetArgs{ + Name: "xxx_test", + Desc: "xxx-test", +} +err := CLIENT.UpdateDeploySet("dset-xxx", getReq) +res, err := CLIENT.CreateDeploySet(getReq) +if err != nil { + fmt.Println("update deploy set failed:", err) +} else { + fmt.Println("update deploy set success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[修改部署集](https://cloud.baidu.com/doc/BEC/s/Ml0tb0d5s) + +### 获取部署集列表 + +通过以下代码,可以获取部署集列表 +```go +getReq := &api.ListRequest{} +res, err := CLIENT.GetDeploySetList(getReq) +if err != nil { + fmt.Println("get deploy set list failed:", err) +} else { + fmt.Println("get deploy set list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[获取部署集列表](https://cloud.baidu.com/doc/BEC/s/Cl0t8xm4g) + +### 获取部署集详情 + +通过以下代码,可以获取部署集详情 +```go +res, err := CLIENT.GetDeploySetDetail("dset-xxxx") +if err != nil { + fmt.Println("get deploy set details failed:", err) +} else { + fmt.Println("get deploy set details success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[获取部署集详情](https://cloud.baidu.com/doc/BEC/s/9l0talan1) + +### 删除部署集 + +通过以下代码,可以删除部署集 +```go +err := CLIENT.DeleteDeploySet("dset-y4tumnel") +if err != nil { + fmt.Println("delete deploy set failed:", err) +} else { + fmt.Println("delete deploy set success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除部署集](https://cloud.baidu.com/doc/BEC/s/1l0ulgpsv) + +### 虚机实例调整部署集 + +通过以下代码,可以调整虚机实例的部署集 +```go +getReq := &api.UpdateVmDeploySetArgs{ +InstanceId: "vm-xxxx", +DeploysetIdList: []string{"dset-xxxx"}, +} +err := CLIENT.UpdateVmInstanceDeploySet(getReq) +if err != nil { + fmt.Println("update vm instance deploy set failed:", err) +} else { + fmt.Println("update vm instance deploy set success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[虚机实例调整部署集](https://cloud.baidu.com/doc/BEC/s/nl0um3hvs) + +### 部署集移除虚机实例 + +通过以下代码,可以将虚机实例从部署集移除 +```go +getReq := &api.DeleteVmDeploySetArgs{ + DeploysetId: "dset-y4tumnel", + InstanceIdList: []string{"vm-dstkrmda-cn-langfang-ct-4thbz"}, +} +err := CLIENT.DeleteVmInstanceFromDeploySet(getReq) +if err != nil { + fmt.Println("remove vm instance from deploy set failed:", err) +} else { + fmt.Println("remove vm instance from deploy set success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[部署集移除实例](https://cloud.baidu.com/doc/BEC/s/Ml0ulrz5z) +## APPBLB相关 +### 创建APPBLB实例 + +通过以下代码,可以创建APPBLB实例 +```go +getReq := &api.CreateAppBlbRequest{ + Name: "xxx_test_applb", + Desc: "xxx-test", + RegionId: "cn-hangzhou-cm", + NeedPublicIp: true, + SubnetId: "sbn-xx", + VpcId: "vpc-xx", +} +res, err := CLIENT.CreateAppBlb("testCreateAppBlb", getReq) + +if err != nil { + fmt.Println("create app blb instance failed:", err) +} else { + fmt.Println("create app blb instance success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建APPBLB实例](https://cloud.baidu.com/doc/BEC/s/zl4nug4yg) + +### 修改APPBLB实例 +通过以下代码,可以修改APPBLB实例 +```go +getReq := &api.ModifyBecBlbRequest{ + Name: "xx_test_applb", + Desc: "xx-test1", +} +err := CLIENT.UpdateAppBlb("testUpdateAppBlb", "applb-xx", getReq) + +if err != nil { + fmt.Println("update app blb instance failed:", err) +} else { + fmt.Println("update app blb instance success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[修改APPBLB实例](https://cloud.baidu.com/doc/BEC/s/Ul4nuv8n2) +### 查询APPBLB实例列表 +通过以下代码,可以查询APPBLB实例列表 +```go +getReq := &api.MarkerRequest{} +res, err := CLIENT.GetAppBlbList(getReq) + +if err != nil { + fmt.Println("get app blb instance list failed:", err) +} else { + fmt.Println("get app blb instance list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询APPBLB实例列表](https://cloud.baidu.com/doc/BEC/s/9l4nv22ji) + +### 查询APPBLB实例详情 +通过以下代码,可以查询APPBLB实例详情 +```go +res, err := CLIENT.GetAppBlbDetails("applb-xxxx") + +if err != nil { + fmt.Println("get app blb instance detail failed:", err) +} else { + fmt.Println("get app blb instance detail success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询APPBLB实例详情](https://cloud.baidu.com/doc/BEC/s/Ul4nvz6d8) + +### 删除APPBLB实例 +通过以下代码,可以删除APPBLB实例 +```go +err := CLIENT.DeleteAppBlbInstance("applb-xxx", "") + +if err != nil { + fmt.Println("delete app blb instance failed:", err) +} else { + fmt.Println("delete app blb instance success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除APPBLB实例](https://cloud.baidu.com/doc/BEC/s/7l4nwir90) + +### 创建TCP监听器 +通过以下代码,可以创建TCP监听器 +```go +getReq := &api.CreateBecAppBlbTcpListenerRequest{ + ListenerPort: 80, + Scheduler: "RoundRobin", + TcpSessionTimeout: 1000, +} +err := CLIENT.CreateTcpListener("testCreateTcpListener", "applb-xxx", getReq) +if err != nil { + fmt.Println("create app tcp listener failed:", err) +} else { + fmt.Println("create app tcp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建TCP监听器](https://cloud.baidu.com/doc/BEC/s/il4nwx3ts) + +### 创建UDP监听器 +通过以下代码,可以创建UDP监听器 +```go +getReq := &api.CreateBecAppBlbUdpListenerRequest{ + ListenerPort: 80, + Scheduler: "RoundRobin", + UdpSessionTimeout: 1000, +} +err := CLIENT.CreateUdpListener("testCreateTcpListener", "applb-xxxx", getReq) +if err != nil { + fmt.Println("create app udp listener failed:", err) +} else { + fmt.Println("create app udp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建UDP监听器](https://cloud.baidu.com/doc/BEC/s/sl4p73e1g) + +### 新建监听器策略 +通过以下代码,可以新建监听器策略 +```go +getReq := &api.CreateAppBlbPoliciesRequest{ + ListenerPort: 80, + AppPolicyVos: []api.AppPolicyVo{ + { + AppIpGroupId: "bec_ip_group-xxx", + Priority: 1, + Desc: "xxx-test", + }, + }, +} +err := CLIENT.CreateListenerPolicy("", "applb-xxx", getReq) +if err != nil { + fmt.Println("create app blb listener policy failed:", err) +} else { + fmt.Println("create app blb listener policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[新建监听器策略](https://cloud.baidu.com/doc/BEC/s/Hl4qgo88o) + +### 查询监听器策略 +通过以下代码,可以查询监听器策略 +```go +getReq := &api.GetBlbListenerPolicyRequest{ + Port: 80, +} +res, err := CLIENT.GetListenerPolicy("applb-xxx", getReq) +if err != nil { + fmt.Println("get app blb listener policy failed:", err) +} else { + fmt.Println("get app blb listener policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询监听器策略](https://cloud.baidu.com/doc/BEC/s/cl4qgvkbp) + +### 删除监听器策略 +通过以下代码,可以删除监听器策略 +```go +getReq := &api.DeleteAppBlbPoliciesRequest{ + Port: 80, + PolicyIdList: []string{ + "bec_policy-scr9cwtk", + }, +} +err := CLIENT.DeleteListenerPolicy("", "applb-xxxxx", getReq) +if err != nil { + fmt.Println("delete app blb listener policy failed:", err) +} else { + fmt.Println("delete app blb listener policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除监听器策略](https://cloud.baidu.com/doc/BEC/s/Bl4qhc0vy) + +### 修改TCP监听器 +通过以下代码,可以修改TCP监听器 +```go +getReq := &api.UpdateBecAppBlbTcpListenerRequest{ + Scheduler: "RoundRobin", + TcpSessionTimeout: 800, +} +err := CLIENT.UpdateTcpListener("testUpdateTcpListener", "applb-xxx", "80", getReq) +if err != nil { + fmt.Println("update app tcp listener failed:", err) +} else { + fmt.Println("update app tcp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[修改TCP监听器](https://cloud.baidu.com/doc/BEC/s/sl4p73e1g) + +### 修改UDP监听器 +通过以下代码,可以修改UDP监听器 +```go +getReq := &api.UpdateBecAppBlbUdpListenerRequest{ + Scheduler: "RoundRobin", + UdpSessionTimeout: 800, +} +err := CLIENT.UpdateUdpListener("testUpdateUdpListener", "applb-xxx", "80", getReq) +if err != nil { + fmt.Println("update app udp listener failed:", err) +} else { + fmt.Println("update app udp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[修改UDP监听器](https://cloud.baidu.com/doc/BEC/s/al4p7c82y) + +### 查询TCP监听器 +通过以下代码,可以查询TCP监听器 +```go +getReq := &api.GetBecAppBlbListenerRequest{ + ListenerPort: 80, +} +res, err := CLIENT.GetTcpListener("applb-xxxx", getReq) +if err != nil { + fmt.Println("get app udp listener failed:", err) +} else { + fmt.Println("get app udp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询TCP监听器](https://cloud.baidu.com/doc/BEC/s/Ul4nxiz6l) + +### 查询TCP监听器 +通过以下代码,可以查询UDP监听器 +```go +getReq := &api.GetBecAppBlbListenerRequest{ + ListenerPort: 80, +} +res, err := CLIENT.GetUdpListener("applb-xxxx", getReq) +if err != nil { + fmt.Println("get app udp listener failed:", err) +} else { + fmt.Println("get app udp listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询UDP监听器](https://cloud.baidu.com/doc/BEC/s/4l4p7ih5s) + +### 删除监听器 +通过以下代码,可以删除监听器 +```go +getReq := &api.DeleteBlbListenerRequest{ +PortTypeList: []api.PortTypeList{ + { + Port: 80, + Type: "TCP", + }, + { + Port: 80, + Type: "UDP", + }, + }, +} +err := CLIENT.DeleteAppBlbListener("applb-xxx", "deleteApplbInstance", getReq) +if err != nil { + fmt.Println("delete app listener failed:", err) +} else { + fmt.Println("delete app listener success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除监听器](https://cloud.baidu.com/doc/BEC/s/dl4p7sfyb) + +### 创建IP组 +通过以下代码,可以创建IP组 +```go +getReq := &api.CreateBlbIpGroupRequest{ + Name: "xxx-testIpGroup", + Desc: "xxx-test", +} +res, err := CLIENT.CreateIpGroup("testIpGroup", "applb-xxx", getReq) +if err != nil { + fmt.Println("create app blb ip group failed:", err) +} else { + fmt.Println("create app blb ip group success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建IP组](https://cloud.baidu.com/doc/BEC/s/nl4p9vtw2) + +### 更新IP组 +通过以下代码,可以更新IP组 +```go +getReq := &api.UpdateBlbIpGroupRequest{ + Name: "xxx-testIpGroupupdate", + Desc: "xxx-testupdate", + IpGroupId: "bec_ip_group-xxx", +} +err := CLIENT.UpdateIpGroup("testIpGroup", "applb-xxx", getReq) +if err != nil { + fmt.Println("update app blb ip group failed:", err) +} else { + fmt.Println("update app blb ip group success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[更新IP组](https://cloud.baidu.com/doc/BEC/s/wl4pahvlw) + +### 查询IP组列表 +通过以下代码,可以查询IP组列表 +```go +getReq := &api.GetBlbIpGroupListRequest{} +res, err := CLIENT.GetIpGroup("applb-xxxx", getReq) +if err != nil { + fmt.Println("get app blb ip group list failed:", err) +} else { + fmt.Println("get app blb ip group list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询IP组列表](https://cloud.baidu.com/doc/BEC/s/El4pan7ox) + +### 删除IP组 +通过以下代码,可以删除IP组 +```go +getReq := &api.DeleteBlbIpGroupRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", +} +err := CLIENT.DeleteIpGroup("testDeleteIpGroup", "applb-xxxx", getReq) +if err != nil { + fmt.Println("delete app blb ip group failed:", err) +} else { + fmt.Println("delete app blb ip group success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除IP组](https://cloud.baidu.com/doc/BEC/s/Ml4qem709) + +### 创建IP组协议 +通过以下代码,可以创建IP组协议 +```go +getReq := &api.CreateBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-xxx, + Type: "TCP", + HealthCheck: "TCP", + HealthCheckPort: 80, + HealthCheckTimeoutInSecond: 10, + HealthCheckIntervalInSecond: 3, + HealthCheckDownRetry: 4, + HealthCheckUpRetry: 5, +} +res, err := CLIENT.CreateIpGroupPolicy("", "applb-xxx", getReq) +if err != nil { + fmt.Println("create app blb ip group policy failed:", err) +} else { + fmt.Println("create app blb ip group policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建IP组协议](https://cloud.baidu.com/doc/BEC/s/Ml4qem709) + +### 更新IP组协议 +通过以下代码,可以更新IP组协议 +```go +getReq := &api.UpdateBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-xxx", + Id: "bec_ip_group_policy-xxx", + HealthCheckPort: 80, +} +err := CLIENT.UpdateIpGroupPolicy("", "applb-xxx", getReq) +if err != nil { + fmt.Println("update app blb ip group policy failed:", err) +} else { + fmt.Println("update app blb ip group policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[更新IP组协议](https://cloud.baidu.com/doc/BEC/s/5l4qexv01) + +### 查询IP组协议列表 +通过以下代码,可以查询IP组协议列表 +```go +getReq := &api.GetBlbIpGroupPolicyListRequest{ + IpGroupId: "bec_ip_group-xxx", +} +res, err := CLIENT.GetIpGroupPolicyList("applb-xxx", getReq) +if err != nil { + fmt.Println("get app blb ip group policy list failed:", err) +} else { + fmt.Println("get app blb ip group policy list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询IP组协议列表](https://cloud.baidu.com/doc/BEC/s/2l4qf4jv6) + +### 删除IP组协议 +通过以下代码,可以删除IP组协议 +```go +getReq := &api.DeleteBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-xxx", + BackendPolicyIdList: []string{"bec_ip_group_policy-xx"}, +} +err := CLIENT.DeleteIpGroupPolicy("", "applb-xxx", getReq) +if err != nil { + fmt.Println("delete app blb ip group policy failed:", err) +} else { + fmt.Println("delete app blb ip group policy success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除IP组协议](https://cloud.baidu.com/doc/BEC/s/Ll4qg1hun) + +### 创建IP组成员 +通过以下代码,可以创建IP组成员 +```go +getReq := &api.CreateBlbIpGroupMemberRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + MemberList: []api.BlbIpGroupMember{ + { + Ip: "172.16.240.25", + Port: 90, + Weight: 100, + }, + }, +} +res, err := CLIENT.CreateIpGroupMember("", "applb-xxxx", getReq) +if err != nil { + fmt.Println("create app blb ip group member failed:", err) +} else { + fmt.Println("create app blb ip group member success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[创建IP组成员](https://cloud.baidu.com/doc/BEC/s/yl4qf9xbw) + +### 更新IP组成员 +通过以下代码,可以更新IP组成员 +```go +getReq := &api.UpdateBlbIpGroupMemberRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + MemberList: []api.UpdateBlbIpGroupMember{ + { + MemberId: "bec_ip_member-ouiinabp", + Port: 8080, + Weight: 100, + }, + }, +} +err := CLIENT.UpdateIpGroupMember("", "applb-xxxx", getReq) +if err != nil { + fmt.Println("update app blb ip group member failed:", err) +} else { + fmt.Println("update app blb ip group member success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[更新IP组成员](https://cloud.baidu.com/doc/BEC/s/Gl4qfi31t) + +### 查询IP组成员列表 +通过以下代码,可以查询IP组成员列表 +```go +getReq := &api.GetBlbIpGroupMemberListRequest{ + IpGroupId: "bec_ip_group-xxx", +} +res, err := CLIENT.GetIpGroupMemberList("applb-xxxx", getReq) +if err != nil { + fmt.Println("get app blb ip group member list failed:", err) +} else { + fmt.Println("get app blb ip group member list success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[查询IP组成员列表](https://cloud.baidu.com/doc/BEC/s/Il4qfnftj) + +### 删除IP组成员 +通过以下代码,可以删除IP组成员 +```go +getReq := &api.DeleteBlbIpGroupBackendMemberRequest{ + IpGroupId: "bec_ip_group-xxx", + MemberIdList: []string{"bec_ip_member-xxx"}, +} +err := CLIENT.DeleteIpGroupMember("", "applb-xxx", getReq) +if err != nil { + fmt.Println("delete app blb ip group member failed:", err) +} else { + fmt.Println("delete app blb ip group member success") +} +``` +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BEC API 文档[删除IP组成员](https://cloud.baidu.com/doc/BEC/s/Hl4qftd63) + + + + + + +## 客户端异常 + +客户端异常表示客户端尝试向BEC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BEC服务端出现异常时,BEC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BEC错误返回](https://cloud.baidu.com/doc/BEC/s/5k4106ncs) + +# 版本变更记录 + +## 更新日期 [2022-08-25] + +更新内容 + +- 更新虚机相关的参数、支持创建VPC虚机、更新获取虚机监控的接口 +- 更新负载均衡相关参数、支持创建APPLB、更新获取负载均衡监控的接口 +- 更新了容器服务接口的相关参数 + +新增内容 + +- 创建、删除、列表、更新、详情虚机部署集 +- 将虚机移入、移除部署集 +- 监控接口新增stepInMin参数 +- 创建、修改、查询、删除APPBLB +- 创建、修改、查询、删除APPBLB监听器以及监听器策略 +- 创建、修改、查询、删除APPBLB IP组、IP组协议、IP组成员 +- 查询容器部署详情、查询容器部署监控、更新容器部署副本、删除容器部署 +- 查询POD列表、详情、资源监控,重启容器组 + + + +## v0.9.11 [2021-04-21] + +首次发布: + + - 创建、列表、更新、删除VM镜像 + - 创建、列表、更新、删除、详情、批量创建、批量删除blb、获取负载均衡监控信息 + - 创建、删除、列表、更新、详情、批量创建blb监听 + - 获取负载均衡已绑定资源列表、获取负载均衡可绑定的部署列表、获取部署中可绑定资源列表、创建后端服务器、删除后端服务器、修改负载均衡已绑定资源权重 + - 创建、更新、列表、详情、删除、操作、批量删除、批量操作虚机服务、获取BEC虚机服务监控 + - 创建无实例的虚机服务、创建虚机实例、添加辅助ip、删除辅助ip + - 列表、详情、删除、更新、重装、操作虚机实例、获取虚机实例配置、获取所在节点的BEC虚机列表、获取虚机监控信息 + - 创建、列表、详情、操作、更新、删除、批量操作、批量删除容器服务、获取BEC容器服务监控 + diff --git a/bce-sdk-go/doc/BLB.md b/bce-sdk-go/doc/BLB.md new file mode 100644 index 0000000..6e9e523 --- /dev/null +++ b/bce-sdk-go/doc/BLB.md @@ -0,0 +1,808 @@ +# BLB服务 + +# 概述 + +本文档主要介绍普通型BLB GO SDK的使用。在使用本文档前,您需要先了解普通型BLB的一些基本知识。若您还不了解普通型BLB,可以参考[产品描述](https://cloud.baidu.com/doc/BLB/s/Ajwvxno34)和[入门指南](https://cloud.baidu.com/doc/BLB/s/cjwvxnr91)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BLB访问域名](https://cloud.baidu.com/doc/BLB/s/cjwvxnzix)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云BLB,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BLB做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建普通型BLB Client + +普通型BLB Client是普通型BLB服务的客户端,为开发者与BLB服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建普通型BLB Client + +通过AK/SK方式访问BLB,用户可以参考如下代码新建一个BLB Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/blb" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BLBClient + blbClient, err := blb.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BLB/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BLB的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`blb.bj.baidubce.com`。 + +### 使用STS创建BLB Client + +**申请STS token** + +BLB可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BLB,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建BLB Client** + +申请好STS后,可将STS Token配置到BLB Client中,从而实现通过STS Token创建BLB Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BLB Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/blb" //导入BLB服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BLB服务的Client对象,Endpoint使用默认值 + blbClient, err := blb.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create blb client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + blbClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BLB Client时,无论对应BLB服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BLB + +BLB支持HTTPS传输协议,您可以通过在创建BLB Client对象时指定的Endpoint中指明HTTPS的方式,在BLB GO SDK中使用HTTPS访问BLB服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/blb" + +ENDPOINT := "https://blb.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +blbClient, _ := blb.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BLB Client + +如果用户需要配置BLB Client的一些细节的参数,可以在创建BLB Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BLB服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/blb" + +//创建BLB Client对象 +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com +client, _ := blb.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/blb" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := blb.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/blb" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := blb.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BLB时,创建的BLB Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BLB Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +普通型blb实例针对用户复杂应用部署架构,特别是大型网站架构。使用基于策略的网络管理框架构建,实现业务驱动的流量负载均衡。 + +## 实例管理 + +### 创建实例 + +通过以下代码,可以创建一个普通型LoadBalancer,返回分配的服务地址及实例ID +```go +args := &blb.CreateLoadBalancerArgs{ + // 设置实例名称 + Name: "sdkBlb", + // 设置实例描述 + Description: "sdk create", + // 设置实例所属vpc + VpcId: vpcId, + // 设置实例所属子网 + SubnetId: subnetId, +} + +// 若要为实例设置标签,可以按照以下代码,标签设置之后,不可修改和删除 +args.Tags = []model.TagModel{ + { + TagKey: "key", + TagValue: "value", + }, +} +result, err := client.CreateLoadBalancer(args) +if err != nil { + fmt.Println("create blb failed:", err) +} else { + fmt.Println("create blb success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateLoadBalancer创建实例](https://cloud.baidu.com/doc/BLB/s/njwvxnv79#createloadbalancer%E5%88%9B%E5%BB%BA%E5%AE%9E%E4%BE%8B) + +### 更新实例 + +通过以下代码,可以更新一个LoadBalancer的配置信息,如实例名称和描述 +```go +args := &blb.UpdateLoadBalancerArgs{ + Name: "testSdk", + Description: "test desc", +} +err := client.UpdateLoadBalancer(blbId, args) +if err != nil { + fmt.Println("update blb failed:", err) +} else { + fmt.Println("update blb success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateLoadBalancer更新实例](https://cloud.baidu.com/doc/BLB/s/njwvxnv79#updateloadbalancer%E6%9B%B4%E6%96%B0%E5%AE%9E%E4%BE%8B) + +### 查询已有的实例 + +通过以下代码,可以查询用户账户下所有LoadBalancer的信息 +```go +args := &blb.DescribeLoadBalancersArgs{} + +// 支持按LoadBalancer的id、name、address进行查询,匹配规则支持部分包含(不支持正则) +args.BlbId = blbId +args.Name = blbName +args.Address = blbAddress +args.ExactlyMatch = true + +// 支持查找绑定指定BLB的LoadBalancer,通过blbId参数指定 +args.BlbId = blbId + +result, err := client.DescribeLoadBalancers(args) +if err != nil { + fmt.Println("list all blb failed:", err) +} else { + fmt.Println("list all blb success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeLoadBalancers查询已有的BLB实例](https://cloud.baidu.com/doc/BLB/s/njwvxnv79#describeloadbalancers%E6%9F%A5%E8%AF%A2%E5%B7%B2%E6%9C%89%E7%9A%84blb%E5%AE%9E%E4%BE%8B) + +### 查询实例详情 + +通过以下代码,可以按id查询用户账户下特定的普通型LoadBalancer的详细信息,包含LoadBalancer所有的监听器端口信息 +```go +result, err := client.DescribeLoadBalancerDetail(blbId) +if err != nil { + fmt.Println("get blb detail failed:", err) +} else { + fmt.Println("get blb detail success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeLoadBalancerDetail查询BLB实例详情](https://cloud.baidu.com/doc/BLB/s/njwvxnv79#describeloadbalancerdetail%E6%9F%A5%E8%AF%A2blb%E5%AE%9E%E4%BE%8B%E8%AF%A6%E6%83%85) + +### 释放实例 + +通过以下代码,可以释放指定LoadBalancer,被释放的LoadBalancer无法找回 +```go +err := client.DeleteLoadBalancer(blbId) +if err != nil { + fmt.Println("delete blb failed:", err) +} else { + fmt.Println("delete blb success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DeleteLoadBalancer释放BLB实例](https://cloud.baidu.com/doc/BLB/s/njwvxnv79#deleteloadbalancer%E9%87%8A%E6%94%BEblb%E5%AE%9E%E4%BE%8B) + +### 添加普通型BLB后端服务器 + +通过以下代码,在指定普通型BLB下绑定后端服务器 +```go +args := &blb.AddBackendServersArgs{ + // 配置后端服务器的列表及权重 + BackendServerList: []blb.BackendServerModel{ + {InstanceId: instanceId, Weight: 100}, + }, +} +err := client.AddBackendServers(blbId, args) +if err != nil { + fmt.Println("add backend servers failed:", err) +} else { + fmt.Println("add backend servers success: ") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[AddBackendServers添加后端服务器](https://cloud.baidu.com/doc/BLB/s/Ujwvxnvxe#addbackendservers%E6%B7%BB%E5%8A%A0%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8) + +### 更新后端服务器权重 + +通过以下代码,更新指定blb下的信息 +```go +args := &blb.UpdateBackendServersArgs{ + // 配置后端服务器的列表及权重 + BackendServerList: []blb.BackendServerModel{ + {InstanceId: instanceId, Weight: 30}, + }, +} +err := client.UpdateBackendServers(blbId, args) +if err != nil { + fmt.Println("update backend servers failed:", err) +} else { + fmt.Println("update backend servers success: ") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateBackendServers更新后端服务器](https://cloud.baidu.com/doc/BLB/s/Ujwvxnvxe#updatebackendservers%E6%9B%B4%E6%96%B0%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8) + +### 查询后端服务器列表信息 + +通过以下代码,查询指定LoadBalancer下所有服务器的信息 +```go +args := &blb.DescribeBackendServersArgs{ + +} +result, err := client.DescribeBackendServers(blbId, args) +if err != nil { + fmt.Println("describe backend servers failed:", err) +} else { + fmt.Println("describe backend servers success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeBackendServers查询后端服务器列表](https://cloud.baidu.com/doc/BLB/s/Ujwvxnvxe#describebackendservers%E6%9F%A5%E8%AF%A2%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%88%97%E8%A1%A8) + +### 查询后端服务器健康状态 + +通过以下代码,查询指定监听端口下后端服务器的健康状态信息 +```go +args := &blb.DescribeHealthStatusArgs{ + ListenerPort: 80, +} +result, err := client.DescribeHealthStatus(blbId, args) +if err != nil { + fmt.Println("describe health status failed:", err) +} else { + fmt.Println("describe health status success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeHealthStatus查询后端服务器健康状态](https://cloud.baidu.com/doc/BLB/s/Ujwvxnvxe#describehealthstatus%E6%9F%A5%E8%AF%A2%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%81%A5%E5%BA%B7%E7%8A%B6%E6%80%81) + +### 释放后端服务器 + +通过以下代码,释放后端服务器 +```go +args := &blb.RemoveBackendServersArgs{ + // 要从后端服务器列表中释放的实例列表 + BackendServerList: []string{instanceId}, +} +err := client.RemoveBackendServers(blbId, args) +if err != nil { + fmt.Println("remove backend servers failed:", err) +} else { + fmt.Println("remove backend servers success: ") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[RemoveBackendServers释放后端服务器](https://cloud.baidu.com/doc/BLB/s/Ujwvxnvxe#removebackendservers%E9%87%8A%E6%94%BE%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%99%A8) + +## 监听器管理 + +### 创建TCP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于TCP协议的普通型blb监听器,监听一个前端端口,将发往该端口的所有TCP流量,根据策略进行转发 +```go +args := &blb.CreateTCPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 后端服务器的监听端口,需要在1-65535之间 + BackendPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", + // TCP设置链接超时时间,默认900秒,需要为10-4000之间的整数 + TcpSessionTimeout: 1000, +} +err := client.CreateTCPListener(BLBID, args) +if err != nil { + fmt.Println("create TCP Listener failed:", err) +} else { + fmt.Println("create TCP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateTCPListener创建TCP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#createtcplistener%E5%88%9B%E5%BB%BAtcp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新TCP监听器 + +通过以下代码,更新指定LoadBalancer下的TCP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &blb.UpdateTCPListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "Hash", + // 更新tcp链接超时时间 + TcpSessionTimeout: 2000, +} +err := client.UpdateTCPListener(BLBID, args) +if err != nil { + fmt.Println("update TCP Listener failed:", err) +} else { + fmt.Println("update TCP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateTCPListener更新TCP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#updatetcplistener%E6%9B%B4%E6%96%B0tcp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询TCP监听器 + +通过以下代码,查询指定LoadBalancer下所有TCP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &blb.DescribeListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeTCPListeners(BLBID, args) +if err != nil { + fmt.Println("describe TCP Listener failed:", err) +} else { + fmt.Println("describe TCP Listener success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeTCPListeners查询TCP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#describetcplisteners%E6%9F%A5%E8%AF%A2tcp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 创建UDP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于UDP协议的监听器,监听一个前端端口,将发往该端口的所有UDP流量,根据策略进行转发 +```go +args := &blb.CreateUDPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 53, + // 后端服务器的监听端口,需要在1-65535之间 + BackendPort: 53, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", + // 健康检查字符串 健康发送的请求字符串,后端服务器收到后需要进行应答,支持16进制\00-\FF和标准ASCII字符串,最大长度1299 + HealthCheckString: "\00\01\01\00\00\01\00\00\00\00\00\00\05baidu\03com\00\00\01\00\01" +} +err := client.CreateUDPListener(BLBID, args) +if err != nil { + fmt.Println("create UDP Listener failed:", err) +} else { + fmt.Println("create UDP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateUDPListener创建UDP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#createudplistener%E5%88%9B%E5%BB%BAudp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新UDP监听器 + +通过以下代码,更新指定LoadBalancer下的UDP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &blb.UpdateUDPListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 53, + // 更新负载均衡的算法 + Scheduler: "Hash", +} +err := client.UpdateUDPListener(BLBID, args) +if err != nil { + fmt.Println("update UDP Listener failed:", err) +} else { + fmt.Println("update UDP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateUDPListener更新UDP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#updateudplistener%E6%9B%B4%E6%96%B0udp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询UDP监听器 + +通过以下代码,查询指定LoadBalancer下所有UDP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &blb.DescribeListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 53, +} +result, err := client.DescribeUDPListeners(BLBID, args) +if err != nil { + fmt.Println("describe UDP Listener failed:", err) +} else { + fmt.Println("describe UDP Listener success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeUDPListeners查询UDP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#describeudplisteners%E6%9F%A5%E8%AF%A2udp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 创建HTTP监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于HTTP协议的监听器,监听一个前端端口,将发往该端口的所有HTTP请求,根据策略转发到后端服务器监听的后端端口上 +```go +args := &blb.CreateHTTPListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 80, + // 后端服务器的监听端口,需要在1-65535之间 + BackendPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection + Scheduler: "RoundRobin", +} +err := client.CreateHTTPListener(BLBID, args) +if err != nil { + fmt.Println("create HTTP Listener failed:", err) +} else { + fmt.Println("create HTTP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateHTTPListener创建HTTP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#createhttplistener%E5%88%9B%E5%BB%BAhttp%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新HTTP监听器 + +通过以下代码,更新指定LoadBalancer下的HTTP监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &blb.UpdateHTTPListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 80, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 开启会话保持功能 + KeepSession: true, +} +err := client.UpdateHTTPListener(BLBID, args) +if err != nil { + fmt.Println("update HTTP Listener failed:", err) +} else { + fmt.Println("update HTTP Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[更新HTTP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#updatehttplistener%E6%9B%B4%E6%96%B0http%E7%9B%91%E5%90%AC%E5%99%A8) + +### 查询HTTP监听器 + +通过以下代码,查询指定LoadBalancer下所有HTTP监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &blb.DescribeListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 80, +} +result, err := client.DescribeHTTPListeners(BLBID, args) +if err != nil { + fmt.Println("describe HTTP Listener failed:", err) +} else { + fmt.Println("describe HTTP Listener success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeHTTPListeners查询HTTP监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#describehttplisteners%E6%9F%A5%E8%AF%A2http%E7%9B%91%E5%90%AC%E5%99%A8) + +### 创建HTTPS监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于HTTPS协议的监听器,监听一个前端端口,将发往该端口的所有HTTPS请求,先通过SSL卸载转换为HTTP请求后,再根据策略转发到后端服务器监听的后端端口上 +```go +args := &blb.CreateHTTPSListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 443, + // 后端服务器的监听端口,需要在1-65535之间 + BackendPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection + Scheduler: "RoundRobin", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.CreateHTTPSListener(BLBID, args) +if err != nil { + fmt.Println("create HTTPS Listener failed:", err) +} else { + fmt.Println("create HTTPS Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateHTTPSListener创建HTTPS监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#createhttpslistener%E5%88%9B%E5%BB%BAhttps%E7%9B%91%E5%90%AC%E5%99%A8) + +### 更新HTTPS监听器 + +通过以下代码,更新指定LoadBalancer下的HTTPS监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &blb.UpdateHTTPSListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 443, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.UpdateHTTPSListener(BLBID, args) +if err != nil { + fmt.Println("update HTTPS Listener failed:", err) +} else { + fmt.Println("update HTTPS Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateHTTPSListener更新HTTPS监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#updatehttpslistener%E6%9B%B4%E6%96%B0https%E7%9B%91%E5%90%AC%E5%99%A8) + + +### 查询HTTPS监听器 + +通过以下代码,查询指定LoadBalancer下所有HTTPS监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &blb.DescribeListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 443, +} +result, err := client.DescribeHTTPSListeners(BLBID, args) +if err != nil { + fmt.Println("describe HTTPS Listener failed:", err) +} else { + fmt.Println("describe HTTPS Listener success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeHTTPSListeners查询HTTPS监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#describehttpslisteners%E6%9F%A5%E8%AF%A2https%E7%9B%91%E5%90%AC%E5%99%A8) + + + +### 创建SSL监听器 + +通过以下代码,在指定LoadBalancer下,创建一个基于SSL协议的blb监听器,监听一个前端端口,将发往该端口的所有SSL流量,根据策略进行转发 +```go +args := &blb.CreateSSLListenerArgs{ + // 监听器监听的端口,需要在1-65535之间 + ListenerPort: 443, + // 后端服务器的监听端口,需要在1-65535之间 + BackendPort: 80, + // 负载均衡算法,支持RoundRobin/LeastConnection/Hash + Scheduler: "RoundRobin", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.CreateSSLListener(BLBID, args) +if err != nil { + fmt.Println("create SSL Listener failed:", err) +} else { + fmt.Println("create SSL Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[CreateSSLListener创建SSL监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#createssllistener%E5%88%9B%E5%BB%BAssl%E7%9B%91%E5%90%AC%E5%99%A8) + + + +### 更新SSL监听器 + +通过以下代码,更新指定LoadBalancer下的SSL监听器参数,所有请求参数中指定的域都会被更新,未指定的域保持不变,监听器通过端口指定 +```go +args := &blb.UpdateSSLListenerArgs{ + // 要更新的监听器端口号 + ListenerPort: 443, + // 更新负载均衡的算法 + Scheduler: "LeastConnection", + // 配置证书列表 + CertIds: []string{certId}, +} +err := client.UpdateSSLListener(BLBID, args) +if err != nil { + fmt.Println("update SSL Listener failed:", err) +} else { + fmt.Println("update SSL Listener success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[UpdateSSLListener更新SSL监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#updatessllistener%E6%9B%B4%E6%96%B0ssl%E7%9B%91%E5%90%AC%E5%99%A8) + + +### 查询SSL监听器 + +通过以下代码,查询指定LoadBalancer下所有SSL监听器的信息,支持按监听器端口进行匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 +```go +args := &blb.DescribeListenerArgs{ + // 要查询的监听器端口 + ListenerPort: 443, +} +result, err := client.DescribeSSLListeners(BLBID, args) +if err != nil { + fmt.Println("describe SSL Listener failed:", err) +} else { + fmt.Println("describe SSL Listener success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DescribeSSLListeners查询SSL监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#describessllisteners%E6%9F%A5%E8%AF%A2ssl%E7%9B%91%E5%90%AC%E5%99%A8) + + +### 删除监听器 + +通过以下代码,释放指定LoadBalancer下的监听器,监听器通过监听端口来指定,支持批量释放 +```go +args := &blb.DeleteListenersArgs{ + ClientToken: "be31b98c-5e41-4838-9830-9be700de5a20", + // 要删除的监听器监听的端口 + PortList: []uint16{80, 443}, +} +err := client.DeleteListeners(BLBID, args) +if err != nil { + fmt.Println("delete Listeners failed:", err) +} else { + fmt.Println("delete Listeners success: ") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考BLB API 文档[DeleteListeners释放监听器](https://cloud.baidu.com/doc/BLB/s/yjwvxnvl6#deletelisteners%E9%87%8A%E6%94%BE%E7%9B%91%E5%90%AC%E5%99%A8) + + +# 错误处理 + +GO语言以error类型标识错误,BLB支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BLB服务返回的错误 + +用户使用SDK调用BLB相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// blbClient 为已创建的BLB Client对象 +blbDetail, err := blbClient.DescribeLoadBalancerDetail(blbId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get blb detail success: ", blbDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BLB发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BLB服务端出现异常时,BLB服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BLB错误返回](https://cloud.baidu.com/doc/BLB/s/Djwvxnzw6) + +# 版本变更记录 + +## v0.9.11 [2020-05-20] + +首次发布: + + - 创建、查看、列表、更新、删除普通型BLB实例 + - 创建、列表、更新、删除后端RS,并支持查询后端服务器健康检查状态 + - 创建、查看、更新、删除监听器端口,支持TCP/UDP/HTTP/HTTPS/SSL协议 diff --git a/bce-sdk-go/doc/BLS.md b/bce-sdk-go/doc/BLS.md new file mode 100644 index 0000000..dc853c9 --- /dev/null +++ b/bce-sdk-go/doc/BLS.md @@ -0,0 +1,872 @@ +# BLS服务 + +# 概述 + +本文档主要介绍BLS GO SDK的使用。在使用本文档前,您需要先了解BLS的一些基本知识,并已开通了BLS服务。若您还不了解BLS,可以参考[产品描述](https://cloud.baidu.com/doc/BLS/index.html)和[入门指南](https://cloud.baidu.com/doc/BLS/s/Gjwvyjbvg)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BLS访问域名](https://cloud.baidu.com/doc/BLS/s/4k8qysj2z)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx)。 + +目前支持“华北-北京”和“华南-广州”两个区域。北京区域:`bls-log.bj.baidubce.com`广州区域:`bls-log.gz.baidubce.com`。对应信息为: + +| 访问区域 | 对应Endpoint | Protocol | +| --------- | ----------------------- | ---------- | +| 华北-北京 | bls-log.bj.baidubce.com | HTTP/HTTPS | +| 华南-广州 | bls-log.gz.baidubce.com | HTTP/HTTPS | + +## 获取密钥 + +要使用百度云BLS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BLS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BLS Client + +BLS Client是BLS服务的客户端,为开发者与BLS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BLS Client + +通过AK/SK方式访问BLS,用户可以参考如下代码新建一个BLS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bls" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BLSClient + blsClient, err := bls.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Secret Access Key”,获取方式请参考《通用参考 [获取AKSK](https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BLS的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://bls-log.bj.baidubce.com`。 + +### 使用STS创建BLS Client + +**申请STS token** + +BLS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BLS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建BLS Client** + +申请好STS后,可将STS Token配置到BLS Client中,从而实现通过STS Token创建BLS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BLS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/bls" //导入BLS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BLS服务的Client对象,Endpoint使用默认值 + blsClient, err := bls.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create bls client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + blsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BLS Client时,无论对应BLS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BLS + +BLS支持HTTPS传输协议,您可以通过在创建BLS Client对象时指定的Endpoint中指明HTTPS的方式,在BLS GO SDK中使用HTTPS访问BLS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bls" + +ENDPOINT := "https://bls-log.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +blsClient, _ := bls.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BLS Client + +如果用户需要配置BLS Client的一些细节的参数,可以在创建BLS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BLS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bls" + +//创建BLS Client对象 +AK, SK := , +ENDPOINT := "bls-log.bj.baidubce.com" +blsClient, _ := bls.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +blsClient.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bls" + +AK, SK := , +ENDPOINT := "bls-log.bj.baidubce.com" +blsClient, _ := bls.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +blsClient.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +blsClient.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bls" + +AK, SK := , +ENDPOINT := "bls-log.bj.baidubce.com" +blsClient, _ := bls.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +blsClient.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +blsClient.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BLS时,创建的BLS Client对象的`Config`字段支持的所有参数如下表所示: + +| 配置项名称 | 类型 | 含义 | +| ------------------------- | --------------------- | -------------------------------------- | +| Endpoint | string | 请求服务的域名 | +| ProxyUrl | string | 客户端请求的代理地址 | +| Region | string | 请求资源的区域 | +| UserAgent | string | 用户名称,HTTP请求的User-Agent头 | +| Credentials | \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 | +| SignOption | \*auth.SignOptions | 认证字符串签名选项 | +| Retry | RetryPolicy | 连接重试策略 | +| ConnectionTimeoutInMillis | int | 连接超时时间,单位毫秒,默认20分钟 | + +> 说明: +> +> 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BLS Client”小节。 +> 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: +> +> | 名称 | 类型 | 含义 | +> | ------------- | ------------------- | ------------------------------------------------------ | +> | HeadersToSign | map[string]struct{} | 生成签名字符串时使用的HTTP头 | +> | Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 | +> | ExpireSeconds | int | 签名字符串的有效期 | +> +> 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +> +> 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +# 主要接口 + +用户可以通过 API 的方式管理 BLS 日志集、写入、下载、查询和分析日志数据等操作。 + +## LogStore操作 + +### 创建LogStore + +创建日志集,命名日志组时,需遵循以下准则: + +- 每个账户每个区域日志集名称不能重复 +- 日志集名称长度不能超过 128 个字符 +- 日志集名称包含的字符仅限于: `a-z, A-Z, 0-9, '_', '-', '.'` + +通过以下代码,创建一个LogStore并指定其存储期限。 + +```go +err := blsClient.CreateLogStore("demo", 3) +if err != nil { + fmt.Println("Create logStore failed: ", err) +} else { + fmt.Println("Create logStore success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[CreateLogStore](https://cloud.baidu.com/doc/BLS/s/pk8to0k59) + +### 更新指定LogStore + +通过以下代码,更新指定的日志集,目前仅支持更改与日志集关联的存储期限。 + +```go +err := blsClient.UpdateLogStore("demo", 5) +if err != nil { + fmt.Println("Update logStore failed: ", err) +} else { + fmt.Println("Update logStore success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[UpdateLogStore](https://cloud.baidu.com/doc/BLS/s/ok8to0kla) + +### 查询指定LogStore + +通过以下代码,获取指定日志集的详情信息。 + +```go +res, err := blsClient.DescribeLogStore("demo") +if err != nil { + fmt.Println("Get logStore failed: ", err) +} else { + fmt.Println("LogStore info: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DescribeLogStore](https://cloud.baidu.com/doc/BLS/s/Bk8to0jp3) + +### 获取LogStore列表 + +通过以下代码,获取当前用户的日志集列表。 + +```go +// 可选参数列表 +args := &api.QueryConditions{ + NamePattern: "bls-log", + Order: "asc", + OrderBy: "creationDateTime", + PageNo: 1, + PageSize: 10} +res, err := blsClient.ListLogStore(args) +if err != nil { + fmt.Println("Get logStore list failed: ", err) +} else { + fmt.Println("List logStore success: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[ListLogStore](https://cloud.baidu.com/doc/BLS/s/Hk8to0kda) + +### 删除LogStore + +通过以下代码,删除指定的日志集,并且会永久删除与其关联的所有已存储日志记录。 + +```go +err := blsClient.DeleteLogStore("demo") +if err != nil { + fmt.Println("Delete logStore failed: ", err) +} else { + fmt.Println("Delete logStore success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DeleteLogStore](https://cloud.baidu.com/doc/BLS/s/ak8to0jx8) + +## LogStream操作 + +LogStream会随着LogStore的创建自动创建,目前暂不支持对LogStream的删除操作。 + +### 获取LogStream列表 + +通过以下代码,获取指定日志集的日志流列表。 + +```go +// 可选参数列表 +args := &api.QueryConditions{ + NamePattern: "bls-log", + Order: "desc", + OrderBy: "creationDateTime", + PageNo: 1, + PageSize: 20, +} +res, err := blsClient.ListLogStore(args) +if err != nil { + fmt.Println("Get logStream list failed: ", err) +} else { + fmt.Println("List logStore success: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[ListLogStream](https://cloud.baidu.com/doc/BLS/s/dk8to0lhy) + +## LogRecord操作 + +### 推送LogRecord + +支持批量推送日志记录到 BLS 平台,日志记录的格式可以是 TEXT,也可以是 JSON 格式。如果是 TEXT,则不对日志进行解析;如果是 JSON 格式,可以自动发现 JSON 字段(仅支持首层字段发现,暂不支持嵌套类型字段的自动发现)。 + +如果既想上传日志原文,又想上传解析出的具体字段,可以使用 JSON 格式进行上传,并在 JSON 中包含日志原文(使用 @raw 作为key,日志原文作为 value)。 BLS 解析到 @raw 的时候,会将其内容作为日志原文处理。 + +通过以下代码,可以批量推送JSON日志记录到指定日志集的指定日志流中。 + +```go +// 推送JSON格式日志记录 +jsonRecords := []api.LogRecord{ + { + Message: "{\"body_bytes_sent\":184,\"bytes_sent\":398,\"client_ip\":\"120.193.204.39\"}", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 1, + }, + { + Message: "{\"body_bytes_sent\":14,\"bytes_sent\":408,\"client_ip\":\"120.193.222.39\"}", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 2, + }, +} +// 指定logRecord类型为JSON,并将日志记录推送到日志集demo中的日志流json-logStream中 +// 若没有该日志流,则自动创建 +err := blsClient.PushLogRecord("demo", "json-logStream", "JSON", jsonRecords) +if err != nil { + fmt.Println("Push logRecords failed: ", err) +} else { + fmt.Println("Push logRecords success") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[PushLogRecord](https://cloud.baidu.com/doc/BLS/s/dk8to0ktn) + +### 查看指定LogRecord + +通过以下代码,查看指定日志流中的日志记录,您可以获取最近的日志记录或使用时间范围进行过滤。 + +```go +args := &api.PullLogRecordArgs{ + // 必须指定日志流名称 + LogStreamName: "json-logStream", + // 可选参数 + StartDateTime: "2021-01-01T10:11:44Z", + EndDateTime: "2021-12-10T16:11:44Z", + Limit: 500, // 返回最大条目数 + Marker: "", // 指定查看的位置标记 +} +res, err := blsClient.PullLogRecord("demo", args) +if err != nil { + fmt.Println("Pull logRecord failed: ", err) +} else { + fmt.Println("LogRecords result: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[PullLogRecord](https://cloud.baidu.com/doc/BLS/s/Ek8to0l1o) + +### 查询指定LogRecord + +用户通过提交 Query 检索或分析指定日志集中的数据,每次只能查询一个日志集的内容。 + +Query 语句格式支持 检索 + SQL分析,通过竖线分隔,即在检索的结果集上执行 SQL,形如:`Search | SQL`。 + +例如 `method:GET and status >= 400 | select host, count(*) group by host` + +注: + +- 如果只需要检索原日志,不需要执行 SQL 分析,可以省略竖线和 SQL 语句。 +- 如果不需要检索,只需要 SQL 分析,那么检索语句可以写为 `*`,表示匹配所有记录。即 `* | SQL`。如果查询的日志集没有开启索引,也可以省略检索语句和竖线,只写 SQL 语句。 + +查询相关限制如下: + +- 每个账户支持最多的查询并发数是 15 个 +- 限制返回的结果集大小不超过 1MB 或 1000 条记录。 + +检索语法请参考 [检索语法](https://cloud.baidu.com/doc/BLS/s/Okbta3asp) + +SQL 语句中可以不包括 from 子句,语法详情可以参考 [SQL 语法](https://cloud.baidu.com/doc/BLS/s/xk5cc9piu) + +通过以下代码,您可以在指定日志集中查询满足条件的日志记录。 + +```go +args := &api.QueryLogRecordArgs{ + // 必选参数 + Query: "select count(*)", // 查询SQL + StartDateTime: "2021-01-01T10:11:44Z", + EndDateTime: "2021-12-10T16:11:44Z", + // 可选参数 + LogStreamName: "json-logStream", // 不指定则在日志集所有日志流中查询 + Limit: 100, +} +res, err := blsClient.QueryLogRecord("demo", args) +if err != nil { + fmt.Println("Query logRecord failed: ", err) +} else { + fmt.Println("LogRecords result: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[QueryLogRecord](https://cloud.baidu.com/doc/BLS/s/hk8to0l9o) + +## FastQuery操作 + +### 创建FastQuery + +创建快速查询的实例名称必须遵循以下准则: + +- 每个账户每个区域快速查询名称不能相同 +- 快速查询名称长度不能超过128个字符 +- 快速查询名称包含的字符仅限于:`a-z, A-Z, 0-9, '_', '-', '.'` + +通过以下代码,可以创建一个快速查询。 + +```go +args := &api.CreateFastQueryBody{ + FastQueryName: "macro", + Query: "select count(*)", + LogStoreName: "demo", + // 可选参数 + Description: "calculate record number", + LogStreamName: "json-logStream", +} +err := blsClient.CreateFastQuery(args) +if err != nil { + fmt.Println("Create fastQuery failed: ", err) +} else { + fmt.Println('Create fastQuery success.') +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[CreateFastQuery](https://cloud.baidu.com/doc/BLS/s/kk8to0m6g) + +### 获取指定FastQuery + +通过以下代码,获取指定名称的快速查询的详细信息。 + +```go +res, err := blsClient.DescribeFastQuery("macro") +if err != nil { + fmt.Println("Get fastQuery failed: ", err) +} else { + fmt.Println("Fastquery info: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DescribeFastQuery](https://cloud.baidu.com/doc/BLS/s/Zk8to0mmn) + +### 更新指定FastQuery + +通过以下代码,更新指定名称的快速查询实例信息。 + +```go +args := &api.UpdateFastQueryBody{ + LogStoreName: "demo", + // 可选参数 + Query: "select * limit 3", + Description: "Top 3", + LogStreamName: "", +} +err := blsClient.UpdateFastQuery("macro", args) +if err != nil { + fmt.Println("Update fastQuery failed: ", err) +} else { + fmt.Println("Update fastQuery success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[UpdateFastQuery](https://cloud.baidu.com/doc/BLS/s/Ik8to0mei) + +### 获取FastQuery列表 + +通过以下代码,获取当前用户保存的快速查询列表。 + +```go +// 可选参数列表 +args := &api.QueryConditions{ + NamePattern: "m", + Order: "desc", + OrderBy: "", + PageNo: 1, + PageSize: 20, +} +res, err := blsClient.ListFastQuery(args) +if err != nil { + fmt.Println("List fastQuery failed: ", err) +} else { + fmt.Println("FastQuery list: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[ListFastQuery](https://cloud.baidu.com/doc/BLS/s/0k8to0lyf) + +### 删除指定FastQuery + +通过以下代码,删除指定名称的快速查询示例。 + +```go +err := blsClient.DeleteFastQuery("macro") +if err != nil { + fmt.Println("Delete fastQuery failed: ", err) +} else { + fmt.Println("Delete fastQuery success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DeleteFastQuery](https://cloud.baidu.com/doc/BLS/s/Uk8to0lqd) + +## Index操作 + +### 创建Index + +通过以下代码,为指定的日志集创建索引。 + +```go +indexMappings := map[string]api.LogField{ + "age": { + Type: "long", + }, + "salary": { + Type: "text", + }, + "name": { + Type: "text", + }, +} +err := blsClient.CreateIndex("demo", true, indexMappings) // true表示索引开启状态 +if err != nil { + fmt.Println("Create index failed: ", err) +} else { + fmt.Println("Create index success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[CreateIndex](https://cloud.baidu.com/doc/BLS/s/dkbt4q6ps) + +### 更新指定Index + +通过以下代码,更新指定日志集的索引结构。 + +```go +indexMappings := map[string]api.LogField{ + "age": { + Type: "long", + }, + "wage": { + Type: "float", + }, + "name": { + Type: "text", + }, +} +err := blsClient.UdpateIndex("demo", true, indexMappings) +if err != nil { + fmt.Println("Update index failed: ", err) +} else { + fmt.Println("Update index success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[UpdateIndex](https://cloud.baidu.com/doc/BLS/s/bkbt4w0fe) + +### 获取指定Index + +通过以下代码,获取指定日志集的索引结构。 + +```go +res, err := blsClient.DescribeIndex("demo") +if err != nil { + fmt.Println("Get index failed: ", err) +} else { + fmt.Println("Index info: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DescribeIndex](https://cloud.baidu.com/doc/BLS/s/1kbt4yem2) + +### 删除指定Index + +通过以下代码,删除指定日志集的索引,该操作会将索引数据清空。 + +```go +res, err := blsClient.DeleteIndex("demo") +if err != nil { + fmt.Println("Delete index failed: ", err) +} else { + fmt.Println("Delete index success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DeleteIndex](https://cloud.baidu.com/doc/BLS/s/bkbt56uvu) + +## LogShipper操作 + +### 创建LogShipper + +创建投递任务,需要遵循以下准则: + +- 每个日志集可以创建多个投递任务 +- 总投递任务上限为300 +- 投递任务名称,最长63个字符,包含字母、数字、-和_ +- 投递开始时间,最早为前180天,最迟为后24小时,默认为任务创建时间为开始时间,格式为ISO8601 + +```go +args := &api.CreateLogShipperBody{ + LogShipperName: "demo", + LogStoreName: "store", + StartTime: "2021-07-06T19:01:00Z", + DestConfig: &api.ShipperDestConfig{ + BOSPath: "bucket_1/demo/", + }, +} +id, err := blsClient.CreateLogShipper(args) +if err != nil { + fmt.Println("Create LogShipper failed: ", err) +} else { + fmt.Printf("Create LogShipper %s success.", id) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[CreateLogShipper](https://cloud.baidu.com/doc/BLS/s/Skref348s) + +### 更新指定LogShipper + +通过以下代码,更新指定的投递任务,目前不支持更改投递任务的日志集、起始时间和目的类型。 + +```go +args := &api.UpdateLogShipperBody{ + LogShipperName: "shipper-sdk", + DestConfig: &api.ShipperDestConfig{ + PartitionFormatLogStream: true, + MaxObjectSize: 50, + CompressType: "snappy", + DeliverInterval: 30, + StorageFormat: "json", + }, +} +err := blsClient.UpdateLogShipper("logShipperID", args) +if err != nil { + fmt.Println("Update LogShipper failed: ", err) +} else { + fmt.Println("Update LogShipper success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[UpdateLogShipper](https://cloud.baidu.com/doc/BLS/s/ukrektd01) + +### 查询指定LogShipper + +通过以下代码,获取指定投递任务的详情信息。 + +```go +res, err := blsClient.GetLogShipper("logShipperID") +if err != nil { + fmt.Println("Get LogShipper failed: ", err) +} else { + fmt.Println("LogShipper info: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[GetLogShipper](https://cloud.baidu.com/doc/BLS/s/xkrekxc8z) + +### 获取LogShipper列表 + +通过以下代码,查看符合查询条件的投递任务。 + +```go +args := &api.ListLogShipperCondition{ + LogShipperID: "logShipperID", + LogStoreName: "demo*", + Status: "Running", +} +res, err := blsClient.ListLogShipper(args) +if err != nil { + fmt.Println("Get LogShipper list failed: ", err) +} else { + fmt.Println("List LogShipper success: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[ListLogShipper](https://cloud.baidu.com/doc/BLS/s/bkrekyq51) + +### 查看LogShipper执行记录 + +通过以下代码,查看投递任务的执行记录。 + +```go +args := &api.ListShipperRecordCondition{ + SinceHours: 20 * 24, +} +res, err := blsClient.ListLogShipperRecord("logShipperID", args) +if err != nil { + fmt.Println("Get LogShipper record failed: ", err) +} else { + fmt.Println("Get LogShipper record success: ", res) +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[ListLogShipperRecord](https://cloud.baidu.com/doc/BLS/s/dkrel0hiy) + +### 启停LogShipper + +#### 单个启停 + +通过以下代码,启停指定的投递任务。 + +```go +args := &api.SetSingleShipperStatusCondition{DesiredStatus: "Paused"} +err := blsClient.SetSingleLogShipperStatus("logShipperID", args) +if err != nil { + fmt.Println("Set LogShipper status failed: ", err) +} else { + fmt.Println("Set LogShipper status success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[SetSingleLogShipperStatus](https://cloud.baidu.com/doc/BLS/s/dkrel1tsv) + +#### 批量启停 + +通过以下代码,批量启停投递任务。 + +```go +args := &api.BulkSetShipperStatusCondition{ + LogShipperIDs: []string{"id1_to_set", "id2_to_set"}, + DesiredStatus: "Paused", +} +err := blsClient.BulkSetLogShipperStatus(args) +if err != nil { + fmt.Println("Bulk set LogShipper status failed: ", err) +} else { + fmt.Println("Bulk set LogShipper status success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[BulkSetLogShipperStatus](https://cloud.baidu.com/doc/BLS/s/6krel2nl8) + +### 删除LogShipper + +#### 单个删除 + +通过以下代码,删除指定的投递任务。 + +```go +err := blsClient.DeleteSingleLogShipper("logShipperID") +if err != nil { + fmt.Println("Delete LogShipper failed: ", err) +} else { + fmt.Println("Delete LogShipper success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[DeleteSingleLogShipper](https://cloud.baidu.com/doc/BLS/s/1krel3v47) + +#### 批量删除 + +通过一下代码,批量删除投递任务。 + +```go +ids := []string{"id1_to_del", "id2_to_del"} +args := &api.BulkDeleteShipperCondition{LogShipperIDs:ids} +err := blsClient.BulkDeleteLogShipper("logShipperID") +if err != nil { + fmt.Println("Bulk delete LogShipper failed: ", err) +} else { + fmt.Println("Bulk delete LogShipper success.") +} +``` + +> **提示:** +> +> - 详细的参数配置及限制条件,可以参考BLS API 文档[BulkDeleteLogShipper](https://cloud.baidu.com/doc/BLS/s/Ykrel4g5f) + diff --git a/bce-sdk-go/doc/BOS.md b/bce-sdk-go/doc/BOS.md new file mode 100644 index 0000000..592054d --- /dev/null +++ b/bce-sdk-go/doc/BOS.md @@ -0,0 +1,1986 @@ +# BOS服务 + +# 概述 + +本文档主要介绍BOS GO SDK的使用。在使用本文档前,您需要先了解BOS的一些基本知识,并已开通了BOS服务。若您还不了解BOS,可以参考[产品描述](https://cloud.baidu.com/doc/BOS/ProductDescription.html)和[入门指南](https://cloud.baidu.com/doc/BOS/GettingStarted-new.html)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BOS访问域名](https://cloud.baidu.com/doc/BOS/DevRef.html#BOS.E8.AE.BF.E9.97.AE.E5.9F.9F.E5.90.8D)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/Regions.html)。 + +目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:`http://bj.bcebos.com`,广州区域:`http://gz.bcebos.com`,苏州区域:`http://su.bcebos.com`。对应信息为: + +访问区域 | 对应Endpoint +---|--- +BJ | bj.bcebos.com +GZ | gz.bcebos.com +SU | su.bcebos.com + +## 获取密钥 + +要使用百度云BOS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BOS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BOS Client + +BOS Client是BOS服务的客户端,为开发者与BOS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BOS Client + +通过AK/SK方式访问BOS,用户可以参考如下代码新建一个BOS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bos" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BosClient + bosClient, err := bos.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BOS/GettingStarted.html#.E7.AE.A1.E7.90.86ACCESSKEY)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BOS的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://bj.bcebos.com`。 + +### 使用STS创建BOS Client + +**申请STS token** + +BOS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问BOS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/BOS/API.html#STS.E7.AE.80.E4.BB.8B)。 + +**用STS token新建BOS Client** + +申请好STS后,可将STS Token配置到BOS Client中,从而实现通过STS Token创建BOS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建BOS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/bos" //导入BOS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建BOS服务的Client对象,Endpoint使用默认值 + bosClient, err := bos.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create bos client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + bosClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置BOS Client时,无论对应BOS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问BOS + +BOS支持HTTPS传输协议,您可以通过在创建BOS Client对象时指定的Endpoint中指明HTTPS的方式,在BOS GO SDK中使用HTTPS访问BOS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos" + +ENDPOINT := "https://bj.bcebos.com" //指明使用HTTPS协议 +AK, SK := , +bosClient, _ := bos.NewClient(AK, SK, ENDPOINT) +``` + +## 配置BOS Client + +如果用户需要配置BOS Client的一些细节的参数,可以在创建BOS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问BOS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos" + +//创建BOS Client对象 +AK, SK := , +ENDPOINT := "bj.bcebos.com" +client, _ := bos.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos" + +AK, SK := , +ENDPOINT := "bj.bcebos.com" +client, _ := bos.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos" + +AK, SK := , +ENDPOINT := "bj.bcebos.com" +client, _ := bos.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问BOS时,创建的BOS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建BOS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# Bucket管理 + +Bucket既是BOS上的命名空间,也是计费、权限控制、日志记录等高级功能的管理实体。 + +- Bucket名称在所有区域中具有全局唯一性,且不能修改。 + +> **说明:** +> +> 百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/Regions.html)。 +> 目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:`http://bj.bcebos.com`,广州区域:`http://gz.bcebos.com`,苏州区域:`http://su.bcebos.com`。 + +- 存储在BOS上的每个Object都必须包含在一个Bucket中。 +- 一个用户最多可创建100个Bucket,但每个Bucket中存放的Object的数量和大小总和没有限制,用户不需要考虑数据的可扩展性。 + +## Bucket权限管理 + +### 设置Bucket的访问权限 + +如下代码将Bucket的权限设置为了private。 + +```go +err := bosClient.PutBucketAclFromCanned(bucketName, "private") +``` + +用户可设置的CannedACL包含三个值:`private` 、`public-read` 、`public-read-write`,它们分别对应相关权限。具体内容可以参考BOS API文档 [使用CannedAcl方式的权限控制](https://cloud.baidu.com/doc/BOS/API.html#.4F.FA.21.55.58.27.F8.31.85.2D.01.55.89.10.A7.16)。 + +### 设置指定用户对Bucket的访问权限 + +BOS还可以实现设置指定用户对Bucket的访问权限,参考如下代码实现: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +// 1. 直接上传ACL文件流 +aclBodyStream := bce.NewBodyFromFile("") +err := bosClient.PutBucketAcl(bucket, aclBodyStream) + +// 2. 直接使用ACL json字符串 +aclString := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" //指定用户ID + }], + "permission":["FULL_CONTROL"] //指定用户权限 + } + ] +}` +err := bosClient.PutBucketAclFromString(bucket, aclString) + +// 3. 使用ACL文件 +err := bosClient.PutBucketAclFromFile(bucket, "") + +// 4. 使用ACL struct对象设置 +grantUser1 := api.GranteeType{""} +grantUser2 := api.GranteeType{""} +grant1 := api.GrantType{ + Grantee: []api.GranteeType{grantUser1}, + Permission: []string{"FULL_CONTROL"} +} +grant2 := api.GrantType{ + Grantee: []api.GranteeType{granteUser2}, + Permission: []string{"READ"} +} +grantArr := make([]api.GranteType) +grantArr = append(grantArr, grant1) +grantArr = append(grantArr, grant2) +args := &api.PutBucketAclArgs{grantArr} +err := bosClient.PutBucketAclFromStruct(bucketName, args) +``` + +> **注意:** +> Permission中的权限设置包含三个值:`READ`、`WRITE`、`FULL_CONTROL`,它们分别对应相关权限。具体内容可以参考BOS API文档 [上传ACL文件方式的权限控制](https://cloud.baidu.com/doc/BOS/API.html#.D4.56.61.2C.A5.B1.68.B6.42.32.3E.18.15.BD.CE.43)。 +> ACL规则比较复杂,直接编辑ACL的文件或JSON字符串比较困难,因此提供了第四种方式方便使用代码创建ACL规则。 + +### 设置更多Bucket访问权限 + +1. 通过设置referer白名单方式设置防盗链 + +```go +aclString := `{ + "accessControlList":[ + { + "grantee":[{"id":"*"]}, //指定用户ID为全部用户 + "permission":["FULL_CONTROL"], //指定用户权限 + "condition":[{"referer": {"stringEquals": "http://allowed-domain/"}}] + } + ] +}` +err := bosClient.PutBucketAclFromString(bucket, aclString) +``` + +2. 限制客户端IP访问,只允许部分客户端IP访问 + +```go +aclString := `{ + "accessControlList":[ + { + "grantee":[{"id":"*"]}, //指定用户ID为全部用户 + "permission":["READ"], //指定用户权限 + "condition":[{"ipAddress": ["ip-1", "ip-2"]}] + } + ] +}` +err := bosClient.PutBucketAclFromString(bucket, aclString) +``` + +### 设置STS临时token权限 + +对于通过STS方式创建的临时访问身份,管理员也可进行专门的权限设定。 + +STS的简介及设置临时权限的方式可参见[临时授权访问](https://cloud.baidu.com/doc/BOS/API.html#.E4.B8.B4.E6.97.B6.E6.8E.88.E6.9D.83.E8.AE.BF.E9.97.AE)。 + +使用BOS GO SDK设置STS临时token权限可参考如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/services/sts" + +AK, SK := , +stsClient, err := sts.NewClient(AK, SK) +aclString := `{ + "accessControlList":[ + { + "grantee":[{"id":"*"]}, //指定用户ID为全部用户 + "permission":["FULL_CONTROL"], //指定用户权限 + "condition":[{"referer": {"stringEquals": "http://allowed-domain/"}}] + } + ] +}` +//使用有效期为300秒且指定ACL的方式获取临时STS token +sts, err := stsClient.GetSessionToken(300, aclString) +``` + +### 查看Bucket的访问权限 + +用户可以通过如下接口查看Bucket的访问权限,注意:Bucket的访问权限不能删除,默认为私有。 + +```go +result, err := bosClient.GetBucketAcl(bucketName) +``` + +返回的结果对象的字段包含了访问权限的详细内容,具体定义如下: + +```go +type GetBucketAclResult struct { + AccessControlList []struct{ + Grantee []struct { + Id string + } + Permission []string + } + Owner struct { + Id string + } +} +``` + +## 查看Bucket所属的区域 + +Bucket Location即Bucket Region,百度云支持的各region详细信息可参见[区域选择说明](https://cloud.baidu.com/doc/Reference/Regions.html)。 + +如下代码可以获取该Bucket的Location信息: + +```go +location, err := bosClient.GetBucketLocation(bucketName) +``` + +## 新建Bucket + +如下代码可以新建一个Bucket: + +```go +// 新建Bucket的接口为PutBucket,需指定Bucket名称 +if loc, err := bosClient.PutBucket(); err != nil { + fmt.Println("create bucket failed:", err) +} else { + fmt.Println("create bucket success at location:", loc) +} +``` + +> **注意:** 由于Bucket的名称在所有区域中是唯一的,所以需要保证bucketName不与其他所有区域上的Bucket名称相同。 +> +> Bucket的命名有以下规范: +> - 只能包括小写字母,数字,短横线(-)。 +> - 必须以小写字母或者数字开头。 +> - 长度必须在3-63字节之间。 + +## 列举Bucket + +如下代码可以列出用户所有的Bucket: + +```go +if res, err := bosClient.ListBuckets(); err != nil { + fmt.Println("list buckets failed:", err) +} else { + fmt.Println("owner:", res.Owner) + for i, b := range res.Buckets { + fmt.Println("bucket", i) + fmt.Println(" Name:", b.Name) + fmt.Println(" Location:", b.Location) + fmt.Println(" CreationDate:", b.CreationDate) + } +} +``` + +## 删除Bucket + +如下代码可以删除一个Bucket: + +```go +err := bosClient.DeleteBucket(bucketName) +``` + +> **注意:** +> - 在删除前需要保证此Bucket下的所有Object已经已被删除,否则会删除失败。 +> - 在删除前确认该Bucket没有开通跨区域复制,不是跨区域复制规则中的源Bucket或目标Bucket,否则不能删除。 + +## 判断Bucket是否存在 + +若用户需要判断某个Bucket是否存在,则如下代码可以做到: + +```go +exists, err := bosClient.DoesBucketExist(bucketName) +if err == nil && exists { + fmt.Println("Bucket exists") +} else { + fmt.Println("Bucket not exists") +} +``` + + +> **注意:** +> 如果Bucket不为空(即Bucket中有Object存在),则Bucket无法被删除,必须清空Bucket后才能成功删除。 + + +# 文件管理 + +## 上传文件 + +在BOS中,用户操作的基本数据单元是Object。Object包含Key、Meta和Data。其中,Key是Object的名字;Meta是用户对该Object的描述,由一系列Name-Value对组成;Data是Object的数据。 + +BOS GO SDK提供了丰富的文件上传接口,可以通过以下方式上传文件: + +- 简单上传 +- 追加上传 +- 抓取上传 +- 分块上传 + +### 简单上传 + +BOS在简单上传的场景中,支持以指定文件形式、以数据流方式、以二进制串方式、以字符串方式执行Object上传,请参考如下代码: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" + +// 从本地文件上传 +etag, err := bosClient.PutObjectFromFile(bucketName, objectName, fileName, nil) + +// 从字符串上传 +str := "test put object" +etag, err := bosClient.PutObjectFromString(bucketName, objectName, str, nil) + +// 从字节数组上传 +byteArr := []byte("test put object") +etag, err := bosClient.PutObjectFromBytes(bucketName, objectName, byteArr, nil) + +// 从文件上传 +body, err := bce.NewBodyFromFile(fileName) +etag, err := bosClient.PutObject(bucketName, objectName, body, nil) + +// 从数据流上传(分块编码上传) +reader, err := os.Open(fileName) +etag, err := bosClient.PutObjectFromStream(bucketName, objectName, reader, nil) + +// 使用基本接口,提供必需参数从数据流上传 +reader, err := os.Open(fileName) +body, err := bce.NewBodyFromSizedReader(reader, -1) +etag, err := bosClient.BasicPutObject(bucketName, objectName, body) +``` + +Object以文件的形式上传到BOS中,上述简单上传的接口支持不超过5GB的Object上传。在请求处理成功后,BOS会在Header中返回Object的ETag作为文件标识。 + +**设置文件元信息** + +文件元信息(Object Meta),是对用户在向BOS上传文件时,同时对文件进行的属性描述,主要分为分为两种:设置HTTP标准属性(HTTP Headers)和用户自定义的元信息。 + +***设定Object的Http Header*** + +BOS GO SDK本质上是调用后台的HTTP接口,因此用户可以在上传文件时自定义Object的Http Header。常用的http header说明如下: + +名称 | 描述 |默认值 +---|---|--- +Content-MD5 | 文件数据校验,设置后BOS会启用文件内容MD5校验,把您提供的MD5与文件的MD5比较,不一致会抛出错误 | 有 +Content-Type | 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如没有指定,BOS则根据文件的扩展名自动生成,如文件没有扩展名则填默认值 | application/octet-stream +Content-Disposition | 指示MIME用户代理如何显示附加的文件,打开或下载,及文件名称 | 无 +Content-Length | 上传的文件的长度,超过流/文件的长度会截断,不足为实际值 | 流/文件的长度 +Expires| 缓存过期时间 | 无 +Cache-Control | 指定该Object被下载时的网页的缓存行为 | 无 + +参考代码如下: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.PutObjectArgs) + +// 设置上传内容的MIME类型 +args.ContentType = "text/javascript" + +// 设置上传内容的长度 +args.ContentLength = 1024 + +// 设置缓存过期时间 +args.Expires = "Mon, 19 Mar 2018 11:55:32 GMT" + +// 设置缓存行为 +args.CacheControl = "max-age=3600" + +etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args) +``` + +> 注意:用户上传对象时SDK会自动设置ContentLength和ContentMD5,用来保证数据的正确性。如果用户自行设定ContentLength,必须为大于等于0且小于等于实际对象大小的数值,从而上传截断部分的内容,为负数或大于实际大小均报错。 + +***用户自定义元信息*** + +BOS支持用户自定义元数据来对Object进行描述。如下代码所示: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.PutObjectArgs) + +// 设置用户自定义元数据 +args.UserMeta = map[string]string{ + "name1": "my-metadata1", + "name2": "my-metadata2", +} + +etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args) +``` + +> **提示:** +> - 在上面代码中,用户自定义了一个名字为“name1”和“name2”,值分别为“my-metadata1”和“my-metadata2”的元数据 +> - 当用户下载此Object的时候,此元数据也可以一并得到 +> - 一个Object可以有多个类似的参数,但所有的User Meta总大小不能超过2KB + +**上传Object时设置存储类型** + +BOS支持标准存储、低频存储和冷存储,上传Object并存储为低频存储类型通过指定StorageClass实现,三种存储类型对应的参数如下: + +存储类型 | 参数 +---|--- +标准存储 | STANDRAD +低频存储 | STANDARD_IA +冷存储 | COLD + +以低频存储为例,代码如下: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.PutObjectArgs) +args.StorageClass = api.STORAGE_CLASS_STANDARD_IA +etag, err := bosClient.PutObject(bucketName, objectName, bodyStream, args) +``` + +### 追加上传 + +上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。 + +正因如此,百度云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。当您的网络情况较差时,推荐使用AppendObject的方式进行上传,每次追加较小数据(如256kb)。 + +通过AppendObject方式上传示例代码如下: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.AppendObjectArgs) + +// 1. 原始接口上传,设置为低频存储,设置追加的偏移位置 +args.StorageClass = api.STORAGE_CLASS_STANDARD_IA +args.Offset = 1024 +res, err := bosClient.AppendObject(bucketName, objectName, bodyStream, args) + +// 2. 封装的简单接口,仅支持设置offset +res, err := bosClient.SimpleAppendObject(bucketName, objectName, bodyStream, offset) + +// 3. 封装的从字符串上传接口,仅支持设置offset +res, err := bosClient.SimpleAppendObjectFromString(bucketName, objectName, "abc", offset) + +// 4. 封装的从给出的文件名上传文件的接口,仅支持设置offset +res, err := bosClient.SimpleAppendObjectFromFile(bucketName, objectName, "", offset) + +fmt.Println(res.ETag) // 打印ETag +fmt.Println(res.ContentMD5) // 打印ContentMD5 +fmt.Println(res.NextAppendOffset) // 打印NextAppendOffset +``` + +### 抓取上传 + +BOS支持用户提供的url自动抓取相关内容并保存为指定Bucket的指定名称的Object。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.FetchObjectArgs) + +// 1. 原始接口抓取,设置为异步抓取模式 +args.FetchMode = api.FETCH_MODE_ASYNC +res, err := bosClient.FetchObject(bucket, object, url, args) + +// 2. 基本抓取接口,默认为同步抓取模式 +res, err := bosClient.BasicFetchObject(bucket, object, url) + +// 3. 易用接口,直接指定可选参数 +res, err := bosClient.SimpleFetchObject(bucket, object, url, + api.FETCH_MODE_ASYNC, api.STORAGE_CLASS_STANDARD_IA) + +fmt.Println(res.ETag) // 打印ETag +``` + +### 分块上传 + +除了通过简单上传几追加上传方式将文上传件到BOS以外,BOS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如: + +- 需要支持断点上传。 +- 上传超过5GB大小的文件。 +- 网络条件较差,和BOS的服务器之间的连接经常断开。 +- 需要流式地上传文件。 +- 上传文件之前,无法确定上传文件的大小。 + +BOS GO SDK提供了分块操作的控制参数: + +- MultipartSize:每个分块的大小,默认为10MB,最小不得低于5MB +- MaxParallel:分块操作的并发数,默认为10 + +下面的示例代码设置了分块的大小为20MB,并发数为100: + +``` +// import "github.com/baidubce/bce-sdk-go/services/bos" + +client := bos.NewClient(, , ) +client.MultipartSize = 20 * (1 << 10) +client.MaxParallel = 100 +``` + +除了上述参数外,还会对设置的每个分块数进行1MB对齐,同时限制是最大分块数目不得超过10000,如果分块较小导致分块数超过这个上限会自动调整分块大小。 + +下面将一步步介绍Multipart Upload的实现。假设有一个文件,本地路径为 `/path/to/file.zip`,由于文件比较大,将其分块传输到BOS中。 + +**初始化Multipart Upload** + +使用`BasicInitiateMultipartUpload`方法来初始化一个基本的分块上传事件: + +```go +res, err := bosClient.BasicInitiateMultipartUpload(bucketName, objectKey) +fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId +``` + +返回结果中含有 `UploadId` ,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。 + +***上传低频存储类型Object的初始化*** + +BOS GO SDK提供的`InitiateMultipartUpload`接口可以设置其他分块上传的相关参数,下面的代码初始化了低频存储的一个分块上传事件: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.InitiateMultipartUploadArgs) +args.StorageClass = api.STORAGE_CLASS_STANDARD_IA +res, err := bosClient.InitiateMultipartUpload(bucketName, objectKey, contentType, args) +fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId +``` + +***上传冷存储类型Object的初始化*** + +初始化低频存储的一个分块上传事件: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.InitiateMultipartUploadArgs) +args.StorageClass = api.STORAGE_CLASS_COLD +res, err := bosClient.InitiateMultipartUpload(bucketName, objectKey, contentType, args) +fmt.Println(res.UploadId) // 打印初始化分块上传后获取的UploadId +``` + +**上传分块** + +接着,把文件分块上传。 + +```go +// import "github.com/baidubce/bce-sdk-go/bce" +// import "github.com/baidubce/bce-sdk-go/services/bos" +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +file, _ := os.Open("/path/to/file.zip") + +// 分块大小按MULTIPART_ALIGN=1MB对齐 +partSize := (bosClient.MultipartSize + + bos.MULTIPART_ALIGN - 1) / bos.MULTIPART_ALIGN * bos.MULTIPART_ALIGN + +// 获取文件大小,并计算分块数目,最大分块数MAX_PART_NUMBER=10000 +fileInfo, _ := file.Stat() +fileSize := fileInfo.Size() +partNum := (fileSize + partSize - 1) / partSize +if partNum > bos.MAX_PART_NUMBER { // 超过最大分块数,需调整分块大小 + partSize = (fileSize + bos.MAX_PART_NUMBER + 1) / bos.MAX_PART_NUMBER + partSize = (partSize + bos.MULTIPART_ALIGN - 1) / bos.MULTIPART_ALIGN * bos.MULTIPART_ALIGN + partNum = (fileSize + partSize - 1) / partSize +} + +// 创建保存每个分块上传后的ETag和PartNumber信息的列表 +partEtags := make([]api.UploadInfoType) + +// 逐个分块上传 +for i := int64(1); i <= partNum; i++ { + // 计算偏移offset和本次上传的大小uploadSize + uploadSize := partSize + offset := partSize * (i - 1) + left := fileSize - offset + if left < partSize { + uploadSize = left + } + + // 创建指定偏移、指定大小的文件流 + partBody, _ := bce.NewBodyFromSectionFile(file, offset, uploadSize) + + // 上传当前分块 + etag, err := bosClient.BasicUploadPart(bucketName, objectKey, uploadId, int(i), partBody) + + // 保存当前分块上传成功后返回的序号和ETag + partEtags = append(partEtags, api.UploadInfoType{int(partNum), etag}) +} +``` + +上面代码的核心是调用 `BasicUploadPart` 方法来上传每一个分块,但是要注意以下几点: + +- BasicUploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于等于5MB。但是该接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验。 +- 为了保证数据在网络传输过程中不出现错误,建议您在`BasicUploadPart`后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。 +- Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。 +- 每次上传Part之后,BOS的返回结果会包含一个 `PartETag`对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些`PartETag` 对象将被保存到List中。 + +**完成分块上传** + +如下代码所示,完成分块上传: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +completeArgs := &api.CompleteMultipartUploadArgs{Parts: partEtags} +res, _ := bosClient.CompleteMultipartUploadFromStruct( + bucketName, objectKey, uploadId, completeArgs) + +// 输出结果对象的内容 +fmt.Println(res.Location) +fmt.Println(res.Bucket) +fmt.Println(res.Key) +fmt.Println(res.ETag) +``` + +上面代码中的 `partETags`是第二部中保存的partETag的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。 + +**取消分块上传** + +用户可以使用abortMultipartUpload方法取消分块上传。 + +```go +bosClient.AbortMultipartUpload(bucketName, objectKey, uploadId) +``` + +**获取未完成的分块上传** + +用户可以使用 `ListMultipartUploads` 方法获取Bucket内未完成的分块上传事件。 + +```go +// 列出给定bucket下所有未完成的分块信息 +res, err := BasicListMultipartUploads(bucketName) + +// 输出返回结果状态信息 +fmt.Println(res.Bucket) +fmt.Println(res.Delimiter) +fmt.Println(res.Prefix) +fmt.Println(res.IsTruncated) +fmt.Println(res.KeyMarker) +fmt.Println(res.NextKeyMarker) +fmt.Println(res.MaxUploads) + +// 遍历所有未完成分块信息列表 +for _, multipartUpload := range res.Uploads { + fmt.Println("Key:", multipartUpload.Key, ", UploadId:", multipartUpload.UploadId) +} +``` + +> **注意:** +> 1. 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextKeyMarker作为下次读取的起点。 +> 2. 若想返回更多分块上传事件的数目,可以使用KeyMarker参数分次读取。 + +**获取所有已上传的块信息** + +用户可以使用 `ListParts` 方法获取某个上传事件中所有已上传的块。 + +```go +// 使用基本接口列出当前上传成功的分块 +res, err := bosClient.BasicListParts(bucketName, objectKey, uploadId) + +// 使用原始接口提供参数,列出当前上传成功的最多100个分块 +args := new(api.ListPartsArgs) +args.MaxParts = 100 +res, err := bosClient.ListParts(bucketName, objectKey, uploadId, args) + +// 打印返回的状态结果 +fmt.Println(res.Bucket) +fmt.Println(res.Key) +fmt.Println(res.UploadId) +fmt.Println(res.Initiated) +fmt.Println(res.StorageClass) +fmt.Println(res.PartNumberMarker) +fmt.Println(res.NextPartNumberMarker) +fmt.Println(res.MaxParts) +fmt.Println(res.IsTruncated) + +// 打印分块信息 +for _, part := range res.Parts { + fmt.Println("PartNumber:", part.PartNumber, ", Size:", part.Size, + ", ETag:", part.ETag, ", LastModified:", part.LastModified) +} +``` + +> **注意:** +> 1. 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextPartNumberMarker作为下次读取的起点。 +> 2. 若想返回更多分块上传事件的数目,可以使用PartNumberMarker参数分次读取。 + +上述示例是使用API依次实现,没有并发执行,如果需要加快速度需要用户实现并发上传的部分。为了方便用户使用,BOS Client特封装了分块上传的并发接口`UploadSuperFile`: + +- 接口:`UploadSuperFile(bucket, object, fileName, storageClass string) error` +- 参数: + - bucket: 上传对象的bucket的名称 + - object: 上传对象的名称 + - fileName: 本地文件名称 + - storageClass: 上传对象的存储类型,默认标准存储 +- 返回值: + - error: 上传过程中的错误,成功则为空 + +用户只需给出`bucket`、`object`、`filename`即可并发的进行分块上传,同时也可指定上传对象的`storageClass`。 + +## 下载文件 + +BOS GO SDK提供了丰富的文件下载接口,用户可以通过以下方式从BOS中下载文件: + +- 简单流式下载 +- 下载到本地文件 +- 范围下载 + +### 简单流式下载 + +用户可以通过如下代码将Object读取到一个流中: + +```go +// 提供Bucket和Object,直接获取一个对象 +res, err := bosClient.BasicGetObject(bucketName, objectName) + +// 获取ObjectMeta +meta := res.ObjectMeta + +// 获取Object的读取流(io.ReadCloser) +stream := res.Body + +// 确保关闭Object读取流 +defer stream.Close() + +// 调用stream对象的Read方法处理Object +... +``` + +> **注意:** +> 1. 上述接口的返回结果对象中包含了Object的各种信息,包含Object所在的Bucket、Object的名称、MetaData以及一个读取流。 +> 2. 可通过结果对象的ObjectMeta字段获取对象的元数据,它包含了Object上传时定义的ETag,Http Header以及自定义的元数据。 +> 3. 可通过结果对象的Body字段获取返回Object的读取流,通过操作读取流将Object的内容读取到文件或者内存中或进行其他操作。 + +### 下载到本地文件 + +用户可以通过如下代码直接将Object下载到指定文件: + +```go +err := bosClient.BasicGetObjectToFile(bucketName, objectName, "path-to-local-file") +``` + +### 范围下载 + +为了实现更多的功能,可以指定下载范围、返回header来实现更精细化地获取Object。如果指定的下载范围是0 - 100,则返回第0到第100个字节的数据,包括第100个,共101字节的数据,即[0, 100]。 + +```go +// 指定范围起始位置和返回header +responseHeaders := map[string]string{"ContentType": "image/gif"} +rangeStart = 1024 +rangeEnd = 2048 +res, err := bosClient.GetObject(bucketName, objectName, responseHeaders, rangeStart, rangeEnd) + +// 只指定起始位置start +res, err := bosClient.GetObject(bucketName, objectName, responseHeaders, rangeStart) + +// 不指定range +res, err := bosClient.GetObject(bucketName, objectName, responseHeaders) + +// 不指定返回可选头部 +res, err := bosClient.GetObject(bucketName, objectName, nil) +``` + +基于范围下载接口,用户可以据此实现文件的分段下载和断点续传。为了方便用户使用,BOS GO SDK封装了并发下载的接口`DownloadSuperFile`: + +- 接口:`DownloadSuperFile(bucket, object, fileName string) error` +- 参数: + - bucket: 下载对象所在bucket的名称 + - object: 下载对象的名称 + - fileName: 该对象保存到本地的文件名称 +- 返回值: + - error: 下载过程中的错误,成功则为空 + +该接口利用并发控制参数执行并发范围下载,直接下载到用户指定的文件中。 + +### 其他使用方法 + +**获取Object的存储类型** + +Object的storage class属性分为`STANDARD`(标准存储)、`STANDARD_IA`(低频存储)和`COLD`(冷存储),通过如下代码可以实现: + +```go +res, err := bosClient.GetObjectMeta(bucketName, objectName) +fmt.Println(res.StorageClass) +``` + +**只获取Object Metadata** + +通过GetObjectMeta方法可以只获取Object Metadata而不获取Object的实体。如下代码所示: + +```go +res, err := bosClient.GetObjectMeta(bucketName, objectName) +fmt.Printf("Metadata: %+v\n", res) +``` + +## 获取文件下载URL + +用户可以通过如下代码获取指定Object的URL: + +```go +// 1. 原始接口,可设置bucket、object名称,过期时间、请求方法、请求头和请求参数 +url := bosClient.GeneratePresignedUrl(bucketName, objectName, + expirationInSeconds, method, headers, params) + +// 2. 基本接口,默认为`GET`方法,仅需设置过期时间 +url := bosClient.BasicGeneratePresignedUrl(bucketName, objectName, expirationInSeconds) +``` + +> **说明:** +> +> * 用户在调用该函数前,需要手动设置endpoint为所属区域域名。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/Regions.html)。目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:`http://bj.bcebos.com`,广州区域:`http://gz.bcebos.com`,苏州区域:`http://su.bcebos.com`。 +> * `expirationInSeconds`为指定的URL有效时长,时间从当前时间算起,为可选参数,不配置时系统默认值为1800秒。如果要设置为永久不失效的时间,可以将`expirationInSeconds`参数设置为-1,不可设置为其他负数。 +> * 如果预期获取的文件时公共可读的,则对应URL链接可通过简单规则快速拼接获取: http://{$bucketName}.{$region}.bcebos.com/{$objectName}。 + +## 列举存储空间中的文件 + +BOS GO SDK支持用户通过以下两种方式列举出object: + +- 简单列举 +- 通过参数复杂列举 + +除此之外,用户还可在列出文件的同时模拟文件夹。 + +### 简单列举 + +当用户希望简单快速列举出所需的文件时,可通过ListObjects方法返回ListObjectsResult对象,ListObjectsResult对象包含了此次请求的返回结果。用户可以从ListObjectsResult对象的Contents字段获取Object的所有描述信息。 + +```go +listObjectResult, err := bosClient.ListObjects(bucketName, nil) + +// 打印当前ListObjects请求的状态结果 +fmt.Println("Name:", listObjectResult.Name) +fmt.Println("Prefix:", listObjectResult.Prefix) +fmt.Println("Delimiter:", listObjectResult.Delimiter) +fmt.Println("Marker:", listObjectResult.Marker) +fmt.Println("NextMarker:", listObjectResult.NextMarker) +fmt.Println("MaxKeys:", listObjectResult.MaxKeys) +fmt.Println("IsTruncated:", listObjectResult.IsTruncated) + +// 打印Contents字段的具体结果 +for _, obj := range listObjectResult.Contents { + fmt.Println("Key:", obj.Key, ", ETag:", obj.ETag, ", Size:", obj.Size, + ", LastModified:", obj.LastModified, ", StorageClass:", obj.StorageClass) +} +``` + +> **注意:** +> 1. 默认情况下,如果Bucket中的Object数量大于1000,则只会返回1000个Object,并且返回结果中IsTruncated值为True,并返回NextMarker做为下次读取的起点。 +> 2. 若想增大返回Object的数目,可以使用Marker参数分次读取。 + +### 通过参数复杂列举 + +除上述简单列举外,用户还可通过设置ListObjectsArgs参数实现各种灵活的查询功能。ListObjectsArgs可设置的参数如下: + +参数 | 功能 +-----|----- +Prefix | 限定返回的object key必须以prefix作为前缀 +Delimiter | 分隔符,是一个用于对Object名字进行分组的字符所有名字包含指定的前缀且第一次出现。Delimiter字符之间的Object作为一组元素 +Marker | 设定结果从marker之后按字母排序的第一个开始返回 +MaxKeys | 限定此次返回object的最大数,如果不设定,默认为1000,max-keys取值不能大于1000 + +> **注意:** +> 1. 如果有Object以Prefix命名,当仅使用Prefix查询时,返回的所有Key中仍会包含以Prefix命名的Object,详见[递归列出目录下所有文件](#递归列出目录下所有文件)。 +> 2. 如果有Object以Prefix命名,当使用Prefix和Delimiter组合查询时,返回的所有Key中会有Null,Key的名字不包含Prefix前缀,详见[查看目录下的文件和子目录](#查看目录下的文件和子目录)。 + +下面我们分别以几个案例说明通过参数列举的方法: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.ListObjectsArgs) + +// 指定最大返回参数为500 +args.MaxKeys = 500 + +// 指定满足特定前缀 +args.Prefix = "my-prefix/" + +// 指定分隔符,实现类似文件夹的功能 +args.Delimiter = "/" + +// 设置特定Object之后的排序结果 +args.Marker = "bucket/object-0" + +listObjectResult, err := bosClient.ListObjects(bucketName, args) +``` + +### 模拟文件夹功能 + +在BOS的存储结果中是没有文件夹这个概念的,所有元素都是以Object来存储,但BOS的用户在使用数据时往往需要以文件夹来管理文件。因此,BOS提供了创建模拟文件夹的能力,其本质上来说是创建了一个size为0的Object。对于这个Object可以上传下载,只是控制台会对以“/”结尾的Object以文件夹的方式展示。 + +用户可以通过Delimiter和Prefix参数的配合模拟出文件夹功能。Delimiter和Prefix的组合效果是这样的: + +如果把Prefix设为某个文件夹名,就可以罗列以此Prefix开头的文件,即该文件夹下递归的所有的文件和子文件夹(目录)。文件名在Contents中显示。 +如果再把 Delimiter 设置为“/”时,返回值就只罗列该文件夹下的文件和子文件夹(目录),该文件夹下的子文件名(目录)返回在CommonPrefixes 部分,子文件夹下递归的文件和文件夹不被显示。 + +如下是几个应用方式: + +**列出Bucket内所有文件** + +当用户需要获取Bucket下的所有文件时,可以参考如下代码: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.ListObjectsArgs) +args.Delimiter = "/" +listObjectResult, err := bosClient.ListObjects(bucketName, args) +``` + +**递归列出目录下所有文件** + +可以通过设置 `Prefix` 参数来获取某个目录下所有的文件: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.ListObjectsArgs) +args.Prefix = "fun/" +listObjectResult, err := bosClient.ListObjects(bucketName, args) +fmt.Println("Objects:") +for _, obj := range listObjectResult.Contents { + fmt.Println(obj.Key) +} +``` + +输出: + + Objects: + fun/ + fun/movie/001.avi + fun/movie/007.avi + fun/test.jpg + +**查看目录下的文件和子目录** + +在 `Prefix` 和 `Delimiter` 结合的情况下,可以列出目录下的文件和子目录: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.ListObjectsArgs) +args.Delimiter = "/" +args.Prefix = "fun/" +listObjectResult, err := bosClient.ListObjects(bucketName, args) + +// 遍历所有的Objects(当前目录和直接子文件) +fmt.Println("Objects:") +for _, obj := range listObjectResult.Contents { + fmt.Println(obj.Key) +} + +// 遍历所有的CommonPrefix(子目录) +fmt.Println("CommonPrefixs:") +for _, obj := range listObjectResult.CommonPrefixes { + fmt.Println(obj.Prefix) +} +``` + +输出: + Objects: + fun/ + fun/test.jpg + + CommonPrefixs: + fun/movie/ + + +返回的结果中,`ObjectSummaries` 的列表中给出的是fun目录下的文件。而`CommonPrefixs`的列表中给出的是fun目录下的所有子文件夹。可以看出`fun/movie/001.avi` ,`fun/movie/007.avi`两个文件并没有被列出来,因为它们属于 `fun` 文件夹下的 `movie` 目录。 + +### 列举Bucket中object的存储属性 + +当用户完成上传后,如果需要查看指定Bucket中的全部Object的storage class属性,可以通过如下代码实现: + +```go +listObjectResult, err := bosClient.ListObjects(bucketName, args) +for _, obj := range listObjectResult.Contents { + fmt.Println("Key:", obj.Key) + fmt.Println("LastModified:", obj.LastModified) + fmt.Println("ETag:", obj.ETag) + fmt.Println("Size:", obj.Size) + fmt.Println("StorageClass:", obj.StorageClass) + fmt.Println("Owner:", obj.Owner.Id, obj.Owner.DisplayName) +} +``` + +## 权限控制 + +### 设置对象的访问权限 + +目前BOS支持两种方式设置ACL。第一种是使用Canned Acl,在PutObjectAcl的时候,通过头域的"x-bce-acl"或者"x-bce-grant-permission"来设置对象的访问权限,当前可设置的权限包括private和public-read,两种类型的header不可以同时在一个请求中出现。第二种方式是上传一个ACL文件。详细信息请参考[设置Object权限控制](https://cloud.baidu.com/doc/BOS/API.html#PutObjectAcl.E6.8E.A5.E5.8F.A3) + +#### 设置Canned ACL + +Canned ACL是预定义的访问权限,用户可选择对某个对象进行设置,支持三种接口: + +```go +// 1. 使用x-bce-acl Header设置 +err := bosClient.PutObjectAclFromCanned(bucket, object, cannedAcl) //cannedAcl可取值为:private、public-read + +// 2. 使用x-bce-grant-{permission} Header设置 +err1 := bosClient.PutObjectAclGrantRead(bucket, object, userId) +err2 := bosClient.PutObjectAclGrantFullControl(bucket, object, userId) +// userId为授权的用户,支持可变参数,传入多个用户ID +``` + +#### 设置自定义ACL + +用户可参考如下代码设置Bucket内的对象的自定义访问权限,支持四种不同参数: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +// 1. 直接上传ACL文件流 +aclBodyStream := bce.NewBodyFromFile("") +err := bosClient.PutObjectAcl(bucket, object, aclBodyStream) + +// 2. 直接使用ACL json字符串 +aclString := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" //指定用户ID + }], + "permission":["FULL_CONTROL"] //指定用户权限 + } + ] +}` +err := bosClient.PutObjectAclFromString(bucket, object, aclString) + +// 3. 使用ACL文件 +err := bosClient.PutObjectAclFromFile(bucket, object, "") + +// 4. 使用ACL struct对象设置 +grantUser1 := api.GranteeType{""} +grantUser2 := api.GranteeType{""} +grant1 := api.GrantType{ + Grantee: []api.GranteeType{grantUser1}, + Permission: []string{"FULL_CONTROL"} +} +grant2 := api.GrantType{ + Grantee: []api.GranteeType{granteUser2}, + Permission: []string{"READ"} +} +grantArr := make([]api.GranteType) +grantArr = append(grantArr, grant1) +grantArr = append(grantArr, grant2) +args := &api.PutObjectAclArgs{grantArr} +err := bosClient.PutObjectAclFromStruct(bucketName, object, args) +``` + +### 获取对象的访问权限 + +如下代码可获取一个对象的访问权限: + +```go +result, err := bosClient.GetObjectAcl(bucketName, object) +``` + +返回结果对象的字段包含了访问权限的详细内容,具体定义如下: + +```go +type GetObjectAclResult struct { + AccessControlList []struct{ + Grantee []struct{ + Id string + } + Permission []string + } +} +``` + +### 删除对象的访问权限 + +对设置过访问权限的对象,可以调用此接口进行删除: + +```go +err := bosClient.DeleteObjectAcl(bucketName, object) +``` + +## 删除文件 + +**删除单个文件** + +可参考如下代码删除了一个Object: + +```go +// 指定要删除Object名称和所在的Bucket名称 +err := bosClient.DeleteObject(bucketName, objectName) +``` + +**删除多个文件** + +用户也可通过一次调用删除同一个Bucket下的多个文件,有如下参数: + +参数名称 | 描述 | 父节点 +---------|---------|-------- +objects | 保存要删除的Object信息的容器,包含一个或多个Object元素 | - ++key | 要删除的Object的名称 | objects + +具体示例如下: + +``` +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +// 1. 原始接口,提供多个Object的List Stream +res, err := bosClient.DeleteMultipleObjects(bucket, objectListStream) + +// 2. 提供json字符串删除 +objectList := `{ + "objects":[ + {"key": "aaa"}, + {"key": "bbb"} + ] +}` +res, err := bosClient.DeleteMultipleObjectsFromString(bucket, objectList) + +// 3. 提供删除Object的List对象 +deleteObjectList := make([]api.DeleteObjectArgs, 0) +deleteObjectList = append(deleteObjectList, api.DeleteObjectArgs{"aaa"}) +deleteObjectList = append(deleteObjectList, api.DeleteObjectArgs{"bbb"}) +multiDeleteObj := &api.DeleteMultipleObjectsArgs{deleteObjectList} +res, err := bosClient.DeleteMultipleObjectsFromStruct(bucket, multiDeleteObj) + +// 4. 直接提供待删除Object的名称列表 +deleteObjects := []string{"aaa", "bbb"} +res, err := bosClient.DeleteMultipleObjectsFromKeyList(bucket, deleteObjects) +``` + +> **说明:** +> +> 一次删除多个Object的时候,返回的结果里包含了未删除成功的Object名称列表。删除部分对象成功时`res`里包含了未删除成功的名称列表。 +> 删除部分对象成功时`err`为`nil`且`res`不为`nil`,判断全部删除成功:`err`为`io.EOF`且`res`为`nil`。 + +## 查看文件是否存在 + +用户可通过如下操作查看某文件是否存在: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" + +_, err := bosClient.GetObjectMeta(bucketName, objectName) +if realErr, ok := err.(*bce.BceServiceError); ok { + if realErr.StatusCode == 404 { + fmt.Println("object not exists") + } +} +fmt.Println("object exists") +``` + +## 获取及更新文件元信息 + +文件元信息(Object Metadata),是对用户上传BOS的文件的属性描述,分为两种:HTTP标准属性(HTTP Headers)和User Meta(用户自定义元信息)。 + +### 获取文件元信息 + +用户通过GetObjectMeta方法可以只获取Object Metadata而不获取Object的实体。如下代码所示: + +```go +res, err := bosClient.GetObjectMeta(bucketName, objectName) +fmt.Printf("Metadata: %+v\n", res) +``` + +### 修改文件元信息 + +BOS修改Object的Metadata通过拷贝Object实现。即拷贝Object的时候,把目的Bucket设置为源Bucket,目的Object设置为源Object,并设置新的Metadata,通过拷贝自身实现修改Metadata的目的。如果不设置新的Metadata,则报错。这种方式下必须使用拷贝模式为“replace”(默认情况为“copy”)。示例如下: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" + +args := new(api.CopyObjectArgs) + +// 必须设置拷贝模式为"replace",默认为"copy"是不能执行Metadata修改的 +args.MetadataDirective="replace" + +// 设置Metadata参数值,具体字段请参考官网说明 +args.LastModified = "Wed, 29 Nov 2017 13:18:08 GMT" +args.ContentType = "text/json" + +// 使用CopyObject接口修改Metadata,源对象和目的对象相同 +res, err := bosClient.CopyObject(bucket, object, bucket, object, args) +``` + +## 拷贝文件 + +### 拷贝一个文件 + +用户可以通过CopyObject方法拷贝一个Object,如下代码所示: + +```go +// 1. 原始接口,可设置拷贝参数 +res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, nil) + +// 2. 忽略拷贝参数,使用默认 +res, err := bosClient.BasicCopyObject(bucketName, objectName, srcBucket, srcObject) + +fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified) +``` + +上述接口返回的结果对象中包含了新Object的ETag和修改时间LastModified。 + +### 设置拷贝参数拷贝Object + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.CopyObjectArgs) + +// 设置用户自定义Metadata +args.UserMeta = map[string]string{"": ""} + +res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, args) +fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified) +``` + +**设置Object的Copy属性** + +用户在执行拷贝的过程中,可以对源Object的Etag或修改状态进行判断,根据判断结果决定是否执行拷贝。详细的参数解释如下: + +| 名称 | 类型 | 描述 | 是否必需 | +| --- | --- | --- | ---- | +| x-bce-copy-source-if-match | String | 如果源Object的ETag值和用户提供的ETag相等,则执行拷贝操作,否则拷贝失败。 | 否 | +| x-bce-copy-source-if-none-match | String | 如果源Object的ETag和用户提供的ETag不相等,则执行拷贝操作,否则拷贝失败。 | 否 | +| x-bce-copy-source-if-unmodified-since | String | 如果源object在x-bce-copy-source-if-unmodified-since之后没被修改,则执行拷贝操作,否则拷贝失败。 | 否 | +| x-bce-copy-source-if-modified-since | String | 如果源object在x-bce-copy-source-if-modified-since之后被修改了,则执行拷贝操作,否则拷贝失败。 | 否 | + +对应的示例代码: + +```go +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +args := new(api.CopyObjectArgs) + +// 设置用户自定义Metadata +args.UserMeta = map[string]string{"": ""} + +// 设置copy-source-if-match +args.IfMatch = "111111111183bf192b57a4afc76fa632" + +// 设置copy-source-if-none-match +args.IfNoneMatch = "111111111183bf192b57a4afc76fa632" + +// 设置copy-source-if-modified-since +args.IfModifiedSince = "Fri, 16 Mar 2018 17:07:21 GMT" + +// 设置copy-source-if-unmodified-since +args.IfUnmodifiedSince = "Fri, 16 Mar 2018 17:07:21 GMT" + +res, err := bosClient.CopyObject(bucketName, objectName, srcBucket, srcObject, args) +fmt.Println("ETag:", res.ETag, "LastModified:", res.LastModified) +``` + +**同步Copy功能** + +当前BOS的CopyObject接口是通过同步方式实现的。同步方式下,BOS端会等待Copy实际完成才返回成功。同步Copy能帮助用户更准确的判断Copy状态,但用户感知的复制时间会变长,且复制时间和文件大小成正比。 + +### 分块拷贝 + +除了通过CopyObject接⼝拷贝文件以外,BOS还提供了另外一种拷贝模式——Multipart Upload Copy。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload Copy,如: + +- 需要支持断点拷贝。 +- 拷贝超过5GB大小的文件。 +- 网络条件较差,和BOS的服务器之间的连接经常断开。 + +分块拷贝与上传对象中的分块上传类似,也是通过三个步骤来实现:初始化、执行分块拷贝和汇总完成,其中初始化和汇总完成的操作同分块上传的接口一致。 + +下面提供分块拷贝的详细示例代码,用户可与分块上传的详细说明进行对比,以便于理解: + +``` +// import "github.com/baidubce/bce-sdk-go/services/bos/api" + +// 1. 初始化分块上传, +initResult, err := bosClient.BasicInitiateMultipartUpload(bucketName, objectKey) +if err != nil { + fmt.Println("initiate multipart upload copy failed:", err) + return +} + +// 2. 执行分块上传 +meta, err := bosClient.GetObjectMeta(bucketName, objectKey) // 获取对象大小以便进行切块 +if err != nil { + fmt.Println("get object length failed:", err) + return +} +const ( + MB = 1 << 20 + MAX_PARTS = 10000 // 分块拷贝的part总数不能超过10000 +) +var ( + leftSize int64 = meta.ContentLength + offset int64 = 0 + + // 单个part须为1MB的整数倍,且不超过5GB,除最后一个外其余part必须大于等于5MB + partSize int64 = 8 * MB +) +totalParts := (meta.ContentLength + partSize - 1) / partSize +if totalParts > MAX_PARTS { + partSize = (meta.ContentLength + MAX_PART - 1) / 10000 + partSize = (partSize + MB - 1) / MB * MB // 按1MB对齐 + totalParts = (meta.ContentLength + partSize - 1) / partSize +} +copyInfo := make([]UploadInfoType, partNumber) +for partNumber := int64(1); partNumber <= totalParts; partNumber++ { + start := (partNumber - 1) * partSize + end := partNumber * partSize + if end > meta.ContentLength { + end = meta.ContentLength + } + + // 设置拷贝的参数,分块拷贝的SourceRange制定当前拷贝的区间,若不指定则拷贝全部对象 + // 还有另外四个参数用户可自行设定:IfMatch、IfNoneMatch、IfModifiedSince、IfUnmodifiedSince + args := &api.UploadPartCopyArgs{ + // 指定拷贝区间时必须使用“bytes=first-last”格式,last是最后一个字节的索引(从0开始算起) + SourceRange: fmt.Sprintf("bytes=%d-%d", start, end-1) + } + res, err := bosClient.UploadPartCopy(destBucket, destObject, + initResult.Bucket, initResult.Key, initResult.UploadId, partNumber, args) + if err != nil { + bosClient.AbortMultipartUpload(initResult.Bucket, initResult.Key, initResult.UploadId) + fmt.Println("abort multipart upload copy with error:", err) + return + } + copyInfo[partNumber-1] = api.UploadInfoType{partNumber, res.ETag} +} + +// 3. 完成汇总 +completeArgs := &api.CompleteMultipartUploadArgs{Parts: copyInfo} +completeRes, err := bosClient.CompleteMultipartUploadFromStruct( + initResult.Bucket, initResult.Key, initResult.UploadId, completeArgs) +if err != nil { + bosClient.AbortMultipartUpload(initResult.Bucket, initResult.Key, initResult.UploadId) + fmt.Println("abort multipart upload copy with error:", err) + return +} +fmt.Println(completeRes.Location) +fmt.Println(completeRes.Bucket) +fmt.Println(completeRes.Key) +fmt.Println(completeRes.ETag) +``` + +# 数据处理及使用 + +## 生命周期管理 + +BOS支持用户对Bucket设置生命周期规则,以自动将过期的文件清除,节省存储空间。针对不同前缀的文件,用户可以同时设置多条规则。 +在为Bucket设置一条生命周期规则时,需注意如下参数的使用方式: + +规则项 | 描述 | 是否必填 | 备注 +-------|--------|------------|-------- +id | 规则的标识符 | 必填 | 同一个bucket内规则id必须唯一,不能重复。如果用户不填系统会自动帮用户生成一个 +status | 规则的状态 | 必填 | 取值为enabled或disabled,当规则处于disabled时规则不生效 +resource | 规则对哪些资源生效 | 必填 | 举例:对samplebucket里以prefix/为前缀的Object生效:`samplebucket/prefix/*` +condition | 规则依赖的条件 | 必填 | 目前只支持time形式 ++time | 时间限制条件 | 必填 | 通过定义的dateGreaterThan实现 +++dateGreaterThan | 描述时间关系 | 必填 | 支持绝对时间date和相对时间days。绝对时间date格式为yyyy-mm-ddThh:mm:ssZ,eg. 2016-09-07T00:00:00Z。绝对时间为UTC时间,必须以00:00:00(UTC 0点)结尾;相对时间days的描述遵循ISO8601,支持的最小时间粒度为天,如:$(lastModified)+P7D表示的时间为object的last-modified之后7天。 +action | 对resource执行的操作动作 | 必填 | - ++name | 执行的操作名称 | 必填 | 取值为Transition、DeleteObject、AbortMultipartUpload ++storageClass | Object的存储类型 | 可选 | action为Transition时可以设定,取值为STANDARD_IA或COLD,表示从原存储类型转为低频存储或冷存储 + +### 设置生命周期规则 + +可通过如下代码设置一条生命周期规则: + +```go +// import "github.com/baidubce/bce-sdk-go/bce" + +ruleStr := `{ + "rule": [ + { + "id": "delete-rule-1", + "status": "enabled", + "resource": ["my-bucket/abc*"], + "condition": { + "time": { + "dateGreaterThan": "2018-01-01T00:00:00Z" + } + }, + "action": { + "name": "DeleteObject" + } + } + ] +}` + +// 1. 通过stream调用接口进行设置 +body, _ := bce.NewBodyFromString(ruleStr) +err := bosClient.PutBucketLifecycle(bucketName, body) + +// 2. 直接传入字符串 +err := bosClient.PutBucketLifecycleFromString(bucketName, ruleStr) +``` + +### 查看生命周期规则 + +可通过如下代码查看Bucket内的生命周期规则: + +```go +res, err := bosClient.GetBucketLifecycle(bucketName) +fmt.Printf("%+v\n", res.Rule) +``` + +### 删除生命周期规则 + +可通过如下代码清空生命周期规则: + +```go +err := bosClient.DeleteBucketLifecycle(bucketName) +``` + +## 跨域资源共享 + +跨域资源共享(Cross-Origin Resource Sharing),简称CORS,是HTML5提供的标准跨域解决方案,BOS目前已经支持CORS标准来实现跨域访问。关于跨域访问的介绍请参考[跨域访问](https://cloud.baidu.com/doc/BOS/DevRef.html#.E8.B7.A8.E5.9F.9F.E8.AE.BF.E9.97.AE)。 + +### 设置CORS规则 + +用户可针对Bucket设置CORS规则,支持通过json字符串、文件、流、对象方式设置: + +```go +// import "github.com/baidubce/bce-sdk-go/service/bos/api" + +// 1. 通过流式调用接口进行设置 +err := bosClient.PutBucketCors(bucketName, body) + +// 2. 直接传入字符串 +err := bosClient.PutBucketCorsFromString(bucketName, corsString) + +// 3. 传入CORS文件名 +err := bosClient.PutBucketCorsFromFile(bucketName, corsFile) + +// 4. 传入对象 +corsObj := &api.BucketCORSType{ + AllowedOrigins: []string{"example.com"}, + AllowedMethods: []string{"HEAD", "GET"}, + AllowedHeaders: []string{"*"}, + AllowedExposeHeaders: []string{"*"}, + MaxAgeSeconds: 3600, +} +err := bosClient.PutBucketCorsFromStruct(bucketName, &api.PutBucketCorsArgs{corsObj}) +``` + +### 获取CORS规则 + +用户可获取指定Bucket的CORS规则: + +```go +result, err := bosClient.GetBucketCors(bucketName) +``` + +结果对象的定义与`PutBucketCorsFromStruct`接口的请求参数相同。 + +### 删除CORS规则 + +可参考如下代码删除Bucket的CORS规则,删除后的Bucket将无法进行跨域访问。 + +```go +err := bosClient.DeleteBucketCors(bucketName) +``` + +## 管理存储类型 + +每个Bucket会有自身的存储类型,如果该Bucket下的Object上传时未指定存储类型则会默认继承该Bucket的存储类型。 + +### 设置Bucket存储类型 + +Bucket默认的存储类型为标准模式,用户可以通过下面的代码进行设置: + +``` +storageClass := "STANDARD_IA" +err := bosClient.PutBucketStorageclass(bucketName, storageClass) +``` + +### 获取Bucket存储类型 + +下面的代码可以查看一个Bucket的默认存储类型: + +``` +storageClass, err := bosClient.GetBucketStorageclass(bucketName) +``` + +## 设置访问日志 + +BOS GO SDK支持将用户访问Bucket时的请求记录记录为日志,用户可以指定访问Bucket的日志存放的位置。日志会包括请求者、Bucket名称、请求时间和请求操作等。关于Bucket日志的详细功能说明可参见[设置访问日志](https://cloud.baidu.com/doc/BOS/DevRef.html#.E6.97.A5.E5.BF.97.E6.A0.BC.E5.BC.8F)。 + +### 开启Bucket日志 + +用户通过设置用于放置日志的Bucket和日志文件前缀来开启Bucket日志功能。下面的示例代码可以设置访问日志的位置和前缀: + +``` +// import "github.com/baidubce/bce-sdk-go/bce" + +// 1. 从JSON字符串设置 +loggingStr := `{"targetBucket": "logging-bucket", "targetPrefix": "my-log/"}` +err := bosClient.PutBucketLoggingFromString(bucketName, loggingStr) + +// 2. 从参数对象设置 +args := new(api.PutBucketLoggingArgs) +args.TargetBucket = "logging-bucket" +args.TargetPrefix = "my-log/" +err := bosClient.PutBucketLoggingFromStruct(bucketName, args) + +// 3. 读取json格式的文件进行设置 +loggingStrem := bce.NewBodyFromFile("") +err := bosClient.PutBucketLogging(bucketName, loggingStream) +``` + +### 查看Bucket日志设置 + +下面的代码分别给出了如何获取给定Bucket的日志配置信息: + +```go +res, err := bosClient.GetBucketLogging(bucketName) +fmt.Println(res.Status) +fmt.Println(res.TargetBucket) +fmt.Println(res.TargetPrefix) +``` + +### 关闭Bucket日志 + +需要关闭Bucket的日志功能是,只需调用删除接口即可实现: + +```go +err := bosClient.DeleteBucketLogging(bucketName) +``` + +## 服务端加密 + +### 设置服务端加密功能 + +用户可针对一个Bucket设置开启服务端加密的功能,所有存储到该Bucket的数据将会进行加密存储,保证数据安全性。 + +```go +err := bosClient.PutBucketEncryption(bucketName, algorithm) +``` +`algorithm`参数为加密算法,当前只支持“AES256”加密算法。 + +### 获取服务端加密 + +用户可调用如下接口获取Bucket的服务端加密功能: + +```go +algorithm, err := bosClient.GetBucketEncryption(bucketName) +``` + +### 删除服务端加密功能 + +用户可调用如下接口删除指定Bucket的服务端加密功能: + +```go +err := bosClient.DeleteBucketEncryption(bucketName) +``` + +## 原图保护功能 + +用户可针对Bucket下存储的图片设置原图保护功能,用户需指定待保护的资源。 + +### 开启原图保护功能 + +通过如下代码开启原图保护功能: + +```go +err := bosClient.PutBucketCopyrightProtection(bucket, resources) +``` + +`resources`参数为可变参数,可指定多个。 + +### 获取原图保护设置 + +用户通过如下示例代码获取Bucket设置的原图保护的资源: + +```go +resources, err := bosClient.GetBucketCopyrightProtection(bucketName) +``` + +### 删除原图保护配置 + +```go +err := bosClient.DeleteBucketCopyrightProtection(bucketName) +``` + +## 静态网站托管 + +### 开启静态网站托管 + +用户可通过如下示例代码设置一个Bucket开启静态网站托管的功能: + +```go +// import "github.com/baidubce/bce-sdk-go/service/bos/api" + +// 1. 通过流式调用接口进行设置 +err := bosClient.PutBucketStaticWebsite(bucketName, body) + +// 2. 直接传入json字符串 +jsonStr := `{"index": "index.html", "notFound": "404.html"}` +err := bosClient.PutBucketStaticWebsiteFromString(bucketName, jsonStr) + +// 3. 传入对象 +args := &api.BucketStaticWebsiteType{ + Index: "index.html", + NotFound: "404.html", +} +err := bosClient.PutBucketStaticWebsiteFromStruct(bucketName, args) + +// 4. 简单接口设置 +err := bosClient.SimplePutBucketStaticWebsite(bucketName, "index.html", "404.html") +``` + +### 获取静态网站托管的设置 + +用户通过如下代码获取指定Bucket的静态网站托管的设置情况: + +```go +result, err := bosClient.SimplePutBucketStaticWebsite(bucketName) +fmt.Println(result.Index) +fmt.Println(result.NotFound) +``` + +### 删除静态网站托管的设置 + +通过如下示例代码删除指定Bucket的静态网站托管功能: + +```go +err := bosClient.DeleteBucketStaticWebsite(bucketName) +``` + +## 跨区域复制 + +BOS提供了跨区域复制功能,针对用户在某个物理区域创建的Bucket,为了数据安全或其他目的,可配置将整个Bucket的数据复制到物理上的另一个区域。 + +### 开启Bucket跨区域复制功能 + +用户可通过如下代码开启Bucket的跨区域复制功能: + +```go +// 1. json字符串 +jsonStr := `{ + "status":"enabled", + "resource":[ + "bucket/abc", + "bucket/cd*", + ], + "destination": { + "bucket":"bucket-name", + "storageClass":"COLD" + }, + "replicateHistory": { + "bucket":"bucket-name", + "storageClass":"COLD" + }, + "replicateDeletes":"enabled", + "id":"sample-bucket-replication-config" +}` +err := bosClient.PutBucketReplicationFromString(bucketName, jsonStr) + +// 2. 使用配置文件名 +err := bosClient.PutBucketReplicationFromFile(bucketName, configFile) + +// 3. 使用参数对象 +argsObj := &api.PutBucketReplicationArgs{ + Id: "sample-bucket-replication-config", + Status: "enabled", + Resource: []string{"bucket/abc"}, + ReplicateDeletes: "enabled", + Destination: &api.BucketReplicationDescriptor{"bucket-abc", "COLD"}, + ReplicateHistory: &api.BucketReplicationDescriptor{"bucket-abc", "COLD"}, +} +err := bosClient.PutBucketReplicationFromStruct(bucketName, argsObj) + +// 4. 使用流 +err := bosClient.PutBucketReplication(bucketName, bodyStream) +``` + +### 获取Bucket跨区域复制的配置 + +用户可使用如下示例代码获取Bucket的跨区域复制的配置,返回的结果与Put接口字段相同。 + +```go +result, err := bosClient.GetBucketReplication(bucketName) +``` + +### 删除Bucket跨区域复制配置 + +用户可使用如下示例代码删除Bucket跨区域复制功能: + +```go +err := bosClient.DeleteBucketReplication(bucketName) +``` + +### 获取跨区域复制到进度 + +由于跨区域复制需要后台进行异步复制操作,用户可通过如下接口查询当前复制到进度: + +```go +result, err := bosClient.GetBucketReplicationProgress(bucketName) +``` + +返回的结果对象包含了如下字段: + +- `Status`(string): 当前的状态 +- `HistoryReplicationPercent`(float64): 当前复制到进度 +- `LatestReplicationTime`(string): 最近一次执行复制的时间 + + +# 错误处理 + +GO语言以error类型标识错误,BOS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | BOS服务返回的错误 + +用户使用SDK调用BOS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// bosClient 为已创建的BOS Client对象 +bucketLocation, err := bosClient.PutBucket("test-bucket") +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("create bucket success, bucket location:", bucketLocation) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BOS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当BOS服务端出现异常时,BOS服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BOS错误信息格式](https://cloud.baidu.com/doc/BOS/API.html#.E9.94.99.E8.AF.AF.E4.BF.A1.E6.81.AF.E6.A0.BC.E5.BC.8F) + +## SDK日志 + +BOS GO SDK自行实现了支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。详见示例代码。 + +### 默认日志 + +BOS GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the BOS go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the BOS go sdk") +``` + + +# 版本变更记录 + +## v0.9.3 [2018-09-21] + + - 支持Bucket跨区域复制、服务端加密、原图保护 + - 支持设置Bucket的CORS规则和静态网站托管 + - 支持Object级别的ACL设置 + - 修复GeneratePresignedUrl问题 + +## v0.9.2 [2018-3-16] + + - 修复go get下载时的错误提示信息 + - 修复重试请求时请求的body流丢失的问题 + - 完善UploadSuperFile返回的错误提示信息 + - 将GeneratePresignedUrl接口统一调整为bucket virtual hosting模式 + +## v0.9.1 [2018-1-4] + +首次发布: + + - 创建、查看、罗列、删除Bucket,获取位置和判断是否存在 + - 支持管理Bucket的生命周期、日志、ACL、存储类型 + - 上传、下载、删除、罗列Object,支持分块上传、分块拷贝 + - 提供AppendObject功能和FetchObject功能 + - 封装并发的下载和分块上传接口 diff --git a/bce-sdk-go/doc/BVW.md b/bce-sdk-go/doc/BVW.md new file mode 100644 index 0000000..ca939c6 --- /dev/null +++ b/bce-sdk-go/doc/BVW.md @@ -0,0 +1,596 @@ +# BVW服务 + +# 概述 + +本文档主要介绍videoworks(以下均简称BVW)GO SDK的使用。在使用本文档前,您需要先了解BVW的一些基本知识,并已开通了BVW服务。若您还不了解BVW,可以参考[产品描述](https://cloud.baidu.com/doc/VideoWorks/s/gjys24iww)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[服务域名](https://cloud.baidu.com/doc/VideoWorks/s/Xjyo34h6o)的部分,理解Endpoint相关的概念。 + +目前BVW服务仅支持“华北-北京”该区域。北京区域:`http://bvw.bj.baidubce.com`,对应信息为: + +| 访问区域 | 对应Endpoint | +| -------- | ------------------- | +| BJ | bvw.bj.baidubce.com | + +## 获取密钥 + +要使用百度云BVW,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BVW做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建BVW Client + +BVW Client是BVW服务的客户端,为开发者与BVW服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建BVW Client + +通过AK/SK方式访问BVW,用户可以参考如下代码新建一个BVW Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/bvw" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BVWClient + MEDIA_CLIENT, err := bvw.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《相关参考 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为BVW的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://bvw.bj.baidubce.com`。 + +# 媒资库 + +## 普通素材 + +### 上传素材 + +用户上传音频/视频/图片到素材库,创作视频时可从素材中心导入 + +```go +req := &api.MatlibUploadRequest{} +req.Key = "dog.mp4" +req.Bucket = "bvw-console" +req.Title = "split_result_267392.mp4" +req.MediaType = "video" +uploadResponse, err := BVW_CLIENT.UploadMaterial(req) +if err != nil { + fmt.Printf("upload error: %+v\n", err) + return +} +fmt.Printf("upload success : %+v\n", uploadResponse) +``` + +> 注意:这里上传的媒资类型需和MediaType严格对应,MediaType可选:video:视频, audio:音频,image:图片 + +### 查询素材 + +使用如下代码可以从媒资库查询一个素材。 + +```go +materialId := "d9b9f08ef1e0a28967fa0f7b5819db30" +materialGetResponse, err := BVW_CLIENT.GetMaterial(materialId) +if err != nil { + fmt.Printf("get material error: %+v\n", err) + return +} +fmt.Printf("get material success : %+v\n", materialGetResponse) +``` + +### 搜索素材 + +使用如下代码可以从媒资库搜索指定条件的素材。 + +```go +req := &api.MaterialSearchRequest{} +req.Size = 5 +req.PageNo = 1 +req.Status = "FINISHED" +req.MediaType = "video" +req.Begin = "2023-01-11T16:00:00Z" +req.End = "2023-04-12T15:59:59Z" +materialResp, err := BVW_CLIENT.SearchMaterial(req) +if err != nil { + fmt.Printf("search material error: %+v\n", err) + return +} +fmt.Printf("search material success : %+v\n", materialResp) +``` + +### 删除素材 + +使用如下代码可以从媒资库删除一个素材。 + +```go +materialId := "d9b9f08ef1e0a28967fa0f7b5819db30" +err := BVW_CLIENT.DeleteMaterial(materialId) +if err != nil { + fmt.Printf("delete material error: %+v\n", err) + return +} +fmt.Println("delete material success") +``` + +## 预置素材 + +预置素材分为音乐/贴图/背景/字幕/转场,除了系统自带的预置素材外,用户可以自定义音乐/贴图预置素材。创作视频时,不需要导入,可直接使用 + +### 上传预置素材 + +使用如下代码可以上传一个音乐/贴图预置素材到媒资库。 + +```go +req := &api.MatlibUploadRequest{} +req.Key = "item2.jpeg" +req.Bucket = "bvw-console" +req.Title = "item2.jpeg" +req.MediaType = "image" +type := "PICTURE" +uploadResponse, err := BVW_CLIENT.UploadMaterialPreset(type, req) +if err != nil { + fmt.Printf("upload preset material error: %+v\n", err) + return +} +fmt.Println("upload preset success : %+v\n", uploadResponse) +``` + +> 注意:预置素材类型type,只支持2种类型自定义:MUSIC:音乐,PICTURE:贴图。 + +### 搜索预置素材 + +使用如下代码可以查询一个指定预置素材。 + +```go +req := &api.MaterialPresetSearchRequest{} +req.PageSize = "10" +req.Status = "FINISHED" +req.PageNo = "1" +req.SourceType = "USER" +req.MediaType = "PICTURE" +response, err := BVW_CLIENT.SearchMaterialPreset(req) +if err != nil { + fmt.Printf("search preset material error: %+v\n", err) + return +} +fmt.Println("search preset success : %+v\n", response) +``` + +### 查询预置素材 + +使用如下代码可以从媒资库搜索指定条件的预置素材。 + +```go +id := "cc0aabdc71421abaa17e80a26caa009f" +response, err := BVW_CLIENT.GetMaterialPreset(id) +if err != nil { + fmt.Printf("get preset material error: %+v\n", err) + return +} +fmt.Println("get preset success : %+v\n", response) +``` + +### 删除预置素材 + +使用如下代码可以删除一个指定预置素材。 + +```go +id := "cc0aabdc71421abaa17e80a26caa009f" +err := BVW_CLIENT.DeleteMaterialPreset(id) +if err != nil { + fmt.Printf("delete preset material error: %+v\n", err) + return +} +fmt.Println("delete preset material success") +``` + +## 媒资库设置 + +媒资库相关操作需要预先设置bucket。 + +### 创建媒资库设置 + +使用如下代码可以创建媒资库设置,设置媒资库交互中使用的默认bucket。 + +```go +request := &api.MatlibConfigBaseRequest{ + Bucket: "go-test"} +response, err := BVW_CLIENT.CreateMatlibConfig(request) +if err != nil { + fmt.Printf("create config error: %+v\n", err) + return +} +fmt.Println("create config success : %+v\n", response) +``` + +### 更新媒资库设置 + +使用如下代码可以更新媒资库设置,设置媒资库交互中使用的默认bucket。 + +```go +err := BVW_CLIENT.UpdateMatlibConfig(&api.MatlibConfigUpdateRequest{Bucket: "go-test"}) +if err != nil { + fmt.Printf("update config error: %+v\n", err) + return +} +fmt.Println("update config success") +``` + +### 查询媒资库设置 + +使用如下代码可以查询媒资库设置,设置媒资库交互中使用的默认bucket。 + +```go +response, err := BVW_CLIENT.GetMatlibConfig() +if err != nil { + fmt.Printf("get config error: %+v\n", err) + return +} +fmt.Println("get config success : %+v\n", response) +``` + +# 快编 + +## 创建草稿 + +在进行视频合成前需要先创建草稿,以获取任务id用于后续视频合成任务 + +```go +response, err := BVW_CLIENT.CreateDraft(&api.CreateDraftRequest{ + Duration: "0", + Titile: "testCreateDraft3", + Ratio: "hori16x9"}) +if err != nil {     + fmt.Printf("create draft error: %+v\n", err)     + return +} +fmt.Println("create draft success taskId :", response.Id) +``` + +## 更新草稿 + +如下代码可更新所创建的草稿 + +```go +var request api.MatlibTaskRequest +jsonStr := "{\"title\":\"updatesucess\",\"draftId\":\"1017890\",\"timeline\":{\"timeline\":{\"video\":[{\"key\"" + +":\"name\",\"isMaster\":true,\"list\":[{\"type\":\"video\",\"start\":0,\"end\":5.859375,\"showStart\":0," + +"\"showEnd\":5.859375,\"xpos\":0,\"ypos\":0,\"width\":1280,\"height\":720,\"duration\":5.859375,\"extInfo" + +"\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0,\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1" + +",\"transitionStart\":\"\",\"transitionEnd\":\"black\",\"transitionDuration\":1,\"volume\":1,\"rotate\":0," + +"\"mirror\":\"\",\"blankArea\":\"\"},\"mediaInfo\":{\"fileType\":\"video\",\"sourceType\":\"USER\",\"" + +"sourceUrl\":\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-token=" + +"ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7x" + +"G%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hb" + +"SJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRp" + +"OC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ" + +"%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2" + +"F%2Fb227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\":\"" + +"https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjY" + +"yYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ" + +"5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3" + +"OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzT" + +"Cgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=" + +"bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1154f" + +"f0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\",\"audioKey\":" + +"\"audio/dog.mp3\",\"coverImage\":\"https://bj.bcebos.com/v1/bvw-console/thumbnail/dog00000500.jpg" + +"?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZp" + +"Ppsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQz" + +"FlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6P" + +"z36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQj" + +"Wm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cb" + +"c0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f9028fb85e00ff5dc7df6" + +"daf\",\"duration\":18.73,\"width\":1920,\"height\":1080,\"status\":\"FINISHED\",\"name\":\"dog.mp4\"," + +"\"thumbnailPrefix\":\"\",\"thumbnailKeys\":[\"thumbnail/dog00000500.jpg\"],\"mediaId\":\"" + +"1f10ce0db10b8eb5b2f2755daf544900\",\"offstandard\":false},\"uid\":\"a081f1c6-9dc9-4e7b-a00e-6eb5217e771d" + +"\"},{\"type\":\"video\",\"start\":5.859375,\"end\":18.73,\"showStart\":5.859375,\"showEnd\":18.73,\"xpos" + +"\":0,\"ypos\":0,\"width\":1280,\"height\":720,\"duration\":12.870625,\"extInfo\":{\"style\":\"\",\"lightness" + +"\":0,\"gifMode\":0,\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"black\",\"" + +"transitionEnd\":\"\",\"transitionDuration\":1,\"volume\":1,\"rotate\":0,\"mirror\":\"\",\"blankArea\":\"\"}," + +"\"mediaInfo\":{\"fileType\":\"video\",\"sourceType\":\"USER\",\"sourceUrl\":" + +"\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyN" + +"jk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9" + +"RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7D" + +"GO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df" + +"5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%" + +"3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2Fb" + +"227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\":" + +"\"https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjY" + +"yYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5" + +"Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3O" + +"HDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCg" + +"h8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=" + +"bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1" + +"154ff0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\",\"" + +"audioKey\":\"audio/dog.mp3\",\"coverImage\":\"https://bj.bcebos.com/v1/bvw-console/thumbnail/dog00000500" + +".jpg?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZp" + +"Ppsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQz" + +"FlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz" + +"36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm" + +"231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0" + +"fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f9028fb85e00ff5dc7df6da" + +"f\",\"duration\":18.73,\"width\":1920,\"height\":1080,\"status\":\"FINISHED\",\"name\":\"dog.mp4\",\"thumb" + +"nailPrefix\":\"\",\"thumbnailKeys\":[\"thumbnail/dog00000500.jpg\"],\"mediaId\":\"1f10ce0db10b8eb5b2f2755d" + +"af544900\",\"offstandard\":false},\"uid\":\"70af482e-0bf5-4c38-8f05-03c9e3b8ae03\"}],\"unlinkMaster\":true" + +"}],\"audio\":[{\"key\":\"\",\"isMaster\":false,\"list\":[{\"start\":0,\"end\":155.99,\"showStart\":" + +"0.234375,\"showEnd\":156.224375,\"duration\":155.99,\"xpos\":0,\"ypos\":0,\"hidden\":false,\"mediaInfo\"" + +":{\"fileType\":\"audio\",\"sourceUrl\":\"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset" + +"/music/audio/%E5%8F%A4%E9%A3%8E%E9%A3%98%E6%89%AC.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772" + +"a1409590%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F2ddcf78c92de29ae3d7c3166a4e17e7c5d07fa38dcefd24c29c4f4d5b" + +"5ba46fe\",\"audioUrl\":\"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/music/audio/" + +"%E5%8F%A4%E9%A3%8E%E9%A3%98%E6%89%AC.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2" + +"023-04-14T07%3A16%3A35Z%2F86400%2F%2F2ddcf78c92de29ae3d7c3166a4e17e7c5d07fa38dcefd24c29c4f4d5b5ba46fe\"," + +"\"bucket\":\"videoworks-system-preprocess\",\"key\":\"systemPreset/music/古风飘扬.aac\",\"audioKey\":\"" + +"systemPreset/music/audio/古风飘扬.mp3\",\"coverImage\":\"\",\"duration\":155.99,\"name\":\"\",\"thumbnailList" + +"\":[],\"mediaId\":\"\",\"offstandard\":false},\"type\":\"audio\",\"uid\":\"bd52be7f-1f19-4368-8c41-44e991af" + +"8164\",\"name\":\"古风飘扬\",\"extInfo\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0,\"contrast\":0,\"" + +"saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"\",\"transitionDuration" + +"\":1,\"volume\":1,\"rotate\":0},\"boxDataLeft\":4,\"dragBoxWidth\":1996.6720000000003,\"lineType\":\"audio" + +"\"}]}],\"subtitle\":[{\"key\":\"\",\"list\":[{\"duration\":3,\"hidden\":false,\"name\":\"time-place\",\"" + +"tagExtInfo\":{\"marginBottom\":0,\"textFadeIn\":1,\"textFadeOut\":1,\"textOutMaskDur\":1},\"showStart\":" + +"5.859375,\"showEnd\":8.859,\"templateId\":\"6764ce3331ea7e406e4ab4475d1dff18\",\"type\":\"subtitle\",\"uid" + +"\":\"5aaa35f4-8fae-4b8c-b7ed-761a54550244\",\"xpos\":\"0\",\"ypos\":\"309\",\"config\":[{\"alpha\":0,\"" + +"fontColor\":\"#ffffff\",\"fontSize\":50,\"fontStyle\":\"normal\",\"fontType\":\"方正时代宋 简 Extrabold\"" + +",\"lineHeight\":1.2,\"name\":\"时间\",\"text\":\"haha\",\"backgroundColor\":\"#2468F2\",\"backgroundAlpha" + +"\":0,\"fontx\":0,\"fonty\":0,\"invisible\":false},{\"alpha\":0,\"fontColor\":\"#000000\",\"fontSize\":50," + +"\"fontStyle\":\"normal\",\"fontType\":\"方正时代宋 简 Extrabold\",\"lineHeight\":1.2,\"name\":\"地点\",\"" + +"text\":\"cd\",\"backgroundColor\":\"#ffffff\",\"backgroundAlpha\":0,\"fontx\":0,\"fonty\":0,\"invisible\"" + +":false}],\"boxDataLeft\":76,\"dragBoxWidth\":38.400000000000006,\"lineType\":\"subtitle\"}],\"master\":" + +"false}],\"sticker\":[{\"key\":\"\",\"isMaster\":false,\"list\":[{\"showStart\":0,\"showEnd\":3,\"duration\"" + +":3,\"xpos\":0,\"ypos\":0,\"width\":215,\"height\":120.9375,\"hidden\":false,\"mediaInfo\":{\"sourceUrl\":\"" + +"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/picture/%E9%9D%A2%E5%8C%85%E3%80%81%E8" + +"%82%A0.png?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2023-04-14T07%3A16%3A35Z%2F8640" + +"0%2F%2F84867e4874cc94eb374898017cb4367ed8c24a5750a9d8ebd14d8ca989cf2e53\",\"audioUrl\":\"" + +"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/picture/audio/%E9%9D%A2%E5%8C%85%E3%80%" + +"81%E8%82%A0.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2023-04-14T07%3A16%3A35Z%2F" + +"86400%2F%2F1807d3005f5f8fc270fa17238ec550c20162252c19952e6a45ae497ef7148086\",\"bucket\":\"" + +"videoworks-system-preprocess\",\"key\":\"systemPreset/picture/面包、肠.png\",\"audioKey\":\"systemPreset" + +"/picture/audio/面包、肠.mp3\",\"coverImage\":\"\",\"width\":215,\"height\":120,\"name\":\"\",\"thumbnailList" + +"\":[],\"mediaId\":\"\",\"offstandard\":false},\"type\":\"image\",\"uid\":\"e419b583-aedb-4265-" + +"9a67-7e21fc621f85\",\"name\":\"面包、肠\",\"extInfo\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0," + +"\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"\"," + +"\"transitionDuration\":1,\"volume\":1,\"rotate\":0},\"lineType\":\"sticker\",\"boxDataLeft\":1,\"" + +"dragBoxWidth\":38.400000000000006}]}]}},\"ratio\":\"hori16x9\",\"resourceList\":[{\"id\":\"1f10ce0db10" + +"b8eb5b2f2755daf544900\",\"userId\":\"e7e47aa53fbb47dfb1e4c86424bb7ad3\",\"mediaType\":\"video\",\"" + +"sourceType\":\"USER\",\"status\":\"FINISHED\",\"title\":\"dog.mp4\",\"sourceUrl\":\"https://bj.bcebos.com" + +"/v1/bvw-console/dog.mp4?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM" + +"1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Z" + +"zgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkh" + +"MLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7" + +"D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1" + +"a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F4a3c399db912e35f6b6c008faffebe5752c47e36fcd21d4bf03" + +"bc908c3a29e5e\",\"sourceUrl360p\":\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-toke" + +"n=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZT" + +"NAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwb" + +"rCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F" + +"9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujH" + +"KfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07" + +"%3A16%3A35Z%2F86400%2F%2Fb227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\"" + +":\"https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1Z" + +"jYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8" + +"sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46Nx" + +"S3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOF" + +"zTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorizatio" + +"n=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1" + +"154ff0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"thumbnailList\":[\"https://bj.bcebos.com/v1/bvw-cons" + +"ole/thumbnail/dog00000500.jpg?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAA" + +"BgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9" + +"xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSIS" + +"jn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCK" + +"r%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9" + +"411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f902" + +"8fb85e00ff5dc7df6daf\"],\"subtitleUrls\":[],\"createTime\":\"2023-04-11 16:55:32\",\"updateTime\":\"2023-0" + +"4-11 16:55:43\",\"duration\":18.73,\"height\":1080,\"width\":1920,\"fileSizeInByte\":8948434,\"thumbnailK" + +"eys\":[\"thumbnail/dog00000500.jpg\"],\"subtitles\":[\"\"],\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\"" + +",\"key360p\":\"360p/dog.mp4\",\"key720p\":\"720p/dog.mp4\",\"audioKey\":\"audio/dog.mp3\",\"used\":true}]" + +",\"coverBucket\":\"bvw-console\",\"coverKey\":\"thumbnail/dog00000500.jpg\"}" +jsonErr := json.Unmarshal([]byte(jsonStr), &request) +ExpectEqual(t.Errorf, jsonErr, nil) +err := BVW_CLIENT.UpdateDraft(1017890, &request) +if err != nil {     + fmt.Printf("update draft error: %+v\n", err)     + return +} +fmt.Println("update draft success") +``` + +## 获取草稿列表 + +使用如下代码可以获取所创建了所有快编草稿 + +```go +response, err := BVW_CLIENT.GetDraftList(&api.DraftListRequest{ + PageNo: 1, + PageSize: 20, + BeginTime: "2023-01-11T16:00:00Z", + EndTime: "2023-04-12T15:59:59Z"}) +if err != nil { + fmt.Printf("get draft list error: %+v\n", err) + return +} +fmt.Println("get draft list success : %+v\n", response) +``` + +## 获取单条草稿 + +如下代码可以通过快编任务id获取草稿信息 + +```go +taskId := 123456 +response, err := BVW_CLIENT.GetSingleDraft(taskId) +if err != nil { + fmt.Printf("get draft error: %+v\n", err) + return +} +fmt.Println("get draft success : %+v\n", response) +``` + +## 视频合成 + +视频合成功能支持将一段视频编辑的Timeline(不同媒体分类组成的时间轴数据)编码合成输出。 + +使用如下代码可以发起合成。 + +```go +var request api.VideoEditCreateRequest +jsonStr := "{\"title\":\"新建作品-202304141603\",\"taskId\":\"1017895\",\"bucket\":\"vwdemo\",\"cmd\":{\"timeline" + +"\":{\"video\":[{\"list\":[{\"type\":\"video\",\"start\":0,\"end\":7.65625,\"showStart\":0,\"showEnd\":" + +"7.65625,\"xpos\":0,\"ypos\":0,\"width\":1,\"height\":1,\"duration\":7.65625,\"extInfo\":{\"style\":\"\"" + +",\"lightness\":0,\"contrast\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"black\"" + +",\"transitionDuration\":1,\"mirror\":\"\",\"rotate\":0,\"blankArea\":\"\",\"volume\":1},\"mediaInfo\":{\"" + +"mediaId\":\"1f10ce0db10b8eb5b2f2755daf544900\",\"key\":\"dog.mp4\",\"bucket\":\"bvw-console\",\"fileType\"" + +":\"video\",\"width\":1920,\"height\":1080}},{\"type\":\"video\",\"start\":7.65625,\"end\":18.73,\"showStart" + +"\":7.65625,\"showEnd\":18.73,\"xpos\":0,\"ypos\":0,\"width\":1,\"height\":1,\"duration\":11.07375,\"" + +"extInfo\":{\"style\":\"\",\"lightness\":0,\"contrast\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"black\"" + +",\"transitionEnd\":\"\",\"transitionDuration\":1,\"mirror\":\"\",\"rotate\":0,\"blankArea\":\"\",\"volume\":" + +"1},\"mediaInfo\":{\"mediaId\":\"1f10ce0db10b8eb5b2f2755daf544900\",\"key\":\"dog.mp4\",\"bucket\":\"" + +"bvw-console\",\"fileType\":\"video\",\"width\":1920,\"height\":1080}}]}],\"audio\":[{\"list\":[{\"name\":" + +"\"古风飘扬\",\"start\":0,\"end\":155.99,\"duration\":155.99,\"showStart\":0.078125,\"showEnd\":156.068125,\"" + +"uid\":\"cc8c1ecc-fcd3-493d-be5b-8cce8c15ed15\",\"extInfo\":{\"volume\":\"1.0\",\"transitions\":[]},\"" + +"mediaInfo\":{\"fileType\":\"audio\",\"key\":\"systemPreset/music/古风飘扬.aac\",\"bucket\":\"videoworks-" + +"system-preprocess\",\"name\":\"古风飘扬\"}}]}],\"subtitle\":[{\"list\":[{\"templateId\":\"6764ce3331ea7e406e" + +"4ab4475d1dff18\",\"showStart\":0,\"showEnd\":3,\"duration\":3,\"uid\":\"05e59686-b23e-4b33-96d7-040eab63" + +"85b6\",\"tag\":\"time-place\",\"xpos\":0,\"ypos\":0.431,\"config\":[{\"text\":\"时间\",\"fontSize\":50,\"" + +"fontType\":\"方正时代宋 简 Extrabold\",\"fontColor\":\"#ffffff\",\"alpha\":0,\"fontStyle\":\"normal\",\"" + +"backgroundColor\":\"#2468F2\",\"backgroundAlpha\":0,\"fontx\":0.039,\"fonty\":0.028,\"rectx\":0,\"recty\"" + +":0.431,\"rectWidth\":0.156,\"rectHeight\":0.139},{\"text\":\"地点\",\"fontSize\":50,\"fontType\":\"" + +"方正时代宋 简 Extrabold\",\"fontColor\":\"#000000\",\"alpha\":0,\"fontStyle\":\"normal\",\"backgroundColor" + +"\":\"#ffffff\",\"backgroundAlpha\":0,\"fontx\":0.039,\"fonty\":0.028,\"rectx\":0.156,\"recty\":0.431,\"" + +"rectWidth\":0.156,\"rectHeight\":0.139}],\"tagExtInfo\":{\"glExtInfo\":{}}}]}],\"sticker\":[{\"list\":" + +"[{\"type\":\"image\",\"showStart\":0,\"showEnd\":3,\"duration\":3,\"xpos\":0,\"ypos\":0,\"width\":0.168" + +",\"height\":0.168,\"extInfo\":{},\"mediaInfo\":{\"key\":\"systemPreset/picture/面包、肠.png\",\"bucket\":" + +"\"videoworks-system-preprocess\",\"width\":215,\"height\":120.9375,\"fileType\":\"image\"}}]}]}},\"extInfo" + +"\":{\"aspect\":\"hori16x9\",\"resolutionRatio\":\"v720p\"}}" +jsonErr := json.Unmarshal([]byte(jsonStr), &request) +fmt.Println("jsonError: ", jsonErr) +t.Logf("%+v", &request) +response, err := BVW_CLIENT.CreateVideoEdit(&request) +if err != nil { + fmt.Printf("create edit job error: %+v\n", err) + return +} +fmt.Println("create edit job success : %+v\n", response) +``` + +> 上述代码模拟了前端请求后端数据结构进行视频合成任务,若不想采用上述方式也可自行构建VideoEditCreateRequest结构体进行视频合成任务。 + +## 查询视频合成结果 + +使用如下代码可以查询视频合成结果。 + +```json +editId := 123456 +response, err := BVW_CLIENT.PollingVideoEdit(editId) +if err != nil { + fmt.Printf("get edit job error: %+v\n", err) + return +} +fmt.Println("get edit job success : %+v\n", response) +``` + +# 错误处理 + +GO语言以error类型标识错误,BVW支持两种错误见下表: + +| 错误类型 | 说明 | +| --------------- | ------------------ | +| BceClientError | 用户操作产生的错误 | +| BceServiceError | BVW服务返回的错误 | + +用户使用SDK调用BVW相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// MEDIA_CLIENT 为已创建的BVW Client对象 +result, err := MEDIA_CLIENT.ListPipelines() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向BVW发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当BVW服务端出现异常时,BVW服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +BVW GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +BVW GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + +``` +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` +``` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +```go +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the BVW go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the BVW go sdk") +``` + +# 版本变更记录 + +首次发布: + +- BVW支持go-sdk啦,现在您可以通过golang调用BVW-SDK服务。当前SDK能力支持快编(视频编辑合成)服务、媒资库服务。 \ No newline at end of file diff --git a/bce-sdk-go/doc/CCE.md b/bce-sdk-go/doc/CCE.md new file mode 100644 index 0000000..55f9ace --- /dev/null +++ b/bce-sdk-go/doc/CCE.md @@ -0,0 +1,612 @@ +# CCE服务 + +# 概述 + +本文档主要介绍CCE GO SDK的使用。在使用本文档前,您需要先了解CCE的一些基本知识,并已开通了CCE服务。若您还不了解CCE,可以参考[产品描述](https://cloud.baidu.com/doc/CCE/s/Bjwvy0x5g)和[操作指南](https://cloud.baidu.com/doc/CCE/s/zjxpoqohb)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[CCE服务域名](https://cloud.baidu.com/doc/CCE/s/Fjwvy1fl4)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/CCE/s/Fjwvy1fl4)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | cce.bj.baidubce.com | HTTP and HTTPS +GZ | cce.gz.baidubce.com | HTTP and HTTPS +SU | cce.su.baidubce.com | HTTP and HTTPS +HKG| cce.hkg.baidubce.com| HTTP and HTTPS +FWH| cce.fwh.baidubce.com| HTTP and HTTPS +BD | cce.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云CCE,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CCE做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CCE Client + +CCE Client是CCE服务的客户端,为开发者与CCE服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CCE Client + +通过AK/SK方式访问CCE,用户可以参考如下代码新建一个CCE Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/cce" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个CCEClient + cceClient, err := cce.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为CCE的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`cce.bj.baidubce.com`。 + +### 使用STS创建CCE Client + +**申请STS token** + +CCE可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CCE,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CCE Client** + +申请好STS后,可将STS Token配置到CCE Client中,从而实现通过STS Token创建CCE Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CCE Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cce" //导入CCE服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CCE服务的Client对象,Endpoint使用默认值 + cceClient, err := cce.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "cce.bj.baidubce.com") + if err != nil { + fmt.Println("create cce client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + cceClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CCE Client时,无论对应CCE服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问CCE + +CCE支持HTTPS传输协议,您可以通过在创建CCE Client对象时指定的Endpoint中指明HTTPS的方式,在CCE GO SDK中使用HTTPS访问CCE服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +ENDPOINT := "https://cce.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +cceClient, _ := cce.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CCE Client + +如果用户需要配置CCE Client的一些细节的参数,可以在创建CCE Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CCE服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +//创建CCE Client对象 +AK, SK := , +ENDPOINT := "cce.bj.baidubce.com" +client, _ := cce.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +AK, SK := , +ENDPOINT := "cce.bj.baidubce.com" +client, _ := cce.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +AK, SK := , +ENDPOINT := "cce.bj.baidubce.com" +client, _ := cce.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CCE时,创建的CCE Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CCE Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# CCE管理 + +百度智能云容器引擎(Cloud Container Engine,即CCE)是高度可扩展的高性能容器管理服务,您可以在托管的云服务器实例集群上轻松运行应用程序。 + +> 注意: +> - 百度智能云容器引擎免费为用户提供服务,只会对其使用的资源例如BCC、BLB和EIP等资源收费 + +## 获取容器网络 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" +args := &cce.GetContainerNetArgs{ + // 容器所在VPC ID + VpcShortId: "vpc-xxxxxx", + VpcCidr: "192.168.0.0/24", +} +result, err := client.GetContainerNet(args) +if err != nil { + fmt.Printf("get container net error: %+v\n", err) + return +} +fmt.Printf("get cce container net success: %s\n", result.ContainerNet) +``` + +## 获取支持版本列表 +```go +result, err := client.ListVersions() +if err != nil { + fmt.Printf("list version error: %+v\n", err) + return +} +fmt.Printf("list version success with result: %+v\n", result) +``` + +## 创建CCE Cluster +使用以下代码可以创建一个CCE Cluster。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.CreateClusterArgs{ + // 指定CCE Cluster名称 + ClusterName: "sdk-test", + Version: listVersionResult.Data[0], + MainAvailableZone: "zoneA", + ContainerNet: getContainernetResult.ContainerNet, + // CCE 集群高级配置 + //AdvancedOptions: cce.AdvancedOptions{}, + // CCE CDS盘预挂载信息 + //CdsPreMountInfo: cce.CdsPreMountInfo{}, + Comment: "sdk create", + DeployMode: cce.DeployModeBcc, + OrderContent: &cce.BaseCreateOrderRequestVo{Items: []cce.Item{ + { + Config: cce.BccConfig{ + // BCC实例名称,若不指定,将随机生成 + Name: "sdk-create", + ProductType: cce.ProductTypePostpay, + InstanceType: cce.InstanceTypeG3, + Cpu: 1, + Memory: 2, + ImageType: cce.ImageTypeCommon, + SubnetUuid: SubnetId, + SecurityGroupId: Security, + AdminPass: AdminPass, + PurchaseNum: 2, + ImageId: "m-Nlv9C0tF", + ServiceType: cce.ServiceTypeBCC, + }, + }, + }}, +} +result, err := client.CreateCluster(args) +if err != nil { + fmt.Printf("create cce cluster error: %+v\n", err) + return +} +fmt.Printf("create cce cluster success with result: %+v\n", result) +``` + +## 获取CCE Cluster列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ListClusterArgs{} + +// 如果想获取某些状态下的集群,如RUNNING状态下的,可以配置参数 +args.Status = cce.ClusterStatusRunning + +result, err := client.ListClusters(args) +if err != nil { + fmt.Printf("list cce cluster error: %+v\n", err) + return +} +fmt.Printf("list cce cluster success with result: %+v\n", result) +``` + +## 获取CCE Cluster详情 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +result, err := client.GetCluster("cluster_uuid") +if err != nil { + fmt.Printf("get cce cluster error: %+v\n", err) + return +} +fmt.Printf("get cce cluster success with result: %+v\n", result) +``` + +## 获取CCE Cluster Node信息 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ListNodeArgs{ + ClusterUuid: "cluster_uuid", +} + +result, err := client.ListNodes(args) +if err != nil { + fmt.Printf("list cce cluster nodes error: %+v\n", err) + return +} +fmt.Printf("list cce cluster nodes success with result: %+v\n", result) +``` + +## CCE Cluster扩容 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ScalingUpArgs{ + ClusterUuid: "cluster_uuid", + // CCE CDS盘预挂载信息 + //CdsPreMountInfo: cce.CdsPreMountInfo{}, + OrderContent: &cce.BaseCreateOrderRequestVo{Items: []cce.Item{ + { + Config: cce.BccConfig{ + // BCC实例名称,若不指定,将随机生成 + Name: "sdk-create", + ProductType: cce.ProductTypePostpay, + InstanceType: cce.InstanceTypeG3, + Cpu: 1, + Memory: 2, + ImageType: cce.ImageTypeCommon, + SubnetUuid: SubnetId, + SecurityGroupId: Security, + AdminPass: AdminPass, + PurchaseNum: 2, + ImageId: "m-Nlv9C0tF", + ServiceType: cce.ServiceTypeBCC, + }, + }, + }}, +} + +result, err := client.ScalingUp(args) +if err != nil { + fmt.Printf("scaling up cce cluster error: %+v\n", err) + return +} +fmt.Printf("scaling up cce cluster success with result: %+v\n", result) +``` + +## CCE Cluster缩容 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ScalingDownArgs{ + ClusterUuid: "cluster_uuid", + // 可选择是否连带删除EIP和CDS + DeleteEipCds: true, + // 可选择是否连带删除快照 + DeleteSnap: true, + NodeInfo: []cce.NodeInfo{ + { + InstanceId: "instance_id", + }, + }, +} + +err = client.ScalingDown(args) +if err != nil { + fmt.Printf("scaling down cce cluster error: %+v\n", err) + return +} +``` + +## 移除CCE Cluster节点 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ShiftOutNodeArgs{ + ClusterUuid: "cluster_uuid", + NodeInfoList: []cce.CceNodeInfo{ + {InstanceId: "instance_id"}, + }, +} + +err = client.ShiftOutNode(args) +if err != nil { + fmt.Printf("shift node out from cce cluster error: %+v\n", err) + return +} +``` + +## 移入CCE Cluster节点 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ShiftInNodeArgs{ + ClusterUuid: "cluster_uuid", + // 是否要重装系统 + NeedRebuild: false, + // 若重装系统,则选择系统镜像ID + //ImageId: "", + // 若重装系统,则配置新的密码 + //AdminPass: AdminPass, + InstanceType: cce.ShiftInstanceTypeBcc, + NodeInfoList: []cce.CceNodeInfo{ + { + InstanceId: "instance_id", + // 若选择不重装系统,则需要输入该节点密码 + AdminPass: AdminPass, + }, + }, +} + +err = client.ShiftInNode(args) +if err != nil { + fmt.Printf("shift node into cce cluster error: %+v\n", err) + return +} +``` + +## 获取CCE Cluster可移入节点列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.ListExistedNodeArgs{ + ClusterUuid: "cluster_uuid", +} + +result, err := client.ListExistedBccNode(args) +if err != nil { + fmt.Printf("list cce cluster exist node error: %+v\n", err) + return +} +fmt.Printf("list cce cluster exist node success with result: %+v\n", result) +``` + +## 获取CCE Cluster kubeconfig配置 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.GetKubeConfigArgs{ + ClusterUuid: "cluster_uuid", +} + +// 若要获取公网访问配置,可以选择默认default +args.Type = cce.KubeConfigTypeDefault + +// 若要获取VPC内部访问配置,可以选择 +args.Type = cce.KubeConfigTypeIntraVpc + +// 若要获取B区内部访问配置,可以选择 +args.Type = cce.KubeConfigTypeInternal + +configResult, err := client.GetKubeConfig(configArgs) +if err != nil { + fmt.Printf("create cce cluster error: %+v\n", err) + return +} +fmt.Printf("create cce cluster success with result: %+v\n", result) +``` + +## 删除CCE 集群 +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +args := &cce.DeleteClusterArgs{ + ClusterUuid: "cluster_uuid", + // 是否需要删除EIP和CDS + DeleteEipCds: true, + // 是否需要删除快照 + DeleteSnap: true, +} +err = client.DeleteCluster(args) +if err != nil { + fmt.Printf("delete cce cluster error: %+v\n", err) + return +} +``` + +# 错误处理 + +GO语言以error类型标识错误,CCE支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CCE服务返回的错误 + +用户使用SDK调用CCE相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// client 为已创建的cce Client对象 +args := &cce.ListClusterArgs{} +result, err := client.ListClusters(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CCE发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当CCE服务端出现异常时,CCE服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[CCE错误码](https://cloud.baidu.com/doc/CCE/s/4jwvy1evj) + +## SDK日志 + +CCE GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +CCE GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the CCE go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the CCE go sdk") +``` + + +# 版本变更记录 + +## v0.9.5 [2020-07-02] + +首次发布: + + - 获取CCE支持版本列表,获取CCE容器网络。 + - 支持创建CCE,获取CCE详情,获取CCE列表,CCE扩容,CCE缩容,获取CCE节点信息,删除CCE。 + - 节点移入CCE集群,节点移处CCE集群,获取可移入节点列表。 \ No newline at end of file diff --git a/bce-sdk-go/doc/CCEv2.md b/bce-sdk-go/doc/CCEv2.md new file mode 100644 index 0000000..d2151ce --- /dev/null +++ b/bce-sdk-go/doc/CCEv2.md @@ -0,0 +1,949 @@ +# CCE服务 v2版本 + +# 概述 + +本文档主要介绍CCE GO SDK的使用。在使用本文档前,您需要先了解CCE的一些基本知识,并已开通了CCE服务。若您还不了解CCE,可以参考[产品描述](https://cloud.baidu.com/doc/CCE/s/Bjwvy0x5g)和[操作指南](https://cloud.baidu.com/doc/CCE/s/zjxpoqohb)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[CCE服务域名](https://cloud.baidu.com/doc/CCE/s/Fjwvy1fl4)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/CCE/s/Fjwvy1fl4)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | cce.bj.baidubce.com | HTTP and HTTPS +GZ | cce.gz.baidubce.com | HTTP and HTTPS +SU | cce.su.baidubce.com | HTTP and HTTPS +HKG| cce.hkg.baidubce.com| HTTP and HTTPS +FWH| cce.fwh.baidubce.com| HTTP and HTTPS +BD | cce.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云CCE,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CCE做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CCE Client + +CCE Client是CCE服务的客户端,为开发者与CCE服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CCE Client + +通过AK/SK方式访问CCE,用户可以参考如下代码新建一个CCE Client: +```go +import ( + "github.com/baidubce/bce-sdk-go/services/ccev2" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + //用户指定的endpoint + ENDPOINT := "endpoint" + + // 初始化一个CCEClient + ccev2Client, err := ccev2.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为CCE的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`cce.bj.baidubce.com`。 + +### 使用STS创建CCE Client + +**申请STS token** + +CCE可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CCE,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CCE Client** + +申请好STS后,可将STS Token配置到CCE Client中,从而实现通过STS Token创建CCE Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CCE Client对象: +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cce" //导入CCE服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CCE服务的Client对象,Endpoint使用默认值 + ccev2Client, err := ccev2.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "cce.bj.baidubce.com") + if err != nil { + fmt.Println("create cce client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + ccev2Client.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CCE Client时,无论对应CCE服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问CCE + +CCE支持HTTPS传输协议,您可以通过在创建CCE Client对象时指定的Endpoint中指明HTTPS的方式,在CCE GO SDK中使用HTTPS访问CCE服务: +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" +AK, SK := , +ENDPOINT := "https://cce.bj.baidubce.com" //指明使用HTTPS协议 + +ccev2Client, _ := ccev2.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CCE Client + +如果用户需要配置CCE Client的一些细节的参数,可以在创建CCE Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CCE服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +//创建CCE Client对象 +AK, SK := , +ENDPOINT := "cce.bj.baidubce.com" + +ccev2Client, _ := ccev2.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +ccev2Client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +AK, SK := , +ENDPOINT := "cce.bj.baidubce.com" + +ccev2Client, _ := ccev2.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +ccev2Client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +ccev2Client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cce" + +AK, SK := , +ENDPOINT := "ccev2.bj.baidubce.com" + +ccev2Client, _ := ccev2.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +ccev2Client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +ccev2Client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CCE时,创建的CCE Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CCE Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# CCE管理 + +百度智能云容器引擎(Cloud Container Engine,即CCE)是高度可扩展的高性能容器管理服务,您可以在托管的云服务器实例集群上轻松运行应用程序。 + +> 注意: +> - 百度智能云容器引擎免费为用户提供服务,只会对其使用的资源例如BCC、BLB和EIP等资源收费 + +## 创建集群 +使用以下代码可以创建一个CCE Cluster。 +```go +args := &ccev2.CreateClusterArgs{ + CreateClusterRequest: &ccev2.CreateClusterRequest{ + ClusterSpec: &types.ClusterSpec{ + ClusterName: "your-cluster-name", + K8SVersion: types.K8S_1_16_8, + RuntimeType: types.RuntimeTypeDocker, + VPCID: "vpc-id", + MasterConfig: types.MasterConfig { + MasterType: types.MasterTypeManaged, + ClusterHA: 1, + ExposedPublic: false, + ClusterBLBVPCSubnetID: "cluster-blb-vpc-subnet-id", + ManagedClusterMasterOption: types.ManagedClusterMasterOption{ + MasterVPCSubnetZone: types.AvailableZoneA, + MasterFlavor: types.MasterFlavorSmall, + }, + }, + ContainerNetworkConfig: types.ContainerNetworkConfig{ + Mode: types.ContainerNetworkModeKubenet, + LBServiceVPCSubnetID: "lb-service-vpc-subnet-id", + ClusterPodCIDR: "172.28.0.0/16", + ClusterIPServiceCIDR: "172.31.0.0/16", + }, + }, + NodeSpecs: []*ccev2.InstanceSet{ + &ccev2.InstanceSet{ + Count: 1, + InstanceSpec: types.InstanceSpec{ + InstanceName: "", + ClusterRole: types.ClusterRoleNode, + Existed: false, + MachineType: types.MachineTypeBCC, + InstanceType: bccapi.InstanceTypeN3, + VPCConfig: types.VPCConfig{ + VPCID: "vpc-id", + VPCSubnetID: "vpc-subnet-id", + SecurityGroupID: "security-group-id", + AvailableZone: types.AvailableZoneA, + }, + InstanceResource:types.InstanceResource{ + CPU: 4, + MEM: 8, + RootDiskSize: 40, + LocalDiskSize: 0, + CDSList: []types.CDSConfig{}, + }, + ImageID: "image-id", + InstanceOS: types.InstanceOS{ + ImageType: bccapi.ImageTypeSystem, + }, + NeedEIP: false, + AdminPassword: "admin-password", + SSHKeyID: "ssh-key-id", + InstanceChargingType: bccapi.PaymentTimingPostPaid, + RuntimeType: types.RuntimeTypeDocker, + }, + }, + }, + }, +} + +resp, err := ccev2Client.CreateCluster(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 查询集群详情 +使用以下代码可以查询一个集群的详细信息。 +```go +clusterID := "cluster-id" +resp, err := ccev2Client.GetCluster(clusterID) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 获取集群列表 +使用以下代码可以按关键字获取排序后的集群列表 +```go +args := &ccev2.ListClustersArgs{ + KeywordType: "clusterName", + Keyword: "", + OrderBy: "clusterID", + Order: ccev2.OrderASC, + PageSize: 10, + PageNum: 1, +} + +resp, err := ccev2Client.ListClusters(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 删除集群 +使用以下代码可以删除一个集群 +```go +args := &ccev2.DeleteClusterArgs{ + ClusterID: "cluster-id", + DeleteResource: true, + DeleteCDSSnapshot: true, +} + +resp, err := ccev2Client.DeleteCluster(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 创建节点(集群扩容) +使用以下代码可以新创建节点并对集群进行扩容 +```go +args := &ccev2.CreateInstancesArgs{ + ClusterID: "cluster-id", + Instances: []*ccev2.InstanceSet{ + { + Count: 1, + InstanceSpec: types.InstanceSpec{ + ClusterRole: types.ClusterRoleNode, + Existed: false, + MachineType: types.MachineTypeBCC, + InstanceType: bccapi.InstanceTypeN3, + VPCConfig: types.VPCConfig{ + VPCID: "vpc-id", + VPCSubnetID: "vpc-subnet-id", + SecurityGroupID: "security-group-id", + AvailableZone: types.AvailableZoneA, + }, + InstanceResource:types.InstanceResource{ + CPU: 1, + MEM: 4, + RootDiskSize: 40, + LocalDiskSize: 0, + }, + ImageID: "image-id", + InstanceOS: types.InstanceOS{ + ImageType: bccapi.ImageTypeSystem, + }, + NeedEIP: false, + AdminPassword: "admin-password", + SSHKeyID: "ssh-key-id", + InstanceChargingType: bccapi.PaymentTimingPostPaid, + RuntimeType: types.RuntimeTypeDocker, + }, + }, + }, +} + +resp, err := ccev2Client.CreateInstances(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + + +## 获取节点详情 +使用以下代码可以获取一个节点的详细信息 +```go +args := &ccev2.GetInstanceArgs{ + ClusterID: "cluster-id", + InstanceID: "instance-id", +} + +resp, err := ccev2Client.GetInstance(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 获取集群的节点列表 +使用以下代码可以获取一个集群的节点列表 +```go +args := &ccev2.ListInstancesByPageArgs { + ClusterID: "cluster-id", + Params: &ccev2.ListInstancesByPageParams{ + KeywordType: ccev2.InstanceKeywordTypeInstanceName, + Keyword: "", + OrderBy: "createdAt", + Order: ccev2.OrderASC, + PageNo: 1, + PageSize: 10, + }, +} + +resp, err := ccev2Client.ListInstancesByPage(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 获取节点组的节点列表 +使用以下代码可以获取节点组的节点列表 +```go +args := &ListInstanceByInstanceGroupIDArgs{ + ClusterID: "your-cluster-id", + InstanceGroupID: "your-instance-group-id", + PageSize: 0, + {ageNo: 0, +} +resp, err := ccev2Client.ListInstancesByInstanceGroupID(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 更新节点配置 +使用以下代码可以更新节点的配置信息。需要注意不是所有配置信息都是可更改的。 +```go +args := &UpdateInstanceArgs{ + ClusterID: "your-cluster-id", + InstanceID: "your-instance-id", + InstanceSpec: YOUR_NEW_INSTANCE_SPEC, +} +respUpdate, err := CCE_CLIENT.UpdateInstance(args) + +s, _ := json.MarshalIndent(respUpdate, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 删除节点(集群缩容) +使用以下代码可以删除集群内的一个节点 +```go +args := &ccev2.DeleteInstancesArgs{ + ClusterID: "cluster-id", + DeleteInstancesRequest: &ccev2.DeleteInstancesRequest{ + InstanceIDs: []string{ "instance-id" }, + DeleteOption: &types.DeleteOption{ + MoveOut: false, + DeleteCDSSnapshot: true, + DeleteResource: true, + }, + }, +} + +resp, err := ccev2Client.DeleteInstances(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 检查集群网络网段 +使用以下代码可以检查集群网络网段是否冲突 +```go +args := &ccev2.CheckClusterIPCIDArgs{ + VPCID: "vpc-id", + VPCCIDR: "192.168.0.0/16", + ClusterIPCIDR: "172.31.0.0/16", + IPVersion: "ipv4", +} + +resp, err := ccev2Client.CheckClusterIPCIDR(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 检查容器网络网段 +使用以下代码可以检测容器网络网段是否冲突 +```go +args := &ccev2.CheckContainerNetworkCIDRArgs{ + VPCID: "vpc-id", + VPCCIDR: "192.168.0.0/16", + ContainerCIDR: "172.28.0.0/16", + ClusterIPCIDR: "172.31.0.0/16", + MaxPodsPerNode: 256, +} + +resp, err := ccev2Client.CheckContainerNetworkCIDR(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 推荐集群网络网段 +使用以下代码可以推荐可使用的集群网络网段 +```go +args := &ccev2.RecommendClusterIPCIDRArgs{ + ClusterMaxServiceNum: 2, + ContainerCIDR: "172.28.0.0/16", + IPVersion: "ipv4", + PrivateNetCIDRs: []ccev2.PrivateNetString{ccev2.PrivateIPv4Net172}, + VPCCIDR: "192.168.0.0/16", +} + +resp, err := ccev2Client.RecommendClusterIPCIDR(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 推荐容器网络网段 +使用以下代码可以推荐可使用的容器网络网段 +```go +args := &ccev2.RecommendContainerCIDRArgs{ + ClusterMaxNodeNum: 2, + IPVersion: "ipv4", + K8SVersion: types.K8S_1_16_8, + MaxPodsPerNode: 32, + PrivateNetCIDRs: []ccev2.PrivateNetString{ccev2.PrivateIPv4Net172}, + VPCCIDR: "192.168.0.0/16", + VPCID: "vpc-id", +} + +resp, err := ccev2Client.RecommendContainerCIDR(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 获取集群配额 +使用以下代码可以获取集群配额 +```go +resp, err := ccev2Client.GetClusterQuota() +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 获取集群节点配额 +使用以下代码可以获取集群的节点配额 +```go +clusterID := "cluster-id" + +resp, err := ccev2Client.GetClusterNodeQuota(clusterID) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 创建节点组 +使用以下代码可以创建节点组 +```go +args := &CreateInstanceGroupArgs{ + ClusterID: CCE_CLUSTER_ID, + Request: &CreateInstanceGroupRequest{ + types.InstanceGroupSpec{ + InstanceGroupName: "your-instance-group-name", + CleanPolicy: types.DeleteCleanPolicy, + Replicas: 3, + InstanceTemplate: types.InstanceTemplate{ + InstanceSpec: types.InstanceSpec{ + ClusterRole: types.ClusterRoleNode, + Existed: false, + MachineType: types.MachineTypeBCC, + InstanceType: bccapi.InstanceTypeN3, + VPCConfig: types.VPCConfig{ + VPCID: "your-vpc-id", + VPCSubnetID: "your-vpc-subnet-id", + SecurityGroupID: "your-secuirity-group-id", + AvailableZone: types.AvailableZoneA, + }, + DeployCustomConfig: types.DeployCustomConfig{ + PreUserScript: "your-script", + PostUserScript:"your-script", + }, + InstanceResource: types.InstanceResource{ + CPU: 1, + MEM: 4, + RootDiskSize: 40, + LocalDiskSize: 0, + }, + ImageID: IMAGE_TEST_ID, + InstanceOS: types.InstanceOS{ + ImageType: bccapi.ImageTypeSystem, + }, + NeedEIP: false, + AdminPassword: "your-admin-password", + SSHKeyID: "your-ssh-key-id", + InstanceChargingType: bccapi.PaymentTimingPostPaid, + RuntimeType: types.RuntimeTypeDocker, + }, + }, + }, + }, +} +resp, err := ccev2Client.CreateInstanceGroup(args) + +s, _ = json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + + +## 获取节点组列表 +使用以下代码可以获取节点组列表 +```go +args := &ListInstanceGroupsArgs{ + ClusterID: "your-cluster-id", + ListOption: &InstanceGroupListOption{ + PageNo: 0, + PageSize: 0, + }, +} +resp, err := ccev2Client.ListInstanceGroups(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + + +## 查询节点组详情 +使用以下代码可以查询节点组详情 +```go +args := &GetInstanceGroupArgs{ + ClusterID: "your-cluster-id", + InstanceGroupID: "your-instance-group-id", +} +resp, err := ccev2Client.GetInstanceGroup(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + + +## 修改节点组内节点副本数 +使用以下代码可以修改节点组内节点副本数 +```go +args := &UpdateInstanceGroupReplicasArgs{ + ClusterID: "your-cluster-id", + InstanceGroupID: "your-instance-group-id", + Request: &UpdateInstanceGroupReplicasRequest{ + Replicas: 1, + DeleteInstance: true, + DeleteOption: &types.DeleteOption{ + MoveOut: false, + DeleteCDSSnapshot: true, + DeleteResource: true, + }, + }, +} +resp, err := ccev2Client.UpdateInstanceGroupReplicas(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 修改节点组Autoscaler配置 +使用以下代码可以修改节点Autoscaler配置 +```go +args := &UpdateInstanceGroupClusterAutoscalerSpecArgs{ + ClusterID: "your-cluster0id", + InstanceGroupID: "cce-instance-group-id", + Request: &ClusterAutoscalerSpec{ + Enabled: true, + MinReplicas: 2, + MaxReplicas: 5, + ScalingGroupPriority: 1, + }, +} + +resp, err := ccev2Client.UpdateInstanceGroupClusterAutoscalerSpec(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 删除节点组 +使用以下代码可以删除节点组 +```go +args := &DeleteInstanceGroupArgs{ + ClusterID: "your-cluster-id", + InstanceGroupID: "your-instance-group-id", + DeleteInstances: true, +} +resp, err := ccev2Client.DeleteInstanceGroup(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 创建Autoscaler +使用以下代码为集群创建Autoscaler +```go +args := &CreateAutoscalerArgs{ + ClusterID: "your-cce-cluster-id", +} + +resp, err := ccev2Client.CreateAutoscaler(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 查询Autoscaler +使用以下代码可以查询集群的Autoscaler +```go +args := &GetAutoscalerArgs{ + ClusterID: "your-cce-cluster-id", +} + +resp, err := ccev2Client.GetAutoscaler(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 更新Autoscaler +使用以下代码可以更新集群的Autoscaler +```go +args := &UpdateAutoscalerArgs{ + ClusterID: "your-cluster-id", + AutoscalerConfig: ClusterAutoscalerConfig{ + ReplicaCount: 5, + ScaleDownEnabled: true, + Expander: "random", + }, +} + +resp, err := ccev2Client.UpdateAutoscaler(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 获取Kubeconfig +使用以下代码可以获取集群的Kubeconfig +```go +args := &GetKubeConfigArgs{ + ClusterID: "your-cluster-id", + KubeConfigType: "kubeconfig-type-you-need", +} + +resp, err := ccev2Client.GetAdminKubeConfig(args) + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +# 错误处理 + +GO语言以error类型标识错误,CCE支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CCE服务返回的错误 + +用户使用SDK调用CCE相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// client 为已创建的cce Client对象 +args := &ccev2.ListClusterArgs{} +result, err := ccev2Client.ListClusters(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CCE发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当CCE服务端出现异常时,CCE服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[CCE错误码](https://cloud.baidu.com/doc/CCE/s/4jwvy1evj) + +## SDK日志 + +CCE GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +CCE GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +```go +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the CCE go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the CCE go sdk") +``` + + +# 版本变更记录 + +## v1.0.0 [2020-08-07] + +首次发布: + - 支持创建集群、获取集群列表、获取集群详情、删除集群。 + - 支持创建节点(集群扩容)、获取集群的节点列表、获取节点详情、删除节点(集群缩容) + - 支持检查集群网络网段、检查容器网络网段、推荐集群网络网段、推荐容器网络网段 + - 支持查询集群配额、集群节点配额 + + +## v1.1.0 [2020-08-20] + + 增加节点组相关接口: + - 支持节点组创建、获取节点组列表、查询节点组详情、修改节点组内节点副本数、删除节点组 + - 获取节点组的节点列表 + + +## v1.2.0 [2020-09-18] + +增加Autoscaler相关接口: + - 支持集群Autoscaler的初始化、查询与更新 +增加Kubeconfig相关接口: + - 支持获取集群的kubeconfig +增加Instance 相关接口: + - 支持对Instance部分属性的更新 + +## v1.3.0 [2021-06-17] + +Instance 增加字段: + - 支持新建和移入竞价实例(bid) + - 支持新建和移入实例时, 自定义 K8S Node Annotation + +## v1.4.0 [2021-06-29] + +增加InstanceGroup相关接口: + - 支持创建 InstanceGroup 扩容任务 + - 支持创建 InstanceGroup 缩容任务 +增加Task相关接口: + - 支持查询指定Task执行详情 + - 支持查询Task列表 diff --git a/bce-sdk-go/doc/CDN.md b/bce-sdk-go/doc/CDN.md new file mode 100644 index 0000000..09ae827 --- /dev/null +++ b/bce-sdk-go/doc/CDN.md @@ -0,0 +1,1440 @@ +CDN服务 + +# 概述 + +本文档主要介绍CDN GO SDK的使用。在使用本文档前,您需要先了解CDN的一些基本知识,并已开通了CDN服务。若您还不了解CDN,可以参考[产品描述](https://cloud.baidu.com/doc/CDN/index.html)和[快速入门](https://cloud.baidu.com/doc/CDN/s/ujwvye8ao)。 + +# 初始化 + +## 确认Endpoint + +目前使用CDN服务时,CDN的Endpoint都统一使用`https://cdn.baidubce.com`,这也是默认值。 + +## 获取密钥 + +要使用百度云CDN,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CDN做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 使用AK/SK新建CDN Client + +通过AK/SK方式访问CDN,用户可以参考如下代码新建一个CDN Client: + +```go +ak := "your_access_key_id" +sk := "your_secret_key_id" +endpoint := "https://cdn.baidubce.com" +client, err := cdn.NewClient(ak, sk, endpoint) +``` + +在上面代码中,变量`ak`对应控制台中的“Access Key ID”,变量`sk`对应控制台中的“Access Key Secret”,获取方式请参考《 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。变量`endpoint`必须为`https://cdn.baidubce.com`,也是默认值,为空表示使用默认值,设置为其他则SDK无法工作。 + +在下面的示例中,会频繁使用到GetDefaultClient函数,它的定义为: + +```go +func GetDefaultClient() *cdn.Client { + ak := "your_access_key_id" + sk := "your_secret_key_id" + endpoint := "https://cdn.baidubce.com" + + // ignore error in test, but you should handle error in dev + client, _ := cdn.NewClient(ak, sk, endpoint) + return client +} +``` + +## 自定义HTTP请求 + +> 支持对任何开放接口自定义HTTP请求 + +示例展示了不通过SDK的方法,而是自行根据[文档](https://cloud.baidu.com/doc/CDN/s/qjwvyexh6)构造请求,查询某个域名是否可以添加到百度云CDN系统。 + +```go +cli := GetDefaultClient() +method := "GET" +urlPath := fmt.Sprintf("/v2/domain/%s/valid", testDomain) +var params map[string]string +// 此请求头非必须,仅测试发送请求头 +reqHeaders := map[string]string{ + "X-Test": "go-sdk-test", +} +var bodyObj interface{} +var respObj interface{} +err := cli.SendCustomRequest(method, urlPath, params, reqHeaders, bodyObj, &respObj) +fmt.Printf("err:%+v\n", err) +fmt.Printf("respObj details:\n\ttype:%T\n\tvalue:%+v", respObj, respObj) +``` + +## 域名操作接口 + +### 域名列表查询 ListDomains + +> 查询该用户账户下拥有的加速域名 + +```go +marker := "" +cli := GetDefaultClient() +domains, nextMarker, err := cli.ListDomains(marker) +fmt.Printf("domains:%+v\n", domains) +fmt.Printf("nextMarker:%+v\n", nextMarker) +fmt.Printf("err:%+v\n", err) +``` + +`marker`参数表示从这个字符串所代表的索引开始查询域名列表,空表示从头开始。目前服务器处理分页size是一个很大的数,所以使用的时候将marker赋值为空即可。ListDomains返回的`nextMarker`表示下一个域名列表索引 ,空表示从`marker`开始之后的域名列表全部被获取,非空可用于传递到ListDomains参数。`domains`是一个string slice,表示域名列表数据,如: `["1.baidu.com", "2.baidu.com"]` + +### 查询用户名下所有域名 GetDomainStatus + +> 查询用户名下所有域名和域名状态,可以根据特定状态查询属于这个状态的域名。 + +```go +cli := client.GetDefaultClient() +status := "ALL" +domainStatus, err := cli.GetDomainStatus(status, "") +fmt.Printf("domainStatus:%+v\n", domainStatus) +fmt.Printf("err:%+v\n", err) +``` + +`status`表示域名的状态,如果为`ALL`,表示查询所有状态的域名,如果为`RUNNING`查询已经加速的域名,`STOPPED`查询停止加速的域名,`OPERATING`查询操作中的域名。 +`domainStatus`是**DomainStatus**类型对象,如下所示: + +| 字段 | 类型 | 说明 | +| ------ | ------ | --------------------------------------------------- | +| Domain | string | 域名,如`www.baidu.com` | +| Status | string | 域名状态,合法值为`RUNNING`、`STOPPED`和`OPERATING` | + +`domainStatus`的示例数据如:`[{"Domain":"1.baidu.com", "Status":"RUNNING"}, {"Domain":"2.baidu.com", "Status":"STOPPED"}]` + +### 查询域名是否可添加 IsValidDomain + +> 查询特定域名是否可以使用CDN加速,一个可以被添加的域名必须是合法的域名,不可重复添加,必须通过ICP备案。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" +domainValidInfo, err := cli.IsValidDomain(testDomain) +fmt.Printf("domainValidInfo:%+v\n", domainValidInfo) +fmt.Printf("err:%+v\n", err) +``` +`testDomain`是要测试的域名,`domainValidInfo`是**DomainValidInfo**类型对象,表示的是关于是否可添加的详细信息 + +| 字段 | 类型 | 说明 | +| ------ | ------ | --------------------------------------------------- | +| Domain | string | 域名,如`www.baidu.com` | +| IsValid | bool | true表示该域名可添加,false表示该域名不可被添加 | +| Message | string | 当IsValid为false,表示域名不可被添加的原因;当IsValid为true时,为空 | + +### 创建加速域名接口 CreateDomain + +> 添加一个加速域名到用户名下,该域名在调用IsValidDomain的时候返回必须为true。创建加速域名必须设置源站。 + +```go +cli := client.GetDefaultClient() +domainCreatedInfo, err := cli.CreateDomain("test_go_sdk.baidu.com", &api.OriginInit{ + Origin: []api.OriginPeer{ + { + Peer: "1.1.1.1", + Host: "www.baidu.com", + Backup: true, + Follow302: true, + }, + { + Peer: "http://2.2.2.2", + Host: "www.baidu.com", + Backup: false, + Follow302: true, + }, + }, + DefaultHost: "www.baidu.com", + Form: "default", +}) +fmt.Printf("domainCreatedInfo:%+v\n", domainCreatedInfo) +fmt.Printf("err:%+v\n", err) +``` + +`api.OriginPeer`表示一个源站,`api.OriginInit`表示对于这个加速域名的源站配置,包括一个或多个源站,和一个默认的回源host,表示为`DefaultHost`。`Form`表示加速服务类型,默认为`default`,其他可选的服务类型有`image`表示图片小文件,`download`表示大文件下载,`media`表示流媒体点播,`dynamic`表示动静态加速。 +源站信息**api.OriginPeer**的结构如下: + +| 字段 | 类型 | 说明 | +| --------- | ------ | ------------------------------------------------------------ | +| Peer | string | 源站的endpoint,如`http://2.2.2.2:9090`或 `https://test.baidu.com:9090`,源站类型可以为域名形式、IP形式和bucket,示例代码展示的是IP形式的源站类型,需要注意的是一个域名所有的源站必须具有相同的源站类型。**如果使用的是默认端口,即HTTP的80和HTTPS的443,CDN系统会将源站视为HTTP和HTTPS两种协议都支持**。 | +| Host | string | 表示回源时在HTTP请求头中携带的Host,如果没有设置则使用`DefaultHost` | +| Backup | bool | 表示是否为备用源站,备用源站在主源站都不可用的情况下才会使用,false表示这个源站是主源站。需要注意的是在golang中bool对象默认值为false,如果没有显式设置Backup的值,那么默认就是false。 | +| Follow302 | bool | true表示当回源到源站时源站返回302时,CDN系统会处理Location重定向,主动获取资源返回给客户端。false表示当源站返回302时,CDN系统透传302给客户端。**需要注意的是,目前Follow302已经修改为所有源站级别的配置,所以要求所有的源站Follow302必须一致,否则可能会出现不可预料的结果**。 | + +### 启用/停止/删除加速域名 EnableDomain/DisableDomain/DeleteDomain + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 启用加速域名 +err := cli.EnableDomain(testDomain) +fmt.Printf("err:%+v\n", err) + +// 停用加速域名 +err = cli.DisableDomain(testDomain) +fmt.Printf("err:%+v\n", err) + +// 删除该用户名下的加速域名 +err = cli.DeleteDomain(testDomain) +fmt.Printf("err:%+v\n", err) +``` + +## 域名配置接口 + +### 查询加速域名详情 GetDefaultClient + +> 查询某个特定域名的全部配置信息 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" +domainConfig, err := cli.GetDomainConfig(testDomain) +fmt.Printf("domainConfig:%+v\n", domainConfig) +fmt.Printf("err:%+v\n", err) +``` + +`domainConfig`是`*api.DomainConfig`类型的对象,他的结构相对比较复杂,如下所示: + +| 字段 | 类型 | 说明 | +| -------------- | ---------------- | ------------------------------------------------------------ | +| Domain | string | 域名信息,如:`test.baidu.com`。 | +| Cname | string | 域名的CNAME,如:`test.baidu.com.a.bdydns.com`。 | +| Status | string | 域名状态,合法值为`RUNNING`、`STOPPED`和`OPERATING`。 | +| CreateTime | string | 域名创建的时间,UTC时间,如:`2019-09-02T10:08:38Z。` | +| LastModifyTime | string | 上次修改域名配置的时间,UTC时间,如:`2019-09-06T15:00:21Z`。 | +| IsBan | string | 是否被禁用,禁用的意思是欠费或者其他违规操作被系统封禁,被封禁的域名不拥有加速服务。`ON`表示为被封禁,`YES`表示被封禁了。 | +| Origin | []api.OriginPeer | 源站信息,在创建加速域名接口有详细说明。 | +| DefaultHost | string | 默认的回源host,在创建加速域名接口有详细说明。 | +| CacheTTL | []api.CacheTTL | 文件类型与路径的缓存策略,在`设置/查询缓存过期规则`小段有详细说明。 | +| LimitRate | int | 下载限速,单位Byte/s。 | +| RequestAuth | api.RequestAuth | 访问鉴权配置,在设置访问鉴权有详细说明。 | +| Https | api.HTTPSConfig | HTTPS加速配置,在设置HTTPS加速有详细说明。 | +| FollowProtocol | bool | 是否开启了协议跟随回源,true表示开启了协议跟随回源,即访问资源是https://xxx或http://xxx之类的url,回源也使用相对应的scheme,即分别为HTTPS和HTTP。 | +| SeoSwitch | api.SeoSwitch | seo 开关配置,在设置SEO开关属性有详细说明。 | + +### 设置/查询缓存过期规则 SetCacheTTL/GetCacheTTL + +> 设置针对文件、目录和错误码等相关的缓存策略,合理设置缓存策略可以提高命中率。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置缓存策略使用源站规则 +err := cli.SetCacheTTL(testDomain, []api.CacheTTL{ + { + Type: "origin", + Value: "-", + TTL: 0, + }, +}) +fmt.Printf("err:%+v\n", err) + +// 设置缓存策略,分别设置后缀规则、目录规则、路径完全匹配和错误码 +err = cli.SetCacheTTL(testDomain, []api.CacheTTL{ + { + Type: "suffix", + Value: ".jpg", + TTL: 420000, + Weight: 30, + }, + { + Type: "path", + Value: "/js", + TTL: 10000, + }, + { + Type: "exactPath", + Value: "index.html", + TTL: 600, + Weight: 100, + }, + { + Type: "code", + Value: "404", + TTL: 600, + Weight: 100, + }, +}) +fmt.Printf("err:%+v\n", err) + +// 查询缓存策略 +cacheTTls, err := cli.GetCacheTTL(testDomain) +fmt.Printf("cacheTTls:%+v\n", cacheTTls) +fmt.Printf("err:%+v\n", err) +``` + +`cacheTTls`包含全部的缓存策略,每一个缓存策略用`api.CacheTTL`类型的对象表示,如下关于缓存策略结构的说明。 + +| 字段 | 类型 | 说明 | +| ------ | ------ | ------------------------------------------------------------ | +| Type | string | 缓存策略的类型。合法的类型有:`suffix`、`path`、`origin`、`code`和`exactPath`。"suffix"表示文件名后缀,"path"表示url中的目录,"origin"表示源站规则,此规则只有一条,只表示出权重即可,value为"-", ttl为 0,"code"表示异常码缓存,如可以配置404缓存100s ,“exactPath”表示路径完全匹配。 | +| Value | string | Type所指定类型的配置规则。 | +| Weight | int | 权重,0-100的整数,权重越高优先级越高,默认为0,优先级在为code类型下是没有作用的,可以忽略。权重越大,优先级越高,规则优先生效。不推荐两条缓存策略配置相同的权重,如果权重相同,会随机选择其中一条策略生效。 | +| TTL | int | 缓存时间,单位为秒。 | + +### 设置/查询缓存参数过滤规则 SetCacheUrlArgs/GetCacheUrlArgs + +> 设置缓存key,缓存key为CDN系统对某个资源的进行缓存的时候所采用的key,一个url可能带有很多参数,那么时候所有的参数都需要放在缓存key中呢?其实是不必的,下面展示了不同的设置。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置全URL缓存 +err := cli.SetCacheUrlArgs(testDomain, &api.CacheUrlArgs{ + CacheFullUrl: true, +}) +fmt.Printf("err:%+v\n", err) + +// 设置缓存带有部分参数 +err = cli.SetCacheUrlArgs(testDomain, &api.CacheUrlArgs{ + CacheFullUrl: false, + CacheUrlArgs: []string{"name", "id"}, +}) +fmt.Printf("err:%+v\n", err) + +// 设置忽略所有参数 +err = cli.SetCacheUrlArgs(testDomain, &api.CacheUrlArgs{ + CacheFullUrl: false, + CacheUrlArgs: []string{"name", "id"}, +}) +fmt.Printf("err:%+v\n", err) + +// 查询关于URL参数缓存设置 +cacheUrlArgs, err := cli.GetCacheUrlArgs(testDomain) +fmt.Printf("cacheUrlArgs:%+v\n", cacheUrlArgs) +``` + +`cacheUrlArgs`是`api.CacheUrlArgs`类型的对象,下面是详细说明: + +| 字段 | 类型 | 说明 | +| ------------ | -------- | ------------------------------------------------------------ | +| CacheFullUrl | bool | true或false,true表示支持全URL缓存,false表示忽略参数缓存(可保留部分参数)。注意golang中如果不显式赋值CacheFullUrl为true或false,那么取零值false。 | +| CacheUrlArgs | []string | CacheFullUrl为true时,此项不起作用;CacheFullUrl为false时,此项表示保留的参数列表,如果为空,表示忽略所有参数。 | + +### 设置/查询自定义错误码页面 SetErrorPage/GetErrorPage + +> 用户可以定义当访问源站或CDN系统内部出现错误时(通常是返回4xx或5xx错误码),有CDN系统返回给用户的重定向页面,用户可以设置这个重定向页面的链接。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置错误出现时的重定向页面 +err := cli.SetErrorPage(testDomain, []api.ErrorPage{ + { + Code: 510, + RedirectCode: 301, + Url: "/customer_404.html", + }, + { + Code: 403, + // 这里没有设置RedirectCode,表示使用默认的302重定向 + Url: "/custom_403.html", + }, +}) +fmt.Printf("err:%+v\n", err) + +// 取消设置错误出现时的重定向页面 +err = cli.SetErrorPage(testDomain, []api.ErrorPage{}) +fmt.Printf("err:%+v\n", err) + +// 设置错误出现时的重定向页面 +err = cli.SetErrorPage(testDomain, []api.ErrorPage{}) +fmt.Printf("err:%+v\n", err) + +// 查询设置的错误重定向页面 +errorPages, err := cli.GetErrorPage(testDomain) +fmt.Printf("errorPages:%+v\n", errorPages) +``` + +`errorPages`是`[]api.ErrorPage`类型的对象,`api.ErrorPage`结构的详细说明如下: + +| 字段 | 类型 | 说明 | +| ------------ | ------ | ------------------------------------------------------------ | +| Code | int | 特定的状态码,要求必须为HTTP的标准错误码,且不能是408、444、499等客户端异常/提前断开这类特殊状态码。 | +| RedirectCode | int | 重定向状态码,当出现code错误码时,重定向的类型。支持301和302,默认302。 | +| Url | string | 重定向目标地址 ,当出现code错误码是,重定向到这个用户自定义的url,即301或302重定向中HTTP报文中的Location的值为了Url。 | + +### 设置/查询和其他域名共享缓存配置 SetCacheShared/GetCacheShared + +> 用户可以设置域名与另外一个域名共享缓存,提升命中率,前提是两个域名必须属于同一账户。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置test_go_sdk.baidu.com和www.baidu.com共享缓存 +err := cli.SetCacheShared(testDomain, &api.CacheShared{ + Enabled: true, + SharedWith: "www.baidu.com", +}) +fmt.Printf("err:%+v\n", err) + +// 取消test_go_sdk.baidu.com和任何域名共享缓存 +err := cli.SetCacheShared(testDomain, &api.CacheShared{ + Enabled: false, +}) +fmt.Printf("err:%+v\n", err) + +// 查询test_go_sdk.baidu.com的共享缓存配置 +cacheSharedConfig, err := cli.GetCacheShared(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("cacheSharedConfig:%+v\n", cacheSharedConfig) +``` + +### 设置/查询访问Referer控制 SetRefererACL/GetRefererACL + +> 设置Referer的访问控制规则,当通过浏览器或者其他形式跳转到资源是,浏览器通常会在请求头中加入Referer信息。CDN系统提供对Referer进行过滤,可以设置Referer黑名单或者白名单。当Referer出现在黑名单中的请求到达时响应403,当Referer没有出现在白名单中的请求到达时响应403。黑名单和白名单设置只能选其一,要么设置黑名单要么设置白名单。当设置了白名单并且过滤规则为空时,白名单不生效。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" +isAllowEmpty := true + +// 设置白名单ACL +err := cli.SetRefererACL(testDomain, nil, []string{ + "a.bbbbbb.c", + "*.baidu.com.*", +}, isAllowEmpty) +fmt.Printf("err:%+v\n", err) + +// 设置白名单为空,白名单规则不生效 +err = cli.SetRefererACL(testDomain, nil, []string{}, isAllowEmpty) +fmt.Printf("err:%+v\n", err) + +// 设置黑名单ACL +err = cli.SetRefererACL(testDomain, []string{ + "a.b.c", + "*.xxxxx.com.*", +}, nil, isAllowEmpty) +fmt.Printf("err:%+v\n", err) + +// 查询referer ACL设置 +refererACL, err := cli.GetRefererACL(testDomain) +fmt.Printf("refererACL:%+v\n", refererACL) +fmt.Printf("err:%+v\n", err) +``` + +`isAllowEmpty`表示是否允许空Referer访问,true表示允许空Referer访问,即如果Referer为空,那么不管是设置了黑名单还是白名单都不会生效,大多数情况下这个值都设置为**true**;false表示不允许空Referer访问,当设置为false时,如果访问的HTTP报文中不存在Referer那么CDN系统将返回403。`refererACL`是`api.RefererACL`类型的对象,他的详细说明如下: + +| 字段 | 类型 | 说明 | +| ---------- | -------- | ------------------------------------------------------------ | +| BlackList | []string | 可选项,表示referer黑名单列表,支持使用通配符*,不需要加protocol,如设置某个黑名单域名,设置为"www.xxx.com"形式即可,而不是"http://www.xxx.com"。* | +| WhiteList | []string | *可选项,list类型,表示referer白名单列表,支持通配符*,同样不需要加protocol。 | +| AllowEmpty | bool | 必选项,bool类型,表示是否允许空referer访问,为true即允许空referer访问。 | + +### 设置/查询访问IP控制 SetIpACL/GetIpACL + +> CDN获取客户端IP,同配置中的IP黑/白名单进行匹配,对匹配上的客户端请求进行拒绝/放过。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置IP白名单 +err := cli.SetIpACL(testDomain, []string{ + "1.1.1.1", + "2.2.2.2", +}, nil) +fmt.Printf("err:%+v\n", err) + +// 设置IP黑名单,CIDR格式的IP +err = cli.SetIpACL(testDomain, nil, []string{ + "1.2.3.4/24", +}) +fmt.Printf("err:%+v\n", err) + +// 查询IP黑白设置 +ipACL, err := cli.GetIpACL(testDomain) +fmt.Printf("ipACL:%+v\n", ipACL) +fmt.Printf("err:%+v\n", err) +``` + +`ipACL`是`api.IpACL`类型对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| --------- | -------- | ------------------------------------------------------------ | +| BlackList | []string | IP黑名单列表,当设置黑名单生效时,当客户端的IP属于BlackList,CDN系统返回403。BlackList不可与WhiteList同时设置。 | +| WhiteList | []string | IP白名单列表,当设置白名单生效时,当WhiteList为空时,没有白名单效果。当WhiteList非空时,只有客户端的IP属于WhiteList才允许访问。同样不可与BlackList同时设置。 | + +### 设置/查询访问UA控制 SetUaACL/GetUaACL + +> CDN获取HTTP请求头中的User-Agent,同配置中的黑/白名单进行匹配,对匹配上的请求进行拒绝/放过。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置UA白名单 +err = cli.SetUaACL(testDomain, nil, []string{ + "curl/7.73.0", +}) +fmt.Printf("err:%+v\n", err) + +// 设置UA黑名单 +err := cli.SetUaACL(testDomain, []string{ + "Test-Bad-UA", +}, nil) +fmt.Printf("err:%+v\n", err) + +// 查询UA黑白设置 +uaACL, err := cli.GetUaACL(testDomain) +fmt.Printf("uaACL:%+v\n", uaACL) +fmt.Printf("err:%+v\n", err) +``` + +`ipACL`是`api.IpACL`类型对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| --------- | -------- | ------------------------------------------------------------ | +| BlackList | []string | IP黑名单列表,当设置黑名单生效时,当客户端的IP属于BlackList,CDN系统返回403。BlackList不可与WhiteList同时设置。 | +| WhiteList | []string | IP白名单列表,当设置白名单生效时,当WhiteList为空时,没有白名单效果。当WhiteList非空时,只有客户端的IP属于WhiteList才允许访问。同样不可与BlackList同时设置。 | + +### 设置访问鉴权 SetDomainRequestAuth + +> 高级鉴权也是为了防止客户源站内容被盗用,比Referer黑白名单和IP黑白名单更加安全。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" +err := cli.SetDomainRequestAuth(testDomain, &api.RequestAuth{ + Type: "c", + Key1: "secretekey1", + Key2: "secretekey2", + Timeout: 300, + WhiteList: []string{ + "/crossdomain.xml", + }, + SignArg: "sign", + TimeArg: "t", +}) + +fmt.Printf("err:%+v\n", err) +``` + +示例代码设置一个C类鉴权方式,对应的字段在[高级鉴权](https://cloud.baidu.com/doc/CDN/s/ujwvyeo0t)。有非常消息的说明。 + +### 设置域名限速 SetLimitRate (废弃,请使用SetTrafficLimit) + +> 限定此域名下向客户端传输的每份请求的最大响应速率。该速率是针对单个请求的,多请求自动翻倍。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置单请求限速1024Bytes/s +err := cli.SetLimitRate(testDomain, 1024) +fmt.Printf("err:%+v\n", err) + +// 不做任何限速 +err = cli.SetLimitRate(testDomain, 0) +fmt.Printf("err:%+v\n", err) +``` + +### 设置单连接限速 SetTrafficLimit/GetTrafficLimit + +```go +cli := client.GetDefaultClient() +// 开启单连接限速,开启时间为北京时间上午10~12点之间,单链接限速值为1000KB左右。 +trafficLimit := &api.TrafficLimit{ + Enable: true, + LimitRate: 1000, + LimitStartHour: 10, + LimitEndHour: 12, + TrafficLimitUnit: "k", + } +// 设置单连接限速 +err := cli.SetTrafficLimit(testDomain, trafficLimit) + +// 查询单连接限速配置 +trafficLimit, err := cli.GetTrafficLimit(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("trafficLimit:%+v\n", trafficLimit) +``` + +`trafficLimit`是`api.TrafficLimit`类型的对象,详细说明如下: + + +| 字段 | 类型 | 说明 | +| ------- | ---- | ------------------------------------------------------------ | +| Enabled | bool | true表示开启单链接限速,false表示关闭。当enable为false时,下面的配置均无效 | +| limitRate | int | 限速值,建议显示设置,否则效果未定义。单位为limitRateUnit设置的值,默认为Byte/s。 | +| limitStartHour | int | 限速开始时间,请输入0 - 24范围的数字,小于限速结束时间,默认值为0(可选) | +| limitEndHour | int | 限速结束时间,请输入0 - 24范围的数字,大于限速开始时间,默认值为24(可选) | +| limitRateAfter | int | 在发送了多少数据之后限速,单位Byte(可选) | +| trafficLimitArg | string | 限速参数名称,根据url中提取的arg进行限速(可选) | +| trafficLimitUnit | string | 限速参数单位,支持m、k、g,默认为Byte(可选) | + +### 设置/查询Cors跨域 SetCors/GetCors + +> 跨域访问是指发起请求的资源所在域不同于该请求所指向的资源所在域,出于安全考虑,浏览器会限制这种非同源的访问。开启此功能,用户可以自己进行清除缓存及跨域访问配置,当源站(BOS)对象更新后,CDN所有对应的缓存可进行同步自动更新。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置允许的跨域域名 +err := cli.SetCors(testDomain, true, []string{ + "http://www.baidu.com", + "http://*.bce.com", +}) +fmt.Printf("err:%+v\n", err) + +// 取消跨域设置 +err = cli.SetCors(testDomain, false, nil) +fmt.Printf("err:%+v\n", err) + +// 查询跨域设置 +cors, err := cli.GetCors(testDomain) +fmt.Printf("cors:%+v\n", cors) +fmt.Printf("err:%+v\n", err) +``` + +### 设置/查询IP访问限频 SetAccessLimit/GetAccessLimit + +> 限制IP单节点的每秒访问次数,针对所有的访问路径。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置单IP访问限频为200/s +err := cli.SetAccessLimit(testDomain, &api.AccessLimit{ + Enabled: true, + Limit: 200, +}) +fmt.Printf("err:%+v\n", err) + +// 取消IP访问限频 +err = cli.SetAccessLimit(testDomain, &api.AccessLimit{ + Enabled: false, + Limit: 0, +}) +fmt.Printf("err:%+v\n", err) + +// 查询IP访问限频设置 +accessLimit, err := cli.GetAccessLimit(testDomain) +fmt.Printf("accessLimit:%+v\n", accessLimit) +fmt.Printf("err:%+v\n", err) +``` + +`accessLimit`是`api.AccessLimit`类型的对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| ------- | ---- | ------------------------------------------------------------ | +| Enabled | bool | true表示开启IP单节点访问限频,false表示取消限频。这里要注意golang的bool对象的零值为false,设置Limit的值必须要设置Enabled为true。 | +| Limit | int | 1秒内单个IP节点请求次数上限,enabled为true时此项默认为1000,enabled为false此项无意义。 | + +### 设置/查询获取真实用户IP SetClientIp/GetClientIp + +> 用户在使用CDN加速的同时可获取访问源的真实IP地址或客户端IP地址.。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置Client IP,源站可以获取到访问源的客户端IP地址,携带True-Client-Ip +err := cli.SetClientIp(testDomain, &api.ClientIp{ + Enabled: true, + Name: "True-Client-IP", +}) +fmt.Printf("err:%+v\n", err) + +// 设置Real IP:源站可以获取到访问源的真实IP地址,携带X-Real-IP。 +err = cli.SetClientIp(testDomain, &api.ClientIp{ + Enabled: true, + Name: "X-Real-IP", +}) +fmt.Printf("err:%+v\n", err) + +// 关闭设置Client IP和Real IP +err = cli.SetClientIp(testDomain, &api.ClientIp{ + Enabled: false, +}) +fmt.Printf("err:%+v\n", err) + +// 查询关于客户端IP的设置 +clientIp, err := cli.GetClientIp(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("clientIp:%+v\n", clientIp) +``` + +`clientIp`是`api.ClientIp`类型的对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| ------- | ------ | ------------------------------------------------------------ | +| Enabled | bool | true表示开启,false表示关闭。 | +| Name | string | 只能设置为"True-Client-Ip"或"X-Real-IP"两种之一,默认为"True-Client-Ip",enabled为false时此项无意义。 | + +### 设置/查询回源重试条件 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置当回源遇到429、500、502或者503错误码时进行重试 +err := client.SetRetryOrigin(testDomain, &api.RetryOrigin{ + Codes: []int{429, 500, 502}, +}) +fmt.Printf("err:%+v\n", err) + +// 查询回源重试策略 +retryOrigin, err := client.GetRetryOrigin(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("retryOrigin:%+v\n", retryOrigin) +``` + + +### 更新加速域名回源地址 SetDomainOrigin + +> 加速域名的回源地址可以在创建域名的时候设置好,也可以在创建完成后进行更新。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +err := cli.SetDomainOrigin(testDomain, []api.OriginPeer{ + { + Peer: "1.1.1.1", + Host: "www.baidu.com", + Backup: true, + Follow302: true, + }, + { + Peer: "http://2.2.2.2", + Host: "www.baidu.com", + Backup: false, + Follow302: true, + }, +}, "www.baidu.com") +fmt.Printf("err:%+v\n", err) +``` + +`api.OriginPeer`类型的详细说明在**创建加速域名一节**已经有说明。 + +### 设置/查询回源协议 SetOriginProtocol/GetOriginProtocol + +> 设置回源协议指的是设置CDN侧接受请求miss的时候请求源站的协议,可以设置的回源协议有"http"、"https"或者"*","*"表示协议跟随回源(请求是HTTP/HTTPS协议那么请求源站也是HTTP/HTTPS协议)。默认是HTTP回源。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置回源协议为HTTP +err := cli.SetOriginProtocol(testDomain, "http") +fmt.Printf("err:%+v\n", err) + +// 设置回源协议为HTTPS(域名必须HTTPS,否则以下请求失败) +err = cli.SetOriginProtocol(testDomain, "https") +fmt.Printf("err:%+v\n", err) + +// 设置回源跟随协议 +err = cli.SetOriginProtocol(testDomain, "*") +fmt.Printf("err:%+v\n", err) + +// 查询回源协议 +originProtocol, err := cli.GetOriginProtocol(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("originProtocol:%s\n", originProtocol) +``` + +### 设置协议跟随回源 SetFollowProtocol(废弃,设置协议跟随请使用SetOriginProtocol) + +> 设置协议跟随回源,表示CDN节点回源协议与客户端访问协议保持一致。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置协议跟随回源 +err := cli.SetFollowProtocol(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 取消设置协议跟随回源 +err = cli.SetFollowProtocol(testDomain, false) +fmt.Printf("err:%+v\n", err) +``` + +### 设置/查询Range回源 SetRangeSwitch/GetRangeSwitch + +> 设置Range回源,有助于减少大文件分发时回源消耗并缩短响应时间。此功能需源站支持Range请求。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置range回源 +err := cli.SetRangeSwitch(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 取消设置range回源 +err = cli.SetRangeSwitch(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询range回源设置 +rangeSwitch, err := cli.GetRangeSwitch(testDomain) +fmt.Printf("rangeSwitch:%+v\n", rangeSwitch) +``` + +### 设置/查询移动访问控制 SetMobileAccess/GetMobileAccess + +> 开启移动访问控制,源站可有针对性地进行移动端/PC端的资源内容分发,暂不支持自定义进行移动端配置。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置移动访问 +err := cli.SetMobileAccess(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 取消设置移动访问 +err = cli.SetMobileAccess(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询移动访问设置 +mobileAccess, err := cli.GetMobileAccess(testDomain) +fmt.Printf("mobileAccess:%+v\n", mobileAccess) +fmt.Printf("err:%+v\n", err) +``` + +### 设置/查询HttpHeader SetHttpHeader/GetHttpHeader + +> CDN支持CDN节点到客户端的response(HTTP响应头)、CDN节点到源站的request(HTTP请求头)中的header信息修改。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置CDN系统工作时增删的HTTP请求头 +err := cli.SetHttpHeader(testDomain, []api.HttpHeader{ + { + Type: "origin", + Header: "x-auth-cn", + Value: "xxxxxxxxx", + Action: "remove", + }, + { + Type: "response", + Header: "content-type", + Value: "application/octet-stream", + Action: "add", + }, +}) +fmt.Printf("err:%+v\n", err) + +// 取消CDN系统工作时增删的HTTP请求头 +err = cli.SetHttpHeader(testDomain, []api.HttpHeader{}) +fmt.Printf("err:%+v\n", err) + +// 查询CDN系统工作时增删的HTTP请求头 +headers, err := cli.GetHttpHeader(testDomain) +fmt.Printf("headers:%+v\n", headers) +fmt.Printf("err:%+v\n", err) +``` + +`headers`是`api.HttpHeader`类型的对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| -------- | ------ | ------------------------------------------------------------ | +| Type | string | "origin"表示此header 回源生效,"response"表示给用户响应时生效。 | +| Header | string | header为http头字段,一般为HTTP的标准Header,也可以是用户自定义的;如x-bce-authoriztion。 | +| Value | string | 指定Header的值。 | +| Action | string | 表示是删除还是添加,可选remove/add,默认是add;目前console只支持add action; API做后端remove配置的兼容。 | +| Describe | string | 描述,可选,可以是中文,统一使用Unicode统码;长度不能超过100个字符。 | + +### 设置/查询SEO开关属性 SetDomainSeo/GetDomainSeo + +> SEO(Search Engine Optimization)优化是一种利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名的方式。目前CDN系统支持两项优化配置:(1)搜索引擎开启自动回源;(2)数据与百度搜索链接。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置SEO优化 +err := cli.SetDomainSeo(testDomain, &api.SeoSwitch{ + DirectlyOrigin: "ON", + PushRecord: "OFF", +}) +fmt.Printf("err:%+v\n", err) + +// 查询SEO优化设置 +seoSwitch, err := cli.GetDomainSeo(testDomain) +fmt.Printf("seoSwitch:%+v\n", seoSwitch) +fmt.Printf("err:%+v\n", err) +``` + +`seoSwitch`是`api.SeoSwitch`类型的对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| -------------- | ------ | ------------------------------------- | +| DirectlyOrigin | string | ON表示设置直接回源,OFF则相反。 | +| PushRecord | string | ON表示给大搜推送访问记录,OFF则相反。 | + +### 设置/查询页面优化 SetFileTrim/GetFileTrim + +> 用户开启页面优化功能,将自动删除 html中的注释以及重复的空白符,这样可以有效地去除页面的冗余内容,减小文件体积,提高加速分发效率。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置页面优化 +err := cli.SetFileTrim(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 取消页面优化 +err = cli.SetFileTrim(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询页面优化设置 +fileTrim, err := cli.GetFileTrim(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("fileTrim:%+v\n", fileTrim) +``` + +### 设置/查询IPv6开关 SetIPv6/GetIPv6 + +> 开启后,IPv6的客户端请求将支持以IPv6协议访问CDN,CDN也将携带IPv6的客户端IP信息访问您的源站。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 开启IPv6 +err := cli.SetIPv6(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 关闭IPv6 +err = cli.SetIPv6(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询IPv6开关 +ipv6Switch, err := cli.GetIPv6(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("ipv6Switch:%+v\n", ipv6Switch) +``` + +### 设置/查询QUIC开关 SetQUIC/GetQUIC + +> Quick UDP Internet Connection(QUIC)协议是Google公司提出基于UDP的高效可靠的互联网传输层协议。 本接口用于查询特定域名的QUIC启用状态。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 开启QUIC +err := cli.SetQUIC(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 关闭QUIC +err = cli.SetQUIC(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询QUIC开关 +quicSwitch, err := cli.GetQUIC(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("quicSwitch:%+v\n", quicSwitch) +``` + +### 设置/查询离线模式 SetOfflineMode/GetOfflineMode + +> 离线模式指的是在资源过期回源的时候,如果源站异常,那么CDN会响应之前缓存的资源,响应客户端200,但是回源日志中还是显示5xx。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 开启离线模式 +err := cli.SetOfflineMode(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 关闭离线模式 +err = cli.SetOfflineMode(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询离线模式 +offlineMode, err := cli.GetOfflineMode(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("offlineMode:%+v\n", offlineMode) +``` + +### 设置/查询视屏拖拽 SetMediaDrag/GetMediaDrag + +> CDN支持flv与mp4视频类型的拖拽,开启拖拽可降低回源率,提升速度。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置视频拖拽设置 +err := cli.SetMediaDrag(testDomain, &api.MediaDragConf{ + Mp4: &api.MediaCfg{ + DragMode: "second", + FileSuffix: []string{ + "mp4", + "m4a", + "m4z", + }, + StartArgName: "startIndex", + }, + Flv: &api.MediaCfg{ + DragMode: "byteAV", + FileSuffix: []string{}, + }, +}) +fmt.Printf("err:%+v\n", err) + +// 查询视频拖拽设置 +mediaDragConf, err := cli.GetMediaDrag(testDomain) +fmt.Printf("mediaDragConf:%+v\n", mediaDragConf) +fmt.Printf("err:%+v\n", err) +``` + +`mediaDragConf`是`api.MediaDragConf`类型的对象,定义如下: + +```go +type MediaDragConf struct { + Mp4 *MediaCfg + Flv *MediaCfg +} +``` + +可以设置Mp4或Flv类型视频流相关的拖拽,`MediaCfg`的详细说明如下: + +| 字段 | 类型 | 说明 | +| ------------ | -------- | ------------------------------------------------------------ | +| FileSuffix | []string | CDN系统支持MP4文件的伪流(pseudo-streaming)播放,通常这些文件拓展名为.mp4,.m4v,.m4a,因此这个fileSuffix值为文件拓展名集合,如: ["mp4", "m4v", "m4a"],type为mp4,fileSuffix默认值为["mp4"];type为flv,fileSuffix默认值为["flv"] | +| StartArgName | string | start参数名称,默认为“start”,您可以自定义参数名称,但是要求不能和`endArgName`相同 | +| EndArgName | string | end参数名称,默认为“end”,您可以自定义参数名称,但是要求不能和`startArgName`相同 | +| DragMode | string | mp4类型按秒进行拖拽,flv类型按字节进行拖拽。type为flv可选择的模式为“byteAV”或”byte”;type为mp4只能是"second"模式 | + +### 设置/查询页面压缩 SetContentEncoding/GetContentEncoding + +> 开启页面压缩功能后,您可以对大多数静态文件进行压缩,有效减少用户传输内容大小,加速分发效果。目前页面压缩支持Brotli压缩和Gzip压缩两种方式。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 设置页面压缩算法为gzip +err := cli.SetContentEncoding(testDomain, true, "gzip") +fmt.Printf("err:%+v\n", err) + +// 设置页面压缩算法为br +err = cli.SetContentEncoding(testDomain, true, "br") +fmt.Printf("err:%+v\n", err) + +// 关闭页面压缩 +err = cli.SetContentEncoding(testDomain, false, "br") +fmt.Printf("err:%+v\n", err) + +// 查询页面压缩算法,当关闭页面压缩时contentEncoding为空 +contentEncoding, err := cli.GetContentEncoding(testDomain) +fmt.Printf("contentEncoding:%+v\n", contentEncoding) +``` + +### 设置HTTPS加速 SetDomainHttps + +> 配置HTTPS的一个加速域名,必须要上传证书,了解证书详情请参考[证书管理](https://cloud.baidu.com/doc/Reference/s/8jwvz26si/)。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +err := cli.SetDomainHttps(testDomain, &api.HTTPSConfig{ + Enabled: false, + CertId: "ssl-xxxxxx", + Http2Enabled: true, + HttpRedirect: true, + HttpRedirectCode: 301, +}) +fmt.Printf("err:%+v\n", err) + +err = cli.SetDomainHttps(testDomain, &api.HTTPSConfig{ + Enabled: false, +}) +fmt.Printf("err:%+v\n", err) +``` + +`api.HTTPSConfig`的结构比较复杂,详细说明如下: + +| 字段 | 类型 | 说明 | +| ----------------- | ------ | ------------------------------------------------------------ | +| Enabled | bool | 开启HTTPS加速,默认为false,当enabled=false,以下几列字段设置无效。 | +| CertId | string | 当enabled=true时此项为必选,为SSL证书服务返回的证书ID,当enabled=False此项无效。 | +| HttpRedirect | bool | 为true时将HTTP请求重定向到HTTPS(重定向状态码为httpRedirectCode所配置),默认为false,当enabled=false此项无效,不可与httpsRedirect同时为true。 | +| HttpRedirectCode | int | 重定向状态码,可选值301/302,默认302,当enabled=false此项无效,httpRedirect=false此项无效。 | +| HttpsRedirect | bool | 为true时将HTTPS请求重定向到HTTP重定向状态码为httpsRedirectCode所配置),默认为false,当enabled=false此项无效,不可与httpRedirect同时为true。 | +| HttpsRedirectCode | int | 重定向状态码,可选值301/302,默认302,当enabled=false此项无效,httpsRedirect=false此项无效。 | +| Http2Enabled | bool | 开启HTTP2特性,当enabled=false此项无效。必须要注意go的bool对象零值为false。 | +| ~~HttpOrigin~~ | bool | **已弃用**,设置回源协议请参考**SetOriginProtocol**。 | +| SslVersion | string | 设置TLS版本,默认为支持从TLSv1.0到TLSv1.3的版本,也可以设置为以下四个之一,SSLV3,TLSV1,TLSV11,TLSV12,当enabled=false时此项无效,此项一般取默认值,无需设置。 | + +### 设置/查询OCSP SetOCSP/GetOCSP + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 开启OCSP +err := cli.SetOCSP(testDomain, true) +fmt.Printf("err:%+v\n", err) + +// 关闭OCSP +err = cli.SetOCSP(testDomain, false) +fmt.Printf("err:%+v\n", err) + +// 查询OCSP +ocspSwitch, err := cli.GetOCSP(testDomain) +fmt.Printf("err:%+v\n", err) +fmt.Printf("ocspSwitch:%+v\n", ocspSwitch) +``` + +## 证书管理接口 + +### 添加/修改域名证书 PutCert + +> 给某个域名添加或修改证书,如果该域名已经绑定了一个证书,则该方法为修改(将用户新上传的证书替换掉证书库中的老证书,且给域名绑定新证书)。 如果域名之前没有绑定证书,则该方法为上传新证书。可自定义是否开启HTTPS。 + +```go +cli := client.GetDefaultClient() +// certData要带有证书链信息 +certData := "-----BEGIN CERTIFICATE-----\nMIIFPTCCBCWgAwIBAgISBGRxBpT9H0OPrHDku006DU9SMA0GCSqGSIb3DQEBCwUA\nMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD\nEwJSMzAeFw0yMTAzMTgwNzE0MzNaFw0yMTA2MTYwNzE0MzNaMB0xGzAZBgNVBAMM\nEiouY29kaW5nMzY1eDI0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBALM1g+BnRWl26auFUgAEU6L/2OWixH6K1BOp7JtlHW1RYM1I72pohuXyu0Hc\n20vOYpIQHpN1UY1e7bTKHsWiE/qUO/MeORbuslkYLBi3rZDE6i3aiYIFmaxjeYL9\nVQEJFrJI8X1AiIoP3MyiA4QsPotBH++3FyBb6sl5HtgqmCqItbsh6NV2FDN9+bm0\nz4hGSlJ1Wg9N03pXTaE8FT4AsCJzd/m+z+5u02aO5tEHWHiwrL4Yj/Y5H39x8/Ax\nHyaUjK62bgFySYH/XpU89dAKKo+uwOx5iBLXR8ni7yUj3y5NJ5A+AZUa61WbNNh0\n3jmgXff+s1zSwSiK+GP8q+Fz9+sCAwEAAaOCAmAwggJcMA4GA1UdDwEB/wQEAwIF\noDAdBgNVHSUEFjAUwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd\nBgNVHQ4EFgQUArIIp83mJ+O7zRPwVr5ehX6JVJ4wHwYDVR0jBBgwFoAUFC6zF7dY\nVsuuUAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRw\nOi8vcjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNy\nLm9yZy8wLwYDVR0RBCgwJoISKi5jb2RpbmczNjV4MjQuY29tghBjb2RpbmczNjV4\nMjQuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYI\nKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBBQYKKwYBBAHW\neQIEAgSB9gSB8wDxAHcAXNxDkv7mq0VEsV6a1FbmEDf71fpH3KFzlLJe5vbHDsoA\nAAF4RGaGEA1Hh4zntDdVfmKVzQT5p/mQdczLsoQp2hmkHrKiTw\nl8cCIQDlceLBxn2RWzl+LD00gvTZDlqL/iWI/pAJ5qtTKzMH1QB2APZclC/RdzAi\nFFQYCDCUVo7jTRMZM7/fDC8gC8xO8WTjAAABeERmhrYAAAQDAEcwRQIhAKCycKu6\nNch4dkzO9gfQdjwhyCsaKi8nxNDgS199gp+eAiBkePK1AEwf+fvWGV+mXWDXcjjS\n6QCjL7w5lKi7CrVJLzANBgkqhkiG9w0BAQsFA0MKbiwTqtJdEb\nPaaATAtN/NXHoESO/KHFGjJT9ua1PByM0Qqn6mvonck+Tu9fWxM6ZOvXheEdcCbS\n5zLI4AJTdp5yySPRQe2v9UwVO6keDsk0Ux1JWFWgV7otwV5P22pDxaKOKqUQ11ZM\neu5Pk9dKFjlhT+oP88acHKVBJ0CJk/D72jWlDUhn4LwiEJ/+mfGW0oPu5ht+z0Zb\nQLMt7xTX6fATALSCELFPwVrBwleUpCYeafxAJC6XAMF1xeifFfZduORqPAUqyj7U\nYp6h3Wy+rJRKo0bN1roPxgu4aexXnOth6Qeyvi7zq7IO+jMY1/VaEBQJGS/hqity\nKQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow\nMjELMAkGA1UEBhMCVVMxFjAUBgQxCzAJBgNVBAMT\nAlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs\njVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp\nTm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB\nU840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7\ngcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel\n/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R\noYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E\nBAMCAYYwSwYIKwYBBQUHAQEEPzA9ModHRwOi8vYXBwcy5p\nZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE\np7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE\nAYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu\nY3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0\nLmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf\nr52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B\nAQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH\nejWEHx2taPDY/laBL21/WKZuNTYQHHPVvCadTQsvd8\nS8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL\nqjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p\nO5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw\nUdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg==\n-----END CERTIFICATE-----" + +privateKey := "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAszWD4GdFaXbpq4VSAARTov/Y5aLEforUE6nsm2UdbVFgzUjv\namiG5fK7QdzbS85ikhAek3VRjV7ttMoexaIT+pQ78x45Fu6yWRgsGLetkMTqLdqJ\nggWZrGN5gv1VAQkWskjxfUCIig/czKIDhCw+i0Ef77cXIFvqyXke2CqYKoi1uyHo\n1XYUM335ubTPiEZKUnVaD03Te7P7m7TZo7m0QdYeLCsvhiP\n9jkff3Hz8DEfJpSMrrZuAXJJgf9elTz10Aoqj67A7HmIEtdHyeLvJSPfLk0nkD4B\nlRrrVZs02HTeOaBd9/6zXNLBKIr4Y/yr4XP36wIDAQABAoIBAHnKZcyNApxRJy7d\nFURTrG97RvGRM874FHckpVtaVaxkgNAiwCrlzL/bva1eJl8XbN/tOopmUb0tBYk3\nT8BqjP9f3Ho2UQAnymdISTenJLrdSHVPLuKBYdXJaNw/xJRGk/koH45K3EBP1XPw\nq0kZNIw4/zZPjNT+AstXmEGApnLPFIoiEuB8grbfqQfr\nvY5ywxkXZdKQzeUr2ZVEPBp27q6lT13L0VBCCtOxZNNXPca+Lv\nJKlfvXBckuoyqHaTythwnw1PFPjq+uVMWfaa6gttfGGr11NVPnucRHwCMWUO0Cba\nfPw5ShECgYEA6fmMLOK/xRvaax7Hkr/In7IXUwSR1yNYupSeo+OF7C/+lkvvdwJP\nj/An9So2wVWY+DK3gKB7GCrDjYHB7Lv0m0dw7b+vEXv39z86mwUZsD8rMHy+HuqH\nZ4NieBkARWDaP/iE3HbrsWUHog7YvOZLr529byTRRQZdga5R1Vmf5RkCgYEAxBQx\nTPD6PypvVpQ4fkg58EJsFKwkGYTgcfS9dfwsjWbOTNW5gM/hCKgI/LTjsU7a0YYH\nvEMCTim81kuUKZ9rYlfXqB1Xzk9k6mUFeoP4t5KCe79YFi1A\nBUxqHUArJiLyO1g8cZruKK37DQKpT0sYHw7AAaMCgYBuSNUc1yiDVTSn51M0xbdg\nJsa9t9qyaJPLJoB8SaN3h8vdth9CnlE4TH/ZHLPAf4NiAi3isEI1Svrv+WiaGKIc\nixkcx4xSlnd0EFakeUv5elz2NuY6lluKnDBO4aHyEcvt+UtOy7Me47ssVQkuSPMF\n7Tk8aUNG4NA0byFdiihHCQKBgQCBXOkh4CLaJb8LGgMjnbdMAiaYhPHUPExwIo4V\nF2i1acxV+PPIPl4zfdlgEF/gjSvk7E6SMIuG0haaM4bu5xTL7zSC38kcflkQI9Ja\nWy6WYJFh6kjsdfguDn+7bVfVGOqexv/j/wRLhBhzsrxvZOO\negbHjQKBgEe3ghxGKW8dl3+/PDZ3KF8YBb5xUxe9BO8ufM0Pe+tCn8iWrTeAHG16\ncl9JgGlN9eIgx9VOh7suKlb9SsZLbAN60IO9nIx23g2nLqH+HyEZmE6zK5onSxua\n9vgcKjhNmg5WLvmQwz0ECw050HtDpptawvfNPinUY1LsjvkWqwiX\n-----END RSA PRIVATE KEY-----" +certId, err := testCli.PutCert("my.domain.com", &api.UserCertificate{ + CertName: "test", + ServerData: certData, + PrivateData: privateKey, + }, "ON") +fmt.Printf("certId:%s\n", certId) +fmt.Printf("err:%+v\n", err) +``` + +### 查询域名证书 GetCert + +> 查询某个域名的证书信息,如果证书不存在此方法会返回404错误。 + +```go +cli := client.GetDefaultClient() +detail, err := testCli.GetCert("my.domain.com") +fmt.Printf("detail:%v\n", detail) +// {"certId":"cert-8j774s9y3ww2","certName":"test","status":"IN_USE","certCommonName":"*.domain.com","certDNSNames":"*.domain.com,domain.com","certStartTime":"2021-03-18T07:14:33Z","certStopTime":"2021-06-16T07:14:33Z","certCreateTime":"2021-04-25T06:50:55Z","certUpdateTime":"2021-04-25T14:50:54Z"} +fmt.Printf("err:%+v\n", err) +``` + +### 删除域名证书 DeleteCert + +> 删除某个域名的证书,且关闭HTTPS。如果该域名原来没有证书,那么什么都不会做。 + +```go +cli := client.GetDefaultClient() +err := testCli.DeleteCert("my.domain.com") +fmt.Printf("err:%+v\n", err) +``` + +## 缓存管理接口 + +### 刷新缓存/查询刷新状态 Purge/GetPurgedStatus + +> 缓存清除方式有URL刷新、目录刷新除。URL刷新除是以文件或一个资源为单位进行缓存刷新。目录刷新除是以目录为单位,将目录下的所有文件进行缓存清除。 + +```go +cli := client.GetDefaultClient() + +// 刷除 +purgedId, err := cli.Purge([]api.PurgeTask{ + { + Url: "http://my.domain.com/path/to/purge/2.data", + }, + { + Url: "http://my.domain.com/path/to/purege/html/", + Type: "directory", + }, +}) +fmt.Printf("purgedId:%+v\n", purgedId) +fmt.Printf("err:%+v\n", err) + +// 根据任务ID查询刷除状态 +purgedStatus, err := cli.GetPurgedStatus(&api.CStatusQueryData{ + Id: string(purgedId), +}) + +fmt.Printf("purgedStatus:%+v\n", purgedStatus) +fmt.Printf("err:%+v\n", err) +``` + +示例中刷除了两类资源,第一种是刷除一个文件,第二种是刷除某个目录的所有的文件。根据Purge返回的task id去查询任务进度。`api.CStatusQueryData`是一个相对较复杂的结构,可以根据不同的条件查询,具体可以查看定义。 + +### 预热资源/查询预热状态 Prefetch/GetPrefetchStatus + +> URL预热是以文件为单位进行资源预热。 + +```go +cli := client.GetDefaultClient() + +prefetchId, err := cli.Prefetch([]api.PrefetchTask{ + { + Url: "http://my.domain.com/path/to/purge/1.data", + }, +}) +fmt.Printf("prefetchId:%+v\n", prefetchId) +fmt.Printf("err:%+v\n", err) + + +prefetchStatus, err := cli.GetPrefetchStatus(&api.CStatusQueryData{ + Id: string(prefetchId), +}) +fmt.Printf("prefetchStatus:%+v\n", prefetchStatus) +fmt.Printf("err:%+v\n", err) +``` + +### 查询刷新/预热限额 GetQuota + +```go +cli := client.GetDefaultClient() +quotaDetail, err := cli.GetQuota() +fmt.Printf("quotaDetail:%+v\n", quotaDetail) +fmt.Printf("err:%+v\n", err) +``` + +`quotaDetail`是`api.QuotaDetail`类型的对象,详细说明如下: + +| 字段 | 类型 | 说明 | +| --------- | ---- | ------------------------------- | +| DirRemain | int | 当日刷新目录限额余量。 | +| UrlRemain | int | 当日刷新(含预热)URL限额余量。 | +| DirQuota | int | 刷新目录限额总量。 | +| UrlQuota | int | 刷新(含预热)URL限额总量。 | + +## 动态加速接口 + +### 配置动态加速服务 EnableDsa/DisableDsa + +> 开启/关闭DSA是针对用户级别的开启关闭。 + +```go +cli := client.GetDefaultClient() + +// 开启DSA服务 +err := cli.EnableDsa() +fmt.Printf("err:%+v\n", err) + +// 关闭DSA服务 +err = cli.DisableDsa() +fmt.Printf("err:%+v\n", err) +``` + +### 查询动态加速域名列表 ListDsaDomains + +> 查询某个用户配置了DSA加速规则的域名列表。 + +```go +cli := client.GetDefaultClient() +dsaDomains, err := cli.ListDsaDomains() +fmt.Printf("dsaDomains:%+v\n", dsaDomains) +fmt.Printf("err:%+v\n", err) +``` + +`dsaDomains`是string数组,代表配置了DSA加速规则的域名。 + +### 配置域名动态加速规则 SetDsaConfig + +> 配置某个域名的DSA加速规则。 + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" + +// 配置DSA规则 +err := cli.SetDsaConfig(testDomain, &api.DSAConfig{ + Enabled: true, + Rules: []api.DSARule{ + { + Type: "suffix", + Value: ".mp4;.jpg;.php", + }, + { + Type: "path", + Value: "/path", + }, + { + Type: "exactPath", + Value: "/path/to/file.mp4", + }, + }, + Comment: "test", +}) +fmt.Printf("err:%+v\n", err) + +// 取消DSA规则 +err = cli.SetDsaConfig(testDomain, &api.DSAConfig{ + Enabled: false, +}) +fmt.Printf("err:%+v\n", err) +``` + +`api.DSAConfig`的详细说明如下: + +| 字段 | 类型 | 说明 | +| ----- | ------ | ------------------------------------------------------------ | +| Type | string | "suffix"表示文件类型,"path"表示动态路径,“exactPath“表示动态URL。 | +| Value | string | Type所指定类型的配置规则,多条规则使用";"分割。 | + + +## 日志接口 + +### 获取单个域名日志 GetDomainLog + +```go +cli := client.GetDefaultClient() +testDomain := "test_go_sdk.baidu.com" +endTime := "2019-09-01T07:12:00Z" +startTime := "2019-09-09T07:18:00Z" +domainLogs, err := cli.GetDomainLog(testDomain, api.TimeInterval{ + StartTime: startTime, + EndTime: endTime, +}) + +fmt.Printf("domainLogs:%+v\n", domainLogs) +fmt.Printf("err:%+v\n", err) +``` + +示例查询了单个域名在2019-09-01T07:12:00Z~2019-09-09T07:18:00Z之间的日志,`domainLogs`是`api.LogEntry`数组类型,LogEntry包含日志名,所属域名,下载路径,起始时间等信息。 + +### 获取多个域名日志 GetMultiDomainLog + +```go +cli := client.GetDefaultClient() +endTime := "2019-09-01T07:12:00Z" +startTime := "2019-09-09T07:18:00Z" + +domainLogs, err := cli.GetMultiDomainLog(&api.LogQueryData{ + TimeInterval: api.TimeInterval{ + StartTime: startTime, + EndTime: endTime, + }, + Type: 1, + Domains: []string{"1.baidu.com", "2.baidu.com"}, +}) + +fmt.Printf("domainLogs:%+v\n", domainLogs) +fmt.Printf("err:%+v\n", err) +``` + +示例查询["1.baidu.com", "2.baidu.com"]这些域名的日志,`domainLogs`和上一节GetDomainLog返回格式一致。 + +## 工具接口 + +### IP检测 GetIpInfo + +> 验证指定的IP是否属于百度开放云CDN服务节点。 + +```go +cli := client.GetDefaultClient() +ipStr := "1.2.3.4" +ipInfo, err := cli.GetIpInfo(ipStr, "describeIp") + +fmt.Printf("ipInfo:%+v\n", ipInfo) +fmt.Printf("err:%+v\n", err) +``` + +其中GetIpInfo的第二个参数只能为**"describeIp"**,ipInfo包含IP的详细信息,包括区域和ISP,如果不属于百度开放云CDN的节点,区域和ISP都为空。 + +### 批量IP检测接口 GetIpListInfo + +> 验证多个IP是否属于百度开放云CDN服务节点。 + +```go +cli := client.GetDefaultClient() +ipStr := "1.2.3.4" +ipsInfo, err := cli.GetIpListInfo([]string{"116.114.98.35", "59.24.3.174"}, "describeIp") + +fmt.Printf("ipsInfo:%+v\n", ipInfo) +fmt.Printf("err:%+v\n", err) +``` + +第二个参数只能为**"describeIp"** + +### 获取百度云CDN的回源节点信息 GetBackOriginNodes + +```go +cli := client.GetDefaultClient() +backOriginNodes, err := testCli.GetBackOriginNodes() + +fmt.Printf("backOriginNodes:%+v\n", backOriginNodes) +fmt.Printf("err:%+v\n", err) +``` + +## 统计查询 + +### 通用统计接口 + +> 通用统计接口在文档[统计接口](https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn)中有详细说明。 + +`api.QueryCondition`结构包含了最基本的查询条件,如下: + +| **参数** | **类型** | **说明** | +| --------- | -------- | ------------------------------------------------------------ | +| StartTime | string | 查询的时间范围起始值,默认为endTime前推24小时。格式为UTC时间字符串,如:"2019-09-01T07:12:00Z"。 | +| EndTime | string | 查询的时间范围结束值,默认为当前时间。时间跨度最长90天,时间格式和StartTime一样。 | +| Period | int | 查询结果的粒度,单位秒,可选值为60,300,3600,86400;默认为300,uv 默认3600(选60s的时间粒度时建议StartTime和EndTime区间跨度建议选择0.5到1h,否则可能会因为数据量太大无法正常返回) | +| KeyType | int | 标识key的内容,0=>域名,1=>用户id,2=>tag,默认0。 | +| Key | []string | 域名、用户Id或Tag。 | +| GroupBy | string | 返回结果聚合粒度,key => 根据key聚合,最后的key是`total`, 空 => 返回整体结果,每个key的每个时间段都对应一组数据。 | + +`metric`表示查询的统计数据类型,如下,接口具体返回结果的格式在对应的函数中可以看到。 + +| metric | 函数 | 接口类型 | 额外参数 | +| ---------------- | ------------------- | ---------------------------------- | ------------------------------------------------------------ | +| avg_speed | GetAvgSpeed | 查询平均速率 | 无。 | +| avg_speed_region | GetAvgSpeedByRegion | 客户端访问分布查询平均速率 | prov和isp。prov是查询的省份全拼,默认为空,查询全国数据。isp是查询的运营商代码,默认为空,查询所有运营商数据。 | +| pv | GetPv | pv/qps查询 | level,查询边缘节点或者中心节点pv。可填写"all"或"edge"或者"internal",默认为“all”。 | +| pv_src | GetSrcPv | 回源pv/qps查询 | 无。 | +| pv_region | GetPvByRegion | 查询pv/qps(分客户端访问分布) | prov和isp。prov是查询的省份全拼,默认为空,查询全国数据。isp是查询的运营商代码,默认为空,查询所有运营商数据。 | +| uv | GetUv | uv查询 | 无。 | +| flow | GetFlow | 查询流量、带宽 | level,查询边缘节点或者中心节点带宽。可填写"all"或"edge"或"internal",默认为"all"。 | +| flow_protocol | GetFlowByProtocol | 查询流量、带宽(分协议) | protocol,查询http或https的流量、带宽, 取值"http", "https"或者 "all",默认"all"。 | +| flow_region | GetFlowByRegion | 查询流量、带宽(分客户端访问分布) | prov和isp。prov是查询的省份全拼,默认为空,查询全国数据。isp是查询的运营商代码,默认为空,查询所有运营商数据。 | +| src_flow | GetSrcFlow | 查询回源流量、回源带宽 | 无。 | +| real_hit | GetRealHit | 字节命中率查询 | 无。 | +| pv_hit | GetPvHit | 请求命中率查询 | 无。 | +| httpcode | GetHttpCode | 状态码统计查询 | 无。 | +| src_httpcode | GetSrcHttpCode | 回源状态码查询 | 无。 | +| httpcode_region | GetHttpCodeByRegion | 状态码统计查询(分客户端访问分布) | prov和isp。prov是查询的省份全拼,默认为空,查询全国数据。isp是查询的运营商代码,默认为空,查询所有运营商数据。 | +| top_urls | GetTopNUrls | TopN urls | extra,查询指定http状态码的记录,默认值: ""。 | +| top_referers | GetTopNReferers | TopN referers | extra,查询指定http状态码的记录,默认值: ""。 | +| top_domains | GetTopNDomains | TopN domains | extra,查询指定http状态码的记录,默认值: ""。 | +| error | GetError | cdn错误码分类统计查询 | 无。 | + +### 计费统计接口 + +#### 查询域名或者tag的95带宽 GetPeak95Bandwidth + +查询条件: +| **参数** | **类型** | **说明** | +| --------- | -------- | ------------------------------------------------------------ | +| StartTime | string | 查询的时间范围起始值,默认为endTime前推24小时。格式为UTC时间字符串,如:"2019-09-01T07:12:00Z"。 | +| EndTime | string | 查询的时间范围结束值,默认为当前时间。时间跨度最长90天,时间格式和StartTime一样。 | +| domains | []string | 域名集合,和tags互斥存在,设置了domains请设置tags为nil | +| tags | []string | tag集合,和domains互斥存在,设置了tags请设置domains为nil | + +请求示例: + +```go +cli := client.GetDefaultClient() +peak95Time, peak95Band, err := cli.GetPeak95Bandwidth( + "2020-05-01T00:00:00Z", "2020-05-10T00:00:00Z", nil, []string{"www.test.com"}) + +fmt.Printf("peak95Time:%s\n", peak95Time) +fmt.Printf("peak95Band:%d\n", peak95Band) +fmt.Printf("err:%+v\n", err) +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/CERT.md b/bce-sdk-go/doc/CERT.md new file mode 100644 index 0000000..31e965c --- /dev/null +++ b/bce-sdk-go/doc/CERT.md @@ -0,0 +1,496 @@ +# 证书管理服务 + +# 概述 + +本文档主要介绍CERT GO SDK的使用。在使用本文档前,您需要先了解CERT的一些基本知识。若您还不了解CERT,可以参考[证书管理](https://cloud.baidu.com/doc/Reference/s/8jwvz26si)。 + +# 初始化 + +## 确认Endpoint + +目前使用CERT服务时,Endpoint统一使用`certificate.baidubce.com`, 支持http和https两种协议。 + +## 获取密钥 + +要使用百度云CERT,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CERT做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CERT Client + +CERT Client是CERT服务的客户端,为开发者与CERT服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CERT Client + +通过AK/SK方式访问CERT,用户可以参考如下代码新建一个CERT Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/cert" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个CERTClient + certClient, err := cert.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +### 使用STS创建CERT Client + +**申请STS token** + +CERT可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CERT,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CERT Client** + +申请好STS后,可将STS Token配置到CERT Client中,从而实现通过STS Token创建CERT Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CERT Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cert" //导入CERT服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CERT服务的Client对象,Endpoint使用默认值 + certClient, err := cert.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "certificate.baidubce.com") + if err != nil { + fmt.Println("create cert client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + certClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CERT Client时,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问CERT + +CERT支持HTTPS传输协议,您可以通过在创建CERT Client对象时指定的Endpoint中指明HTTPS的方式,在CERT GO SDK中使用HTTPS访问CERT服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +ENDPOINT := "https://certificate.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +certClient, _ := cert.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CERT Client + +如果用户需要配置CERT Client的一些细节的参数,可以在创建CERT Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CERT服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +//创建CERT Client对象 +AK, SK := , +ENDPOINT := "certificate.baidubce.com" +client, _ := cert.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +AK, SK := , +ENDPOINT := "certificate.baidubce.com" +client, _ := cert.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +AK, SK := , +ENDPOINT := "certificate.baidubce.com" +client, _ := cert.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CERT时,创建的CERT Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CERT Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# CERT管理 + +证书管理模块主要用于管理用户的SSL证书,方便用户录入以及查看SSL证书。 + +## 创建证书 + +使用以下代码可以创建证书。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +args := &cert.CreateCertArgs{ + // 指定证书名称, 必选 + CertName: "sdkcreateTest", + // 指定服务器证书的数据内容 (Base64编码), 必选 + CertServerData: testCertServerData, + // 指定证书的私钥数据内容 (Base64编码), 必选 + CertPrivateData: testCertPrivateData, + // 指定证书链数据内容 (Base64编码), 可选 + CertLinkData: certLinkData, +} +result, err := client.CreateCert(args) +if err != nil { + fmt.Printf("create cert error: %+v\n", err) + return +} + +fmt.Printf("create cert success: %+v\n", result) +``` + +> 注意: +> - 证书的名称: 长度限制为1-65个字符,以字母开头,只允许包含字母、数字、’-‘、’/’、’.’、’’,Java正则表达式` ^[a-zA-Z]a-zA-Z0-9\-/\.]{2,64}$` + +该请求可能存在的异常描述如下。 + +异常code | 说明 +--- | --- +CertExceedLimit (409) | 超过用户最大证书数 +UnmatchedPairParameterInvalidException (400) | 证书有效时间不包含当前时间 +PrivateKeyParameterInvalid (400) | 私钥解析异常 +CertificateParameterInvalid (400) | 证书解析异常 +CertChainParameterInvalid (400) | 证书链解析异常 +UnmatchedPairParameterInvalid (400) | 公钥私钥不匹配 + +## 修改证书名称 + +使用以下代码可以修改证书名称。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +args := &cert.UpdateCertNameArgs{ + CertName: "test-sdk-cert", +} +err = client.UpdateCertName(certId, args) +if err != nil { + fmt.Printf("update cert error: %+v\n", err) + return +} +fmt.Printf("update cert success\n") +``` + +该请求可能存在的异常描述如下。 + +异常code | 说明 +--- | --- +AccessDeniedException | 无权限访问 +ResourceNotFoundException | 证书不存在 + +## 查看证书列表 + +使用以下代码可以查看用户的证书列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +result, err := client.ListCerts() +if err != nil { + fmt.Printf("list certs error: %+v\n", err) + return +} + +// 查看证书列表的详细信息 +for _, c := range listResult.Certs { + fmt.Println("cert id: ", c.CertId) + fmt.Println("cert name: ", c.CertName) + fmt.Println("cert common name: ", c.CertCommonName) + fmt.Println("cert start time: ", c.CertStartTime) + fmt.Println("cert stop time: ", c.CertStopTime) + fmt.Println("cert create time: ", c.CertCreateTime) + fmt.Println("cert update time: ", c.CertUpdateTime) + fmt.Println("cert type: ", c.CertType) +} +``` + +## 获取证书信息(无证书公钥私钥) + +使用以下代码可以获取指定的证书信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +result, err := client.GetCertMeta(certId) +if err != nil { + fmt.Printf("get certs meta error: %+v\n", err) + return +} + +// 获取得到证书id +fmt.Println("cert id: ", result.CertId) +// 获取得到证书名称 +fmt.Println("cert name: ", result.CertName) +// 获取得到证书通用名称 +fmt.Println("cert common name: ", result.CertCommonName) +// 获取得到证书生效时间 +fmt.Println("cert start time: ", result.CertStartTime) +// 获取得到证书到期时间 +fmt.Println("cert stop time: ", result.CertStopTime) +// 获取得到证书创建时间 +fmt.Println("cert create time: ", result.CertCreateTime) +// 获取得到证书更新时间 +fmt.Println("cert update time: ", result.CertUpdateTime) +// 获取得到证书类型 +fmt.Println("cert type: ", result.CertType) +``` + +该请求可能存在的异常描述如下。 + +异常code | 说明 +--- | --- +AccessDeniedException | 无权限访问 +ResourceNotFoundException | 证书不存在 + +## 删除证书 + +使用以下代码可以删除指定的证书。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +if err := client.DeleteCert(certId); err != nil { + fmt.Printf("delete certs error: %+v\n", err) + return +} +fmt.Printf("delete certs success\n") +``` + +该请求可能存在的异常描述如下。 + +异常code | 说明 +--- | --- +OperationNotAllowedException | 证书使用中 +AccessDeniedException | 无权限访问 +ResourceNotFoundException | 证书不存在 + +## 替换证书 + +使用以下代码可以替换过期且不再使用中的证书。 +```go +// import "github.com/baidubce/bce-sdk-go/services/cert" + +args := &cert.UpdateCertDataArgs{ + // 指定要替换的证书名称 + CertName: "test-sdk-cert", + // 指定服务器证书的数据内容 (Base64编码) + CertServerData: testUpdateCertServerData, + // 指定证书的私钥数据内容 (Base64编码) + CertPrivateData: testUpdateCertPrivateData, + // 指定证书链数据内容 (Base64编码) + CertLinkData: certLinkData, +} +if err := client.UpdateCertData(createResult.CertId, args); err != nil { + fmt.Printf("update cert data error: %+v\n", err) + return +} +fmt.Printf("update cert data success\n") +``` + +> 注意: 使用该api替换证书后,证书的id保持不变。 + +该请求可能存在的异常描述如下。 + +异常code | 说明 +--- | --- +OperationNotAllowedException(409) | 证书使用中或者证书 +AccessDeniedException(403) | 证书非本用户或子用户无该证书运维权限 +ResourceNotFoundException(404)| 无证书 +CertExceedLimit (409) | 超过用户最大证书数 +UnmatchedPairParameterInvalidException (400) | 证书有效时间不包含当前时间 +PrivateKeyParameterInvalid (400) | 私钥解析异常 +CertificateParameterInvalid (400) | 证书解析异常 +CertChainParameterInvalid (400) | 证书链解析异常 +UnmatchedPairParameterInvalid (400) | 公钥私钥不匹配 + + +# 错误处理 + +GO语言以error类型标识错误,CERT支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CERT服务返回的错误 + +用户使用SDK调用CERT相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// certClient 为已创建的CERT Client对象 +result, err := client.ListCerts() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CERT发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当CERT服务端出现异常时,CERT服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +CERT GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +CERT GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the CERT go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the CERT go sdk") +``` + + +# 版本变更记录 + +## v0.9.5 [2019-09-24] + +首次发布: + + - 支持创建证书、修改证书名称、查看证书列表、获取证书信息(无证书公钥私钥)、删除证书、替换证书接口。 \ No newline at end of file diff --git a/bce-sdk-go/doc/CFC.md b/bce-sdk-go/doc/CFC.md new file mode 100644 index 0000000..231135f --- /dev/null +++ b/bce-sdk-go/doc/CFC.md @@ -0,0 +1,841 @@ +# CFC GO SDK文档 + +# 初始化 + +## 确认Endpoint + +目前支持“华北-北京”、“华南-广州” 两个区域。北京区域:`http://cfc.bj.baidubce.com`,广州区域:`http://cfc.gz.baidubce.com` 对应信息为: + +访问区域 | 对应Endpoint +---|--- +BJ | cfc.bj.baidubce.com +GZ | cfc.gz.baidubce.com + +## 获取密钥 + +要使用百度云BOS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问BOS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## CFC Client + +CFC Client是CFC服务的客户端,为开发者与CFC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CFC Client + +通过AK/SK方式访问CFC,用户可以参考如下代码新建一个CFC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/cfc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个cfcClient + cfcClient, err := cfc.NewClient(AK, SK, ENDPOINT) +} +``` + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://cfc.bj.baidubce.com`。 + +### 使用STS创建CFC Client + +**申请STS token** + +CFC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CFC,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建CFC Client** + +申请好STS后,可将STS Token配置到CFC Client中,从而实现通过STS Token创建CFC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CFC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cfc" //导入CFC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CFC服务的Client对象,Endpoint使用默认值 + cfcClient, err := cfc.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create cfc client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + cfcClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CFC Client时,无论对应CFC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问CFC + +CFC支持HTTPS传输协议,您可以通过在创建CFC Client对象时指定的Endpoint中指明HTTPS的方式,在CFC GO SDK中使用HTTPS访问CFC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfc" + +ENDPOINT := "https://cfc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +cfcClient, _ := cfc.NewClient(AK, SK, ENDPOINT) +``` + +## 配置cfc Client + +如果用户需要配置CFC Client的一些细节的参数,可以在创建CFC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CFC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfc" + +//创建CFC Client对象 +AK, SK := , +ENDPOINT := "cfc.bj.baidubce.com" +client, _ := cfc.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfc" + +AK, SK := , +ENDPOINT := "cfc.bj.baidubce.com" +client, _ := cfc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfc" + +AK, SK := , +ENDPOINT := "cfc.bj.baidubce.com" +client, _ := cfc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CFC时,创建的CFC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CFC Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +## 函数调用 + +使用以下代码可以调用执行一个指定的CFC函数 +```go +args := &api.InvocationsArgs{ + FunctionName: "sdk-create", + InvocationType: api.InvocationTypeRequestResponse, + Payload: nil, +} + +// 若想执行特定版本的函数,可以设置 +args.Qualifier = "1" + +result, err := client.Invocations(args) +if err != nil { + fmt.Println("invocation function failed:", err) +} else { + fmt.Println("invocation function success: ", result) +} +``` + +## 函数操作 + +### 创建函数 + +使用以下代码可以创建一个CFC函数 +```go +arge := &api.CreateFunctionArgs{ + // 配置函数的代码,需要上传代码的zip压缩包 + Code: &api.CodeFile{ZipFile: zipFile}, + // 函数名称,每个用户的函数名称不可重复,不可修改 + FunctionName: "sdk-create", + // 函数调用的入口函数 + Handler: "index.handler", + // 函数的runtime + Runtime: "nodejs8.5", + // 函数运行的内存大小,单位mb,必须是128的整数倍,最大可选1024 + MemorySize: 256, + // 函数执行超时时间,可选1-300s + Timeout: 3, + // 函数描述信息 + Description: "sdk create", + // 函数日志存放方式,可选bos,表示函数执行日志存放在bos中 + LogType: "bos", + // 若LogType配置为bos,此参数设置函数执行日志在bos中的存储地址 + LogBosDir: "bos://ashjfdklsfhlk/", +}) + +// 若要配置从bos bucket中上传函数代码,可以如下设置 +// 这两个参数不能和args.Code.ZipFile同时设置 +args.Code.BosBucket = "bucketName" +args.Code.BosObject = "objectKey" + +// 若要直接发布函数,可以设置 +args.Code.Publish = true + +// 若要配置函数访问VPC网络,可以如下设置 +args.VpcConfig = &api.VpcConfig{ + SubnetIds: []string{"subnet_id1"}, + SecurityGroupIds: []string{"security_group_id1"}, +} + +// 若要配置环境变量,可以如下设置 +args.Environment = &api.Environment{ + Variables: map[string]string{ + "key": "value", + }, +}, + +result, err := client.CreateFunction(args) +if err != nil { + fmt.Println("create function failed:", err) +} else { + fmt.Println("create function success: ", result) +} +``` + +### 函数列表 + +使用以下代码可以获取CFC函数的列表 +```go +args := &api.ListFunctionArgs{} + +// 若想查询指定版本1的函数,可以如下设置 +args.FunctionVersion = "1" + +result, err := client.ListFunctions(args) +if err != nil { + fmt.Println("list function failed:", err) +} else { + fmt.Println("list function success: ", result) +} +``` + +### 函数信息 + +使用以下代码可以获取特定函数的信息 +```go +args := &api.GetFunctionArgs{ + FunctionName: "functionName" +} +result, err := client.GetFunction(args) +if err != nil { + fmt.Println("get function failed:", err) +} else { + fmt.Println("get function success: ", result) +} +``` + +### 删除函数 + +使用以下代码可以删除一个特定的CFC函数 +```go +args := &api.DeleteFunctionArgs{ + FunctionName: "sdk-create", +} + +// 若想删除函数的某个版本,可以设置 +args.Qualifier = "1" + +err := client.DeleteFunction(args) +if err != nil { + fmt.Println("delete function failed:", err) +} +``` + +### 更新函数代码 + +使用以下代码可以更新特定CFC函数的代码 +```go +args := &api.UpdateFunctionCodeArgs{ + FunctionName: "sdk-creat" + ZipFile: []byte(functionZipCode) +} + +// 若要配置从bos bucket中上传函数代码,可以如下设置 +// 这两个参数不能和args.ZipFile同时设置 +args.BosBucket = "bucketName" +args.BosObject = "objectKey" + +// 若要直接发布函数,可以设置 +args.Publish = true + +result, err := client.UpdateFunctionCode(args) +if err != nil { + fmt.Println("update function code failed:", err) +} else { + fmt.Println("update function code success: ", result) +} +``` + +### 获取函数配置 + +使用以下代码可以获取特定CFC函数的配置 +```go +args := &api.GetFunctionConfigurationArgs{ + FunctionName: "sdk-create", +} + +// 若想查询特定版本的函数的配置,可以设置 +args.Qualifier = functionBrn + +if err != nil { + fmt.Println("get function configure failed:", err) +} else { + fmt.Println("get function configure success: ", result) +} +``` + +### 更新函数配置 + +使用以下代码可以更新特定CFC函数的配置 +```go +args := &api.UpdateFunctionConfigurationArgs{ + FunctionName: "sdk-create", + Timeout: 20, + Description: "sdk update", + Runtime: "nodejs8.5", + MemorySize: &memorySize, + Environment: &api.Environment{ + Variables: map[string]string{ + "name": "Test", + }, + }, +}) + +result, err := client.UpdateFunctionConfiguration(args) +if err != nil { + fmt.Println("update function configure failed:", err) +} else { + fmt.Println("update function configure success: ", result) +} +``` + +### 设置函数预留并发度 + +使用以下代码可以设置和更新特定CFC函数的预留并发度 +```go +args := &api.ReservedConcurrentExecutionsArgs{ + FunctionName: "sdk-create", + // 预留并发度会由本函数的所有版本共享,最高能设置90 + ReservedConcurrentExecutions: 10, +}) + +err := client.SetReservedConcurrentExecutions(args) +if err != nil { + fmt.Println("set function reserved concurrent executions failed:", err) +} +``` + +### 删除函数预留并发度设置 + +使用以下代码可以删除特定CFC函数的预留并发度设置 +```go +args := &api.DeleteReservedConcurrentExecutionsArgs{ + FunctionName: "sdk-create", +}) + +err := client.DeleteReservedConcurrentExecutions(args) +if err != nil { + fmt.Println("delete function reserved concurrent executions failed:", err) +} +``` + +## 版本操作 + +### 获取函数版本列表 + +使用以下代码可以获取函数版本列表 +```go +args := &api.ListVersionsByFunctionArgs{ + FunctionName: "sdk-create", +} + +result, err := client.ListVersionsByFunction(args) +if err != nil { + fmt.Println("get function version failed:", err) +} else { + fmt.Println("get function version success: ", result) +} +``` + +### 发布版本 + +使用以下代码可以为函数发布一个版本 +```go +args := &api.PublishVersionArgs{ + FunctionName: "sdk-create", +} + +// 若想添加版本描述,可以设置 +args.Descirption = "publish description" + +// 若想对版本的部署包进行sha256验证,可以设置 +args.CodeSha256 = "codeSha256" + +result, err := client.PublishVersion(args) +if err != nil { + fmt.Println("publish function version failed:", err) +} else { + fmt.Println("publish function version success: ", result) +} +``` + +## 别名操作 + +### 获取别名列表 + +使用以下代码可以获取函数的别名列表 +```go +args := &api.ListAliasesArgs{ + FunctionName: "sdk-create", +} + +// 若想获取特定函数版本的别名,可以设置 +args.FunctionVersion = "1" + +result, err := client.ListAliases(args) +if err != nil { + fmt.Println("list function alias failed:", err) +} else { + fmt.Println("list function alias success: ", result) +} +``` + + +### 创建别名 + +使用以下代码可以为特定函数版本创建一个别名 +```go +args := &api.CreateAliasArgs{ + FunctionName: "sdk-create", + Name: "alias-create", +} + +// 若要将别名绑定到特定函数版本,可以设置 +args.FunctionVersion = "1" + +// 若要设置别名标书,可以设置 +args.Description = "alias description" + + +result, err := client.CreateAlias(args) +if err != nil { + fmt.Println("create function alias failed:", err) +} else { + fmt.Println("create function alias success: ", result) +} +``` + +### 获取别名信息 + +使用以下代码可以获取一个特定函数的别名的信息 +```go +args := &api.GetAliasArgs{ + FunctionName: "sdk-create", + AliasName: "alias-create", +} + +result, err := client.GetAlias(args) +if err != nil { + fmt.Println("get function alias failed:", err) +} else { + fmt.Println("get function alias success: ", result) +} +``` + +### 更新别名 + +使用以下代码可以更新一个函数的别名 +```go +args := &api.UpdateAliasArgs{ + FunctionName: "sdk-create", + AliasName: "alias-create", + Description: "test alias", +} + +// 若要修改别名绑定的函数版本,可以设置 +args.FunctionVersion = "$LATEST" + +result, err := client.UpdateAlias(args) +if err != nil { + fmt.Println("update function alias failed:", err) +} else { + fmt.Println("update function alias success: ", result) +} +``` + +### 删除别名 + +使用以下代码可以删除一个函数的别名 +```go +args := &api.DeleteAliasArgs{ + FunctionName: "sdk-create", + AliasName: "alias-create", +} + +err := client.DeleteAlias(args) +if err != nil { + fmt.Println("delete function alias failed:", err) +} +``` + +## 触发器操作 + +### 获取触发器列表 + +使用以下代码可以获取一个触发器的列表 +```go +args := &api.ListTriggersArgs{ + FunctionBrn: "functionBrn", +} + +// 默认不返回cfc-edge触发器,若想查询所有的触发器,可以设置 +args.ScopeType = "all" + +result, err := client.ListTriggers(args) +if err != nil { + fmt.Println("get function trigger failed:", err) +} else { + fmt.Println("get function trigger success: ", result) +} +``` + +### 创建触发器 + +使用以下代码可以创建一个特定的触发器并绑定 +```go +args := &api.CreateTriggerArgs{ + Target: "functionBrn", + Source: api.SourceTypeCrontab, + // 创建crontab触发器所需的数据 + Data: &api.CrontabTriggerData{ + Name: "sdkName", + Brn: "functionBrn", + ScheduleExpression: "cron(0 10 * * ?)", + Enabled: "Disabled", + }, +} + +result, err := client.CreateTrigger(args) +if err != nil { + fmt.Println("create function trigger failed:", err) +} else { + fmt.Println("create function trigger success: ", result) +} +``` + +> **提示:** +> 1. 不同类型的触发器,其Data字段所需内容不同,具体可以参考文档[触发器配置](https://cloud.baidu.com/doc/CFC/s/Kjwvz47o9#relationconfiguration) + +### 更新触发器 + +使用以下代码可以更新一个函数的触发器 +```go +args := &api.UpdateTriggerArgs{ + RelationId: RelationId, + Target: functionBRN, + Source: api.SourceTypeHTTP, + Data: &api.HttpTriggerData{ + ResourcePath: fmt.Sprintf("tr99-%s", time.Now().Format("2006-01-02T150405")), + Method: "GET", + AuthType: "anonymous", + }, +} + +result, err := client.UpdateTrigger(args) +if err != nil { + fmt.Println("update function trigger failed:", err) +} else { + fmt.Println("update function trigger success: ", result) +} +``` + +### 删除触发器 + +使用以下代码可以删除一个触发器 +```go +args := &api.DeleteTriggerArgs{ + RelationId: RelationId, + Target: functionBRN, + Source: api.SourceTypeHTTP, +} + +err := client.DeleteTrigger(args) +if err != nil { + fmt.Println("delete function trigger failed:", err) +} +``` +## 消息触发器EventSourceMappings操作 + +### 获取消息触发器EventSourceMappings列表 + +使用以下代码可以获取某个函数的消息触发器EventSourceMappings的列表 +```go +args := &api.ListEventSourceArgs{ + FunctionName: "functionBrn", + Marker: 0, + MaxItems: 100, +} + +// 返回指定函数的消息触发器列表 +result, err := client.ListEventSource(args) +if err != nil { + fmt.Println("list function event source mappings failed:", err) +} else { + fmt.Println("list function event source mappings success: ", result) +} +``` +### 获取某个消息触发器EventSourceMappings配置信息 + +使用以下代码可以获取某个消息触发器EventSourceMapping配置信息 +```go +args := &api.GetEventSourceArgs{ + UUID: "uuid", +} + +// 返回指定函数的消息触发器列表 +result, err := client.GetEventSource(args) +if err != nil { + fmt.Println("get function event source mapping info failed:", err) +} else { + fmt.Println("get function event source mapping info success: ", result) +} +``` + +### 创建消息触发器EventSourceMapping + +使用以下代码可以创建一个特定的消息触发器Event Source Mapping并绑定函数 +```go +FunctionBRN = "" +enabled := true +args := &api.CreateEventSourceArgs{ + Enabled: &enabled, + BatchSize: 3, + Type: api.TypeEventSourceDatahubTopic, + FunctionName: FunctionBRN, + DatahubConfig: api.DatahubConfig{ + MetaHostEndpoint: "endpoint", + MetaHostPort: 2181, + ClusterName: "clusterName", + PipeName: "pipeName", + PipeletNum: 1, + StartPoint: -1, + AclName: "aclName", + AclPassword: "aclPaaaword", + }, + } + +result, err := client.CreateEventSource(args) +if err != nil { + fmt.Println("create function event source mapping failed:", err) +} else { + fmt.Println("create function event source mapping success: ", result) +} +``` + +> **提示:** +> 1. 不同类型的消息触发器,其Type字段不同,目前只支持api.TypeEventSourceBms和api.TypeEventSourceDatahubTopic +> 2. 不同类型的触发器,其Data字段所需内容不同,具体可以参考文档[触发器配置](https://cloud.baidu.com/doc/CFC/s/Kjwvz47o9#relationconfiguration) + +### 更新消息触发器EventSourceMapping + +使用以下代码可以更新一个函数的消息触发器EventSourceMapping配置 +```go +unEnabled := false +FunctionBRN = "" +args := &api.UpdateEventSourceArgs{ + UUID: "uuid", + FuncEventSource: api.FuncEventSource{ + Enabled: &unEnabled, + BatchSize: 3, + Type: api.TypeEventSourceDatahubTopic, + FunctionName: FunctionBRN, + DatahubConfig: api.DatahubConfig{ + MetaHostEndpoint:"endpoint", + MetaHostPort: 80, + ClusterName: "clusterName", + PipeName:"pipeName", + PipeletNum:1, + StartPoint:-1, + AclName: "aclName", + AclPassword: "aclPassword", + }, + }, +} + +result, err := client.UpdateEventSource(args) +if err != nil { + fmt.Println("update function event source failed:", err) +} else { + fmt.Println("update function event source success: ", result) +} +``` + +### 删除一个消息触发器 + +使用以下代码可以删除一个触发器 +```go +args := &api.DeleteEventSourceArgs{ + UUID: "uuid", +} + +err := client.DeleteEventSource(args) +if err != nil { + fmt.Println("delete function event source mapping failed:", err) +} +``` + +# 错误处理 + +GO语言以error类型标识错误,CFC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +CFCClientError | 用户操作产生的错误 +BceServiceError | CFC服务返回的错误 + +用户使用SDK调用CFC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// cfcClient 为已创建的CFC Client对象 +args := &api.GetFunctionArgs{ + FunctionName: "functionName" +} +result, err := cfcClient.GetFunction(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get function detail success: ", result) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CFC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当CFC服务端出现异常时,CFC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[CFC错误返回](https://cloud.baidu.com/doc/CFC/s/Djwvz4cwc) + +# 版本变更记录 + +## v0.9.1 [2019-09-26] +首次发布: + + - 执行函数 + - 创建、查看、列表、删除函数,更新函数代码,更新、获取函数配置 + - 设置、删除函数预留并发度 + - 列表、创建函数版本 + - 列表、创建、获取、更新、删除别名 + - 获取、创建、更新、删除触发器 +## v0.9.2 [2021-09-06] +- ## v0.9.2 [2021-09-06] +- 列表、获取、创建、更新、删除消息触发器,包括百度消息bms触发器及Datahub触发器 + diff --git a/bce-sdk-go/doc/CFS.md b/bce-sdk-go/doc/CFS.md new file mode 100644 index 0000000..587096c --- /dev/null +++ b/bce-sdk-go/doc/CFS.md @@ -0,0 +1,427 @@ +# CFS服务 + +# 概述 + +本文档主要介绍CFS GO SDK的使用。在使用本文档前,您需要先了解普通型CFS的一些基本知识。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[CFS访问域名](https://cloud.baidu.com/doc/CFS/s/pjwvy1siw)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云CFS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CFS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CFS Client + +CFS Client是CFS控制面服务的客户端,为开发者与CFS控制面服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CFS Client + +通过AK/SK方式访问CFS,用户可以参考如下代码新建一个CFS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/cfs" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个CFSClient + cfsClient, err := cfs.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/CFS/index.html)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为CFS的控制面服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`cfs.bj.baidubce.com`。 + +### 使用STS创建CFS Client + +**申请STS token** + +CFS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CFS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建CFS Client** + +申请好STS后,可将STS Token配置到CFS Client中,从而实现通过STS Token创建CFS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CFS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cfs" //导入CFS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CFS控制面服务的Client对象,Endpoint使用默认值 + cfsClient, err := cfs.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create cfs client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + cfsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CFS Client时,无论对应CFS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问CFS + +CFS支持HTTPS传输协议,您可以通过在创建CFS Client对象时指定的Endpoint中指明HTTPS的方式,在CFS GO SDK中使用HTTPS访问CFS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfs" + +ENDPOINT := "https://cfs.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +cfsClient, _ := cfs.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CFS Client + +如果用户需要配置CFS Client的一些细节的参数,可以在创建CFS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CFS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfs" + +//创建CFS Client对象 +AK, SK := , +ENDPOINT := "cfs.bj.baidubce.com +client, _ := cfs.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfs" + +AK, SK := , +ENDPOINT := "cfs.bj.baidubce.com" +client, _ := cfs.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfs" + +AK, SK := , +ENDPOINT := "cfs.bj.baidubce.com" +client, _ := cfs.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CFS时,创建的CFS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CFS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +CFS通过对标准NFS协议的支持,兼容POSIX接口,为云上的虚机、容器资源提供了跨操作系统的文件存储及共享能力。同时,百度智能云CFS提供简单、易操作的对外接口,并支持按实际使用量计费(公测期间免费),免去部署、维护费用的同时,最大化提升您的业务效率 + +## 文件系统管理 + +### 创建文件系统实例 + +通过以下代码,可以创建一个CFS文件系统实例,返回对应的文件实例ID + +```go +args := &cfs.CreateFSArgs{ + ClientToken: "be31b98c-5e41-4838-9830-9be700de5a20", + // 设置实例名称 + Name: "sdkCFS", + // 设置实例所属vpc + VpcId: vpcId, + // 设置实例所属协议类型:1.nfs 2.smb + Protocol: protocol, + // 设置实例所属可用区 + Zone: zone, +} + + +result, err := client.CreateFS(args) +if err != nil { + fmt.Println("create cfs failed:", err) +} else { + fmt.Println("create cfs success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[CreateFileSystem创建文件系统](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#createfilesystem%E5%88%9B%E5%BB%BA%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F) + +### 更新实例 + +通过以下代码,可以更新一个CFS实例的配置信息,如实例名称 + +```go +args := &cfs.UpdateFSArgs{ + // 实例ID + FSID: "cfs-xxxxx" + Name: "testSdk", +} +err := client.UpdateFS(args) +if err != nil { + fmt.Println("update cfs failed:", err) +} else { + fmt.Println("update cfs success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[UpdateFileSystem更新文件系统](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#updatefilesystem%E6%9B%B4%E6%96%B0%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F) + +### 查询已有的实例 + +通过以下代码,可以查询用户账户下所有CFS的信息 + +```go +args := &cfs.DescribeFSArgs{} + +// 支持按fsId、userId,匹配规则支持部分包含(不支持正则) +args.FSID = cfsId +args.UserId = userId + + +result, err := client.DescribeFS(args) +if err != nil { + fmt.Println("list all cfs failed:", err) +} else { + fmt.Println("list all cfs success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[DescribeFileSystem查询文件系统](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#describefilesystem%E6%9F%A5%E8%AF%A2%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F) + + +### 释放实例 + +通过以下代码,可以释放指定CFS实例,被释放的CFS无法找回 + +```go +args := &cfs.DropFSArgs{} +args.FSID = cfsId + +err := client.DropFS(args) +if err != nil { + fmt.Println("delete cfs failed:", err) +} else { + fmt.Println("delete cfs success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[DropFileSystem释放文件系统实例](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#dropfilesystem%E9%87%8A%E6%94%BE%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AE%9E%E4%BE%8B) + + +## 挂载点管理 + +### 创建挂载点 + +通过以下代码,在指定CFS实例下,创建一个文件系统的挂载点,返回domain + +```go +args := &cfs.CreateMountTargetArgs{ + // 所属文件系统实例ID + FSID: cfsId, + // 所属子网ID + SubnetId: subnetId, + // 所属vpc短ID + VpcID: vpcId, +} +err := client.CreateMountTarget(args) +if err != nil { + fmt.Println("create Mount Target failed:", err) +} else { + fmt.Println("create Mount Target success") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[CreateMountTarget创建挂载点](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#createmounttarget%E5%88%9B%E5%BB%BA%E6%8C%82%E8%BD%BD%E7%82%B9) + + +### 查询挂载点 + +通过以下代码,查询指定CFS实例下下所有挂载点信息,支持按挂载点匹配查询,结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定 + +```go +args := &cfs.DescribeMountTargetArgs{ + // 要查询的文件系统实例id + FSID: cfsid, +} +result, err := client.DescribeMountTarget(args) +if err != nil { + fmt.Println("describe Mount Target failed:", err) +} else { + fmt.Println("describe Mount Target success: ", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[DescribeMountTarget描述挂载点](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#describemounttarget%E6%8F%8F%E8%BF%B0%E6%8C%82%E8%BD%BD%E7%82%B9) + + +### 删除挂载点 + +通过以下代码,释放指定CFS实例下的挂载点 + +```go +args := &cfs.DropMountTargetArgs{ + // 要删除的文件系统实例ID + FSID: cfsId, + MountId: mountId, +} +err := client.DropMountTarget(args) +if err != nil { + fmt.Println("delete Mount Target failed:", err) +} else { + fmt.Println("delete Mount Target success: ") +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考CFS API 文档[DeleteMountTarget删除挂载点](https://cloud.baidu.com/doc/CFS/s/mjwvy1reo#deletemounttarget%E5%88%A0%E9%99%A4%E6%8C%82%E8%BD%BD%E7%82%B9) + + +# 错误处理 + +GO语言以error类型标识错误,CFS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CFS服务返回的错误 + +用户使用SDK调用CFS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// cfsClient 为已创建的CFS Client对象 +describeArgs := &DescribeFSArgs { + FSID: CFS_ID, +} +cfsDetail, err := cfsClient.DescribeFS(describeArgs) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get cfs detail success: ", cfsDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CFS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当CFS服务端出现异常时,CFS服务端会返回给用户相应的错误信息,以便定位问题 + +# 版本变更记录 + +## v0.9.107 [2022-02-28] + +首次发布: + + - 创建、查看、列表、更新、删除CFS实例 + - 创建、查看、列表、更新、删除挂载点 diff --git a/bce-sdk-go/doc/CFW.md b/bce-sdk-go/doc/CFW.md new file mode 100644 index 0000000..c7dcd37 --- /dev/null +++ b/bce-sdk-go/doc/CFW.md @@ -0,0 +1,492 @@ +# CFW服务 + +# 概述 + +本文档主要介绍CFW GO SDK的使用。在使用本文档前,您需要先了解CFW的一些基本知识,并已开通了CFW服务。若您还不了解CFW,可以参考[产品描述](https://cloud.baidu.com/doc/CFW/s/ql0lu2yzg )和[操作指南](https://cloud.baidu.com/doc/CFW/s/al0m5cuka) 。 + +# 初始化 + +## 确认Endpoint + +云防火墙 API 的服务域名为:cfw.baidubce.com + +API支持HTTP和HTTPS两种调用方式。为了提升数据的安全性,建议通过HTTPS调用。 + + +## 获取密钥 + +要使用百度云CFW,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CFW做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CFW Client + +CFW Client是CFW服务的客户端,为开发者与CFW服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CFW Client + +通过AK/SK方式访问CFW,用户可以参考如下代码新建一个CFW Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/cfw" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个CFWClient + cfwClient, err := cfw.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为CFW的服务地址。 + +### 使用STS创建CFW Client + +**申请STS token** + +CFW可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CFW,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CFW Client** + +申请好STS后,可将STS Token配置到CFW Client中,从而实现通过STS Token创建CFW Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CFW Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/cfw" //导入CFW服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CFW服务的Client对象,Endpoint使用默认值 + cfwClient, err := cfw.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "cfw.baidubce.com") + if err != nil { + fmt.Println("create cfw client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + cfwClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CFW Client时,STS的Endpoint需配置为http://sts.bj.baidubce.com。 + +# 配置HTTPS协议访问CFW + +CFW支持HTTPS传输协议,您可以通过在创建CFW Client对象时指定的Endpoint中指明HTTPS的方式,在CFW GO SDK中使用HTTPS访问CFW服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfw" + +ENDPOINT := "https://cfw.baidubce.com " //指明使用HTTPS协议 +AK, SK := , +cfwClient, _ := cfw.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CFW Client + +如果用户需要配置CFW Client的一些细节的参数,可以在创建CFW Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CFW服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfw" + +//创建CFW Client对象 +AK, SK := , +ENDPOINT := "cfw.baidubce.com" +client, _ := cfw.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfw" + +AK, SK := , +ENDPOINT := "cfw.baidubce.com" +client, _ := cfw.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/cfw" + +AK, SK := , +ENDPOINT := "cfw.baidubce.com" +client, _ := cfw.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CFW时,创建的CFW Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CFW Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 创建CFW策略 + +```go +args := &CreateCfwRequest{ + Name: "hzb_1", + Description: "desc", + CfwRules: []CreateRule{ + { + IpVersion: 4, + Priority: 4, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.4", + DestAddress: "192.168.0.5", + SourcePort: "80", + DestPort: "88", + Action: "allow", + }, + }, + } +result, err := CfwClient.CreateCfw(args) +ExpectEqual(t.Errorf, nil, err) +CfwId := result.CfwId +log.Debug(CfwId) +``` + +## 查询CFW策略列表 + +```go +args := &ListCfwArgs{ + Marker: "cfw-ius2qz1btkvm", +} +result, err := CfwClient.ListCfw(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询指定CFW策略 + +```go +result, err := CfwClient.GetCfw("cfw-ius2qz1btkvm") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 修改指定CFW策略 + +```go +args := &UpdateCfwRequest{ + Name: "hzb_1_1", + Description: "test", +} +err := CfwClient.UpdateCfw("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除指定CFW策略 + +```go +err := CfwClient.DeleteCfw("cfw-g0hs3v1scskq") +ExpectEqual(t.Errorf, nil, err) +``` + +## 批量创建CFW规则 + +```go +args := &CreateCfwRuleRequest{ + CfwRules: []CreateRule{ + { + IpVersion: 4, + Priority: 5, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.3", + DestAddress: "192.168.0.4", + SourcePort: "80", + DestPort: "88", + Action: "allow", + }, + }, +} +err := CfwClient.CreateCfwRule("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 修改指定CFW规则 + +```go +args := &UpdateCfwRuleRequest{ + IpVersion: 4, + Priority: 2, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.1", + DestAddress: "192.168.0.2", + SourcePort: "80", + DestPort: "88", + Action: "allow", +} +err := CfwClient.UpdateCfwRule("cfw-ius2qz1btkvm", "cfwr-9rjvf28r48s3", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 批量删除CFW规则 + +```go +args := &DeleteCfwRuleRequest{ + CfwRuleIds: []string{ + "cfwr-9rjvf28r48s3", + }, +} +err := CfwClient.DeleteCfwRule("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 防护边界实例列表 + +```go +args := &ListInstanceRequest{ + InstanceType: "eip", + MaxKeys: 2, + Marker: "ip-46d82b8b", +} +result, err := CfwClient.ListInstance(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 批量实例绑定CFW + +```go +args := &BindCfwRequest{ + InstanceType: "eip", + Instances: []CfwBind{ + { + Region: "bj", + InstanceId: "ip-06583aaf", + }, + { + Region: "bj", + InstanceId: "ip-0ad38b01", + }, + }, +} +err := CfwClient.BindCfw("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 批量实例解绑CFW + +```go +args := &UnbindCfwRequest{ + InstanceType: "eip", + Instances: []CfwBind{ + { + Region: "bj", + InstanceId: "ip-06583aaf", + }, + { + Region: "bj", + InstanceId: "ip-0ad38b01", + }, + }, +} +err := CfwClient.UnbindCfw("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 实例开启CFW保护 + +```go +args := &EnableCfwRequest{ + InstanceId: "ip-0ad38b01", +} +err := CfwClient.EnableCfw("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 实例关闭CFW保护 + +```go +args := &DisableCfwRequest{ + InstanceId: "ip-0ad38b01", +} +err := CfwClient.DisableCfw("cfw-ius2qz1btkvm", args) +ExpectEqual(t.Errorf, nil, err) +``` + +# 错误处理 + +GO语言以error类型标识错误,CFW支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CFW服务返回的错误 + +用户使用SDK调用CFW相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +args := &ListInstanceRequest{ + InstanceType: "eip", +} +result, err := CfwClient.ListInstance(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CFW发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当CFW服务端出现异常时,CFW服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[CFW错误码](https://cloud.baidu.com/doc/CFW/s/Cl13c6svf) + +## SDK日志 + +CFW GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +CFW GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the CFW go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the CFW go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/CSN.md b/bce-sdk-go/doc/CSN.md new file mode 100644 index 0000000..2281c86 --- /dev/null +++ b/bce-sdk-go/doc/CSN.md @@ -0,0 +1,667 @@ +# CSN服务 + +# 概述 + +本文档主要介绍CSN GO SDK的使用。在使用本文档前,您需要先了解CSN的一些基本知识,并已开通了CSN服务。若您还不了解CSN,可以参考[产品描述](https://cloud.baidu.com/doc/CSN/s/ukk7yyait )和[操作指南](https://cloud.baidu.com/doc/CSN/s/Uklrk4o7b) 。 + +# 初始化 + +## 确认Endpoint + +云智能网 API 的服务域名为:csn.baidubce.com + +API支持HTTP和HTTPS两种调用方式。为了提升数据的安全性,建议通过HTTPS调用。 + + +## 获取密钥 + +要使用百度云CSN,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CSN做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CSN Client + +CSN Client是CSN服务的客户端,为开发者与CSN服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CSN Client + +通过AK/SK方式访问CSN,用户可以参考如下代码新建一个CSN Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/csn" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个CSNClient + csnClient, err := csn.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`取值`csn.baidubce.com`,如果设置为空字符串,会使用默认域名作为CSN的服务地址。 + +### 使用STS创建CSN Client + +**申请STS token** + +CSN可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CSN,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CSN Client** + +申请好STS后,可将STS Token配置到CSN Client中,从而实现通过STS Token创建CSN Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CSN Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/csn" //导入CSN服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CSN服务的Client对象,Endpoint使用默认值 + csnClient, err := csn.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "csn.baidubce.com") + if err != nil { + fmt.Println("create csn client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + csnClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CSN Client时,STS的Endpoint需配置为http://sts.bj.baidubce.com。 + +# 配置HTTPS协议访问CSN + +CSN支持HTTPS传输协议,您可以通过在创建CSN Client对象时指定的Endpoint中指明HTTPS的方式,在CSN GO SDK中使用HTTPS访问CSN服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/csn" + +ENDPOINT := "https://csn.baidubce.com " //指明使用HTTPS协议 +AK, SK := , +csnClient, _ := csn.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CSN Client + +如果用户需要配置CSN Client的一些细节的参数,可以在创建CSN Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CSN服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/csn" + +//创建CSN Client对象 +AK, SK := , +ENDPOINT := "csn.baidubce.com" +client, _ := csn.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/csn" + +AK, SK := , +ENDPOINT := "csn.baidubce.com" +client, _ := csn.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/csn" + +AK, SK := , +ENDPOINT := "csn.baidubce.com" +client, _ := csn.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CSN时,创建的CSN Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CSN Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +# 主要接口 +## 创建云智能网 + +```go +desc := "desc" +args := &CreateCsnRequest{ + Name: "csn_api_1", + Description: &desc, +} + +result, err := CsnClient.CreateCsn(args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +CsnId := result.CsnId +log.Debug(CsnId) +``` + +## 更新云智能网 + +```go +name := "csn_api_2" +args := &UpdateCsnRequest{ + Name: &name, +} + +err := CsnClient.UpdateCsn("csn-jf1tqjuvndzganap", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除云智能网 + +```go +err := CsnClient.DeleteCsn("csn-jf1tqjuvndzganap", getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询云智能网列表 + +```go +args := &ListCsnArgs{ + MaxKeys : 1, + Marker: "csn-jf1tqjuvndzganap", +} +result, err := CsnClient.ListCsn(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询云智能网详情 + +```go +result, err := CsnClient.GetCsn("csn-3cq38gxc8irzuu0x") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询云智能网网络实例列表 + +```go +args := &ListInstanceArgs{ + MaxKeys: 1, + Marker: "tgwAttach-rvu8tkaubphb78eg", +} +result, err := CsnClient.ListInstance("csn-3cq38gxc8irzuu0x", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 云智能网加载网络实例 + +```go +args := &AttachInstanceRequest{ + InstanceId: "vpc-f7lkcxldfcwq", + InstanceType: "bec_vpc", + InstanceRegion: "cn-hangzhou-cm", +} +err := CsnClient.AttachInstance("csn-3cq38gxc8irzuu0x", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 云智能网卸载网络实例 + +```go +args := &DetachInstanceRequest{ + InstanceId: "vpc-05y94xgfpujx", + InstanceType: "vpc", + InstanceRegion: "bj", +} +err := CsnClient.DetachInstance("csn-3cq38gxc8irzuu0x", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + + +## 查询路由表列表 + +```go +args := &ListRouteTableArgs{} +result, err := CsnClient.ListRouteTable("csn-3cq38gxc8irzuu0x", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 添加路由条目 + +```go +args := &CreateRouteRuleRequest{ + AttachId: "tgwAttach-rvu8tkaubphb78eg", + DestAddress: "0.0.0.0/0", + RouteType: "custom", +} +err := CsnClient.CreateRouteRule("csnRt-qjv5vtzxf53qq3wr", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询路由条目 + +```go +args := &ListRouteRuleArgs{} +result, err := CsnClient.ListRouteRule("csnRt-qjv5vtzxf53qq3wr", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 删除路由条目 + +```go +err := CsnClient.DeleteRouteRule("csnRt-qjv5vtzxf53qq3wr", +"7bb14cb1-fb02-4ae3-97d6-4e295338936c", getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 创建学习关系 + +```go +desc := "desc" +args := &CreatePropagationRequest{ + AttachId: "tgwAttach-uff0gvjkis95f6xg", + Description: &desc, +} +err := CsnClient.CreatePropagation("csnRt-qjv5vtzxf53qq3wr", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询学习关系 + +```go +result, err := CsnClient.ListPropagation("csnRt-qjv5vtzxf53qq3wr") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 删除学习关系 + +```go +err := CsnClient.DeletePropagation("csnRt-qjv5vtzxf53qq3wr", +"tgwAttach-uff0gvjkis95f6xg", getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 创建关联关系 + +```go +desc := "desc" +args := &CreateAssociationRequest{ + AttachId: "tgwAttach-uff0gvjkis95f6xg", + Description: &desc, +} +err := CsnClient.CreateAssociation("csnRt-qjv5vtzxf53qq3wr", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询关联关系 + +```go +result, err := CsnClient.ListAssociation("csnRt-qjv5vtzxf53qq3wr") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 删除关联关系 + +```go +err := CsnClient.DeleteAssociation("csnRt-qjv5vtzxf53qq3wr", +"tgwAttach-uff0gvjkis95f6xg", getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 创建带宽包 + +```go +instanceType := "center-edge" +args := &CreateCsnBpRequest{ + Name: "csnBp_api_2", + Bandwidth: 100, + InterworkType: &instanceType, + GeographicA: "China", + GeographicB: "China", + Billing: Billing{ + PaymentTiming: "Postpaid", + }, +} +result, err := CsnClient.CreateCsnBp(args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 更新带宽包 + +```go +args := &UpdateCsnBpRequest{ + Name: "csnBp_api_2", +} +err := CsnClient.UpdateCsnBp("csnBp-nyfb3wn198y7", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除带宽包 + +```go +err := CsnClient.DeleteCsnBp("csnBp-d51bzt2ah6ka", getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询带宽包列表 + +```go +args := &ListCsnBpArgs{} +result, err := CsnClient.ListCsnBp(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询指定带宽包详情 + +```go +result, err := CsnClient.GetCsnBp("csnBp-nyfb3wn198y7") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 带宽包的带宽升降级 + +```go +args := &ResizeCsnBpRequest{ + Bandwidth: 200, +} +err := CsnClient.ResizeCsnBp("csnBp-nyfb3wn198y7", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 带宽包绑定云智能网 + +```go +args := &BindCsnBpRequest{ + CsnId: "csn-3cq38gxc8irzuu0x", +} +err := CsnClient.BindCsnBp("csnBp-sj44m8xnq1z9", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 带宽包解绑云智能网 + +```go +args := &UnbindCsnBpRequest{ + CsnId: "csn-3cq38gxc8irzuu0x", +} +err := CsnClient.UnbindCsnBp("csnBp-nyfb3wn198y7", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 创建地域带宽 + +```go +args := &CreateCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "cn-hangzhou-cm", + Bandwidth: 10, +} +err := CsnClient.CreateCsnBpLimit("csnBp-sj44m8xnq1z9", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 更新地域带宽 + +```go +args := &UpdateCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "cn-hangzhou-cm", + Bandwidth: 20, +} +err := CsnClient.UpdateCsnBpLimit("csnBp-sj44m8xnq1z9", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除地域带宽 + +```go +args := &DeleteCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "cn-hangzhou-cm", +} +err := CsnClient.DeleteCsnBpLimit("csnBp-sj44m8xnq1z9", args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询地域带宽 + +```go +result, err := CsnClient.ListCsnBpLimit("csnBp-sj44m8xnq1z9") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询指定云智能网的地域带宽 + +```go +result, err := CsnClient.ListCsnBpLimitByCsnId("csn-3cq38gxc8irzuu0x") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询TGW列表 + +```go +args := &ListTgwArgs{} +result, err := CsnClient.ListTgw("csn-3cq38gxc8irzuu0x", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 更新TGW信息 + +```go +name := "tgw_1" +desc := "desc" +args := &UpdateTgwRequest{ + Name: &name, + Description: &desc, +} +err := CsnClient.UpdateTgw("csn-3cq38gxc8irzuu0x", "tgw-kyn0dc0e1qrvvaz5", +args, getClientToken()) +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询TGW路由条目 + +```go +args := &ListTgwRuleArgs{} +result, err := CsnClient.ListTgwRule("csn-3cq38gxc8irzuu0x", "tgw-kyn0dc0e1qrvvaz5", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +# 错误处理 + +GO语言以error类型标识错误,CSN支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | CSN服务返回的错误 + +用户使用SDK调用CSN相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +args := &ListInstanceRequest{ + InstanceType: "eip", +} +result, err := CsnClient.ListInstance(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向CSN发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当CSN服务端出现异常时,CSN服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[CSN错误码](https://cloud.baidu.com/doc/CSN/s/Tl56j65ym) + +## SDK日志 + +CSN GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +CSN GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the CSN go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the CSN go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/DBSC.md b/bce-sdk-go/doc/DBSC.md new file mode 100644 index 0000000..aaac743 --- /dev/null +++ b/bce-sdk-go/doc/DBSC.md @@ -0,0 +1,173 @@ +# DBSC服务 + +# 概述 + +本文档主要介绍DBSC GO SDK的使用。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BCC访问域名](https://cloud.baidu.com/doc/BCC/s/0jwvyo603)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云DBSC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问DBSC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DBSC Client + +DBSC Client是BCC服务的客户端,为开发者与DBSC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建DBSC Client + +通过AK/SK方式使用DBSC,用户可以参考如下代码新建一个dbsc Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/dbsc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个BCCClient + dbscClient, err := dbsc.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BCC/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为dbsc的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + + +# 主要接口 + + +### 创建磁盘专属集群 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &CreateVolumeClusterArgs{ + // 创建一个磁盘磁盘专属集群,若要同时创建多个,可以修改此参数 + PurchaseCount: 1, + // 集群大小,支持最小容量:85TB(87040GB),支持最大容量:1015TB(1039360GB),购买步长:10TB + ClusterSizeInGB: 97280, + // 集群名称 + ClusterName: "dbsc", + // 集群磁盘类型:通用型HDD,通用型SSD + StorageType: StorageTypeHdd, + Billing: &Billing{ + // 只支持预付费 + Reservation: &Reservation{ + // 购买时长 + ReservationLength: 6, + ReservationTimeUnit: "MONTH", + }, + }, + // 自动续费时长 + RenewTimeUnit: "MONTH", + RenewTime: 6, +} +result, err := DBSC_CLIENT.CreateVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +clusterId := result.ClusterIds[0] +fmt.Print(clusterId) +``` + +### 磁盘专属集群列表 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &ListVolumeClusterArgs{ +} +result, err := DBSC_CLIENT.ListVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +fmt.Println(result) +``` + +### 磁盘专属集群详情 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +result, err := DBSC_CLIENT.GetVolumeClusterDetail(clusterId) +if err != nil { + fmt.Println(err) +} +fmt.Println(result) +``` + +### 磁盘专属集群扩容 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &ResizeVolumeClusterArgs{ + NewClusterSizeInGB int `json:"newClusterSizeInGB"` +} +err := DBSC_CLIENT.ResizeVolumeCluster(clusterId, args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群续费 +以下代码可以根据实例ID批量查询实例列表 +```go +args := &PurchaseReservedVolumeClusterArgs{ + Billing: &Billing{ + Reservation: &Reservation{ + // 续费时长 + ReservationLength: 6, + ReservationTimeUnit: "MONTH", + }, + }, +} +clusterId := "clusterId" +err := DBSC_CLIENT.PurchaseReservedVolumeCluster(clusterId, args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群自动续费 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &AutoRenewVolumeClusterArgs{ + ClusterId: clusterId, + RenewTime: 6, + RenewTimeUnit: "month", +} +err := DBSC_CLIENT.AutoRenewVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +``` + +### 磁盘专属集群取消自动续费 +以下代码可以根据实例ID批量查询实例列表 +```go +clusterId := "clusterId" +args := &CancelAutoRenewVolumeClusterArgs{ + ClusterId: clusterId, +} +err := DBSC_CLIENT.CancelAutoRenewVolumeCluster(args) +if err != nil { + fmt.Println(err) +} +``` + +首次发布: + + - 创建磁盘专属集群、磁盘专属集群列表、磁盘专属集群详情、磁盘专属集群扩容、磁盘专属集群续费、磁盘专属集群自动续费、磁盘专属集群取消自动续费 diff --git a/bce-sdk-go/doc/DDC.md b/bce-sdk-go/doc/DDC.md new file mode 100644 index 0000000..15e757b --- /dev/null +++ b/bce-sdk-go/doc/DDC.md @@ -0,0 +1,1221 @@ +# DDC服务 + +# 概述 + +本文档主要介绍DDC GO SDK的使用。在使用本文档前,您需要先了解DDC的一些基本知识,并已开通了DDC服务。 + +# 初始化 + +## 确认Endpoint + + +目前支持“华东-苏州”区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +SU | ddc.su.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云DDC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问DDC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DDC Client + +DDC Client是DDC服务的客户端,为开发者与DDC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建DDC Client + +通过AK/SK方式访问DDC,用户可以参考如下代码新建一个DDC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/ddc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个DDCClient + ddcClient, err := ddc.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为DDC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为苏州,则为`ddc.su.baidubce.com`。 + +### 使用STS创建DDC Client + +**申请STS token** + +DDC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问DDC,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建DDC Client** + +申请好STS后,可将STS Token配置到DDC Client中,从而实现通过STS Token创建DDC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建DDC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/ddc" //导入DDC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建DDC服务的Client对象,Endpoint使用默认值 + ddcClient, err := ddc.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "ddc.su.baidubce.com") + if err != nil { + fmt.Println("create ddc client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + ddcClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置DDC Client时,无论对应DDC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问DDC + +DDC支持HTTPS传输协议,您可以通过在创建DDC Client对象时指定的Endpoint中指明HTTPS的方式,在DDC GO SDK中使用HTTPS访问DDC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +ENDPOINT := "https://ddc.su.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +ddcClient, _ := ddc.NewClient(AK, SK, ENDPOINT) +``` + +## 配置DDC Client + +如果用户需要配置DDC Client的一些细节的参数,可以在创建DDC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问DDC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +//创建DDC Client对象 +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddc.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问DDC时,创建的DDC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建DDC Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# DDC管理 + +云数据库专属集群 DDC (Dedicated Database Cluster)是专业、高性能、高可靠的云数据库服务。云数据库 DDC 提供 Web 界面进行配置、操作数据库实例,还为您提供可靠的数据备份和恢复、完备的安全管理、完善的监控、轻松扩展等功能支持。相对于自建数据库,云数据库 DDC 具有更经济、更专业、更高效、更可靠、简单易用等特点,使您能更专注于核心业务。 + +# 部署集管理 + +## 创建部署集 +使用以下代码可以在指定资源池下创建一个新的部署集。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.CreateDeployRequest{ + // 幂等 Token + ClientToken: "xxxyyyzzz", + // 部署集名称 + DeployName: "api-from-go", + // 部署策略 支持集中部署(centralized)/完全打散(distributed) + Strategy: "distributed", +} +err = client.CreateDeploySet(poolId, args) +if err != nil { + fmt.Printf("create deploy set error: %+v\n", err) + return +} + +fmt.Println("create deploy set success.") +``` + +## 查询部署集列表 +使用以下代码可以查询指定资源池下的部署集列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.ListDeploySets(poolId, nil) +if err != nil { + fmt.Printf("list deploy set error: %+v\n", err) + return +} + +for i := range result.Result { + deploy := result.Result[i] + fmt.Println("ddc deploy id: ", deploy.DeployID) + fmt.Println("ddc deploy name: ", deploy.DeployName) + fmt.Println("ddc deploy strategy: ", deploy.Strategy) + fmt.Println("ddc deploy create time: ", deploy.CreateTime) + fmt.Println("ddc instance ids: ", deploy.Instances) +} +``` + +## 查询特定部署集信息 +使用以下代码可以查询特定部署集的详细信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +deploy, err := client.GetDeploySet(poolId, deployId) +if err != nil { + fmt.Printf("get deploy set error: %+v\n", err) + return +} + +// 获取部署集的详细信息 +fmt.Println("ddc deploy id: ", deploy.DeployID) +fmt.Println("ddc deploy name: ", deploy.DeployName) +fmt.Println("ddc deploy strategy: ", deploy.Strategy) +fmt.Println("ddc deploy create time: ", deploy.CreateTime) +fmt.Println("ddc instance ids: ", deploy.Instances) +``` + +## 删除部署集 +使用以下代码可以删除某个资源池下特定的部署集。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +err := DDC_CLIENT.DeleteDeploySet(poolId, deployId) +if err != nil { + fmt.Printf("delete deploy set error: %+v\n", err) + return +} +fmt.Printf("delete deploy set success\n") +``` + +# 账号管理 + +## 创建账号 + +使用以下代码可以在某个主实例下创建一个新的账号。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.CreateAccountArgs{ + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClientToken: "xxxyyyzzz", + // 账号名称,由小写字母、数字、下划线组成、字母开头,字母或数字结尾,最长16个字符,不能为保留关键字,必选 + AccountName: "accountName", + // 账号的密码,由字母、数字和特殊字符(!@#%^_)中的至少两种组成,长度8-32位,必选 + Password: "password", + // 账号权限类型,Common:普通帐号,Super:super账号。可选,默认为 Common + AccountType: "Common", + // 权限设置,可选 + DatabasePrivileges: []ddc.DatabasePrivilege{ + { + // 数据库名称 + DbName: "user_photo_001", + // 授权类型。ReadOnly:只读,ReadWrite:读写 + AuthType: "ReadOnly", + }, + }, + // 帐号备注,最多256个字符(一个汉字等于三个字符),可选 + Desc: "账号user1", +} +err = client.CreateAccount(instanceId, args) +if err != nil { + fmt.Printf("create account error: %+v\n", err) + return +} + +fmt.Println("create account success.") +``` + +> 注意: +> - 实例状态为Available,实例必须是主实例。 +> - 没有超出实例最大账号数量。 + +## 更新账号密码 + +使用以下代码可以更新账号的密码。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateAccountPasswordArgs{ + // 密码,由字母、数字和特殊字符(!@#%^_)中的至少两种组成,长度8-32位,必选 + Password: "password", +} +err = client.UpdateAccountPassword(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account password error: %+v\n", err) + return +} + +fmt.Println("update account password success.") +``` + +## 更新账号备注 + +使用以下代码可以更新账号的备注。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateAccountDescArgs{ + // 帐号备注,最多256个字符(一个汉字等于三个字符),可选 + Desc: "desc", +} +err = client.UpdateAccountDesc(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account desc error: %+v\n", err) + return +} + +fmt.Println("update account desc success.") +``` + +## 更新账号权限 + +使用以下代码可以更新账号的权限。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +databasePrivileges := []ddc.DatabasePrivilege{ + { + DbName: "hello", + // 授权类型。ReadOnly:只读,ReadWrite:读写 + AuthType: "ReadOnly", + }, +} + +args := &ddc.UpdateAccountPrivilegesArgs{ + DatabasePrivileges: databasePrivileges, +} +err = client.UpdateAccountPrivileges(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account privileges error: %+v\n", err) + return +} + +fmt.Println("update account privileges success.") +``` + +## 查询特定账号信息 + +使用以下代码可以查询特定账号信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.GetAccount(instanceId,accountName) +if err != nil { + fmt.Printf("get account error: %+v\n", err) + return +} + +// 获取account的信息 +fmt.Println("ddc accountName: ", result.AccountName) +fmt.Println("ddc desc: ", result.Desc) +// 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) +fmt.Println("ddc accountStatus: ", result.AccountStatus) +// 账号类型(super账号:Super;普通账号:Common) +fmt.Println("ddc accountType: ", result.AccountType) +fmt.Println("ddc databasePrivileges: ", result.DatabasePrivileges) +``` + +## 查询账号列表 + +使用以下代码可以查询指定实例的账号列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.ListAccount(instanceId) +if err != nil { + fmt.Printf("list account error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, account := range result.Accounts { + fmt.Println("ddc accountName: ", account.AccountName) + fmt.Println("ddc desc: ", account.Desc) + // 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) + fmt.Println("ddc accountStatus: ", account.AccountStatus) + // 账号类型(super账号:Super;普通账号:Common) + fmt.Println("ddc accountType: ", account.AccountType) + fmt.Println("ddc databasePrivileges: ", account.DatabasePrivileges) +} +``` + +## 删除特定账号信息 + +使用以下代码可以删除特定账号信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +err := client.DeleteAccount(instanceId,accountName) +if err != nil { + fmt.Printf("delete account error: %+v\n", err) + return +} +fmt.Printf("delete account success\n") +``` + +# 数据库管理 + +## 创建数据库 + +使用以下代码可以在某个主实例下创建一个新的数据库。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.CreateDatabaseArgs{ + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClientToken: "xxxyyyzzz", + // 数据库名称(由大小写字母、数字、下划线组成、字母开头,字母或数字结尾,最长64个字符) + DbName: "dbName", + // 数据库字符集(取值范围:utf8、gbk、latin1、utf8mb4) + CharacterSetName: "utf8", + // 数据库备注,最多256个字符(一个汉字等于三个字符) + Remark: "remark", +} +err = client.CreateDatabase(instanceId, args) +if err != nil { + fmt.Printf("create database error: %+v\n", err) + return +} + +fmt.Println("create database success.") +``` + +> 注意: +> - 实例状态为available,实例必须是主实例。 + +## 更新数据库备注 + +使用以下代码可以更新数据库的备注。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateDatabaseRemarkArgs{ + // 数据库备注,最多256个字符(一个汉字等于三个字符) + Remark: "remark", +} +err = client.UpdateDatabaseRemark(instanceId, dbName, args) +if err != nil { + fmt.Printf("update database remark error: %+v\n", err) + return +} + +fmt.Println("update database remark success.") +``` + +## 查询特定数据库信息 + +使用以下代码可以查询特定数据库信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.GetDatabase(instanceId,dbName) +if err != nil { + fmt.Printf("get database error: %+v\n", err) + return +} + +// 获取account的列表信息 +fmt.Println("ddc dbName: ", result.DbName) +fmt.Println("ddc characterSetName: ", result.CharacterSetName) +// 数据库状态(创建中:Creating;可用中:Available;删除中:Deleting;已删除:Deleted) +fmt.Println("ddc dbStatus: ", result.DbStatus) +fmt.Println("ddc remark: ", result.Remark) +fmt.Println("ddc accountPrivileges: ", result.AccountPrivileges) +``` + +## 查询数据库列表 + +使用以下代码可以查询指定实例的账号列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.ListDatabase(instanceId) +if err != nil { + fmt.Printf("list database error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, database := range result.Databases { + fmt.Println("ddc dbName: ", database.DbName) + fmt.Println("ddc characterSetName: ", database.CharacterSetName) + // 数据库状态(创建中:Creating;可用中:Available;删除中:Deleting;已删除:Deleted) + fmt.Println("ddc dbStatus: ", database.DbStatus) + fmt.Println("ddc remark: ", database.Remark) + fmt.Println("ddc accountPrivileges: ", database.AccountPrivileges) +} +``` + +## 删除特定数据库信息 + +使用以下代码可以删除特定数据库信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +err := client.DeleteDatabase(instanceId,dbName) +if err != nil { + fmt.Printf("delete database error: %+v\n", err) + return +} +fmt.Printf("delete database success\n") +``` + +# 实例管理 + +## 创建实例 +使用以下代码可以创建主实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +args := &ddc.CreateRdsArgs{ + // 指定ddc的数据库引擎,取值mysql,必选 + Engine: "mysql", + // 指定ddc的数据库版本,必选 + EngineVersion: "5.6", + // 计费相关参数,PaymentTiming取值为 预付费:Prepaid,后付费:Postpaid;Reservation:支付方式为后支付时不需要设置,预支付时必须设置;必选 + Billing: ddc.Billing{ + PaymentTiming: "Postpaid", + //Reservation: ddc.Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + // 预付费时可指定自动续费参数 AutoRenewTime 和 AutoRenewTimeUnit + // 自动续费时长(续费单位为year 不大于3,续费单位为mouth 不大于9) + // AutoRenewTime: 1, + // 自动续费单位("year";"mouth") + // AutoRenewTimeUnit: "year", + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //批量创建云数据库 ddc 实例个数, 最大不超过10,默认1,可选 + PurchaseCount: 1, + //ddc实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //所属系列,Basic:单机基础版,Standard:双机高可用版。仅SQLServer 2012sp3 支持单机基础版。默认Standard,可选 + Category: "Standard", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames: ["cn-bj-a"], + //vpc,如果不提供则属于默认vpc,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []ddc.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, +// 实例绑定的标签信息,可选 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + // 部署集id 可选 + DeployId:"xxxyyy-123", + // 资源池id 必选 + PoolId:"xxxyzzzyy-123", + // 创建双机版主实例支持选择同步方式 Semi_sync:开启半同步/Async:异步 + SyncMode:"Semi_sync", +} +result, err := client.CreateRds(args) +if err != nil { + fmt.Printf("create ddc error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create ddc success, instanceId: ", e) +} +``` + +## 创建只读实例 +使用以下代码可以创建只读实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +args := &ddc.CreateReadReplicaArgs{ + //主实例ID,必选 + SourceInstanceId: "sourceInstanceId" + // 计费相关参数,只读实例只支持后付费Postpaid,必选 + Billing: ddc.Billing{ + PaymentTiming: "Postpaid", + }, + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //批量创建云数据库 ddc 只读实例个数, 目前只支持一次创建一个,可选 + PurchaseCount: 1, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames: ["cn-bj-a"], + //与主实例 vpcId 相同,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []ddc.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + // 部署集id 可选 + DeployId:"xxxyyy-123", + // 资源池id 必选与主实例保持一致 + PoolId:"xxxyzzzyy-123", + // RO组ID。(创建只读实例时) 可选 + // 如果不传,默认会创建一个RO组,并将该只读加入RO组中 + RoGroupId:"yyzzcc", + // RO组是否启用延迟剔除,默认不启动。(创建只读实例时)可选 + EnableDelayOff:false, + // 延迟阈值。(创建只读实例时)可选 + DelayThreshold: 1, + // RO组最少保留实例数目。默认为1. (创建只读实例时)可选 + LeastInstanceAmount: 1, + // 只读实例在RO组中的读流量权重。默认为1(创建只读实例时)可选 + RoGroupWeight: 1, +} +result, err := client.CreateReadReplica(args) +if err != nil { + fmt.Printf("create ddc readReplica error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create ddc readReplica success, instanceId: ", e) +} +``` + +## 实例详情 + +使用以下代码可以查询指定实例的详情。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.GetDetail(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取实例详情信息 +fmt.Println("ddc instanceId: ", result.InstanceId) +fmt.Println("ddc instanceName: ", result.InstanceName) +fmt.Println("ddc engine: ", result.Engine) +fmt.Println("ddc engineVersion: ", result.EngineVersion) +fmt.Println("ddc instanceStatus: ", result.InstanceStatus) +fmt.Println("ddc cpuCount: ", result.CpuCount) +fmt.Println("ddc memoryCapacity: ", result.MemoryCapacity) +fmt.Println("ddc volumeCapacity: ", result.VolumeCapacity) +fmt.Println("ddc usedStorage: ", result.UsedStorage) +fmt.Println("ddc paymentTiming: ", result.PaymentTiming) +fmt.Println("ddc instanceType: ", result.InstanceType) +fmt.Println("ddc instanceCreateTime: ", result.InstanceCreateTime) +fmt.Println("ddc instanceExpireTime: ", result.InstanceExpireTime) +fmt.Println("ddc publicAccessStatus: ", result.PublicAccessStatus) +fmt.Println("ddc vpcId: ", result.VpcId) +fmt.Println("ddc Subnets: ", result.Subnets) +fmt.Println("ddc BackupPolicy: ", result.BackupPolicy) +fmt.Println("ddc RoGroupList: ", result.RoGroupList) +fmt.Println("ddc NodeMaster: ", result.NodeMaster) +fmt.Println("ddc NodeSlave: ", result.NodeSlave) +fmt.Println("ddc NodeReadReplica: ", result.NodeReadReplica) +fmt.Println("ddc DeployId: ", result.DeployId) +``` + +## 实例列表 +使用以下代码可以查询实例列表信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.ListRdsArgs{ + // 批量获取列表的查询的起始位置,实例列表中Marker需要指定实例Id,可选 + // Marker: "marker", + // 指定每页包含的最大数量(主实例),最大数量不超过1000,缺省值为1000,可选 + MaxKeys: 1, +} +resp, err := client.ListRds(args) + +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("ddc list marker: ", resp.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("ddc list isTruncated: ", resp.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("ddc list nextMarker: ", resp.NextMarker) +// 每页包含的最大数量 +fmt.Println("ddc list maxKeys: ", resp.MaxKeys) + +// 获取instance的列表信息 +for _, e := range resp.Result { + fmt.Println("ddc instanceId: ", e.InstanceId) + fmt.Println("ddc instanceName: ", e.InstanceName) + fmt.Println("ddc engine: ", e.Engine) + fmt.Println("ddc engineVersion: ", e.EngineVersion) + fmt.Println("ddc instanceStatus: ", e.InstanceStatus) + fmt.Println("ddc cpuCount: ", e.CpuCount) + fmt.Println("ddc memoryCapacity: ", e.MemoryCapacity) + fmt.Println("ddc volumeCapacity: ", e.VolumeCapacity) + fmt.Println("ddc usedStorage: ", e.UsedStorage) + fmt.Println("ddc paymentTiming: ", e.PaymentTiming) + fmt.Println("ddc instanceType: ", e.InstanceType) + fmt.Println("ddc instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("ddc instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("ddc publicAccessStatus: ", e.PublicAccessStatus) + fmt.Println("ddc vpcId: ", e.VpcId) +} +``` + +## 删除实例 +使用以下代码可以批量删除实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +// 多个实例间用英文半角逗号","隔开,最多可输入10个 +err := client.DeleteRds(instanceIds) +if err != nil { + fmt.Printf("delete instance error: %+v\n", err) + return +} +fmt.Printf("delete instance success\n") +``` + +## 修改实例名称 +使用以下代码可以修改实例名称。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateInstanceNameArgs{ + // DDC实例名称,允许小写字母、数字,中文,长度限制为1~64 + InstanceName: "instanceName", +} +err := client.UpdateInstanceName(instanceId, args) +if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return +} +fmt.Printf("update instance name success\n") +``` + +## 主备切换 +使用以下代码可以进行主备切换。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +err := client.SwitchInstance(instanceId) +if err != nil { + fmt.Printf(" main standby switching of the instance error: %+v\n", err) + return +} +fmt.Printf(" main standby switching of the instance success\n") +``` +## 只读组列表 +使用以下代码可以查询只读组列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +resp, err := client.ListRoGroup(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取只读组信息 +for _, e := range resp.RoGroups { + fmt.Println("ddc roGroupId: ", e.RoGroupId) + fmt.Println("ddc vnetIp: ", e.VnetIp) +} +``` + +## VPC列表 +使用以下代码可以查询vpc列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +resp, err := client.ListVpc() +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取vpc列表信息 +for _, e := range* resp { + fmt.Println("ddc vpcId: ", e.VpcId) + fmt.Println("ddc shortId: ", e.ShortId) + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc cidr: ", e.Cidr) + fmt.Println("ddc status: ", e.Status) + fmt.Println("ddc createTime: ", e.CreateTime) + fmt.Println("ddc description: ", e.Description) + fmt.Println("ddc defaultVpc: ", e.DefaultVpc) + fmt.Println("ddc ipv6Cidr: ", e.Ipv6Cidr) + fmt.Println("ddc auxiliaryCidr: ", e.AuxiliaryCidr) + fmt.Println("ddc relay: ", e.Relay) +} +``` + +## 可用区列表 +使用以下代码可以获取可用区列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +resp, err = client.GetZoneList() +if err != nil { + fmt.Printf("get zone list error: %+v\n", err) + return +} +for _, e := range resp.Zones { + fmt.Println("ddc zoneNames: ", e.ZoneNames) + fmt.Println("ddc apiZoneNames: ", e.ApiZoneNames) + fmt.Println("ddc available: ", e.Available) + fmt.Println("ddc defaultSubnetId: ", e.DefaultSubnetId) +} +``` + +## 子网列表 +使用以下代码可以获取一个实例下的子网列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +args := &rds.ListSubnetsArgs{} +resp, err := client.ListSubnets(args) +if err != nil { + fmt.Printf("get subnet list error: %+v\n", err) + return +} +for _, e := range resp.Subnets { + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc subnetId: ", e.SubnetId) + fmt.Println("ddc zoneName: ", e.ZoneName) + fmt.Println("ddc shortId: ", e.ShortId) + fmt.Println("ddc vpcId: ", e.VpcId) + fmt.Println("ddc vpcShortId: ", e.VpcShortId) + fmt.Println("ddc az: ", e.Az) + fmt.Println("ddc cidr: ", e.Cidr) + fmt.Println("ddc createdTime: ", e.CreatedTime) + fmt.Println("ddc updatedTime: ", e.UpdatedTime) +} +``` +# 参数管理 + +## 实例参数列表 +使用以下代码可以查询实例参数列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +resp, err := client.ListParameters(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取参数列表信息 +for _, e := range resp.Items { + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc defaultValue: ", e.DefaultValue) + fmt.Println("ddc value: ", e.Value) + fmt.Println("ddc pendingValue: ", e.PendingValue) + fmt.Println("ddc type: ", e.Type) + fmt.Println("ddc dynamic: ", e.Dynamic) + fmt.Println("ddc modifiable: ", e.Modifiable) + fmt.Println("ddc allowedValues: ", e.AllowedValues) + fmt.Println("ddc desc: ", e.Desc) +} +``` + +## 修改实例参数 +使用以下代码可以修改云数据库 DDC 的参数配置。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateParameterArgs{ + Parameters: []ddc.KVParameter{ + { + Name: "connect_timeout", + Value: "15", + }, + }, +} +err := client.UpdateParameter(instanceId, args) +if err != nil { + fmt.Printf("update parameter: %+v\n", err) + return +} +fmt.Printf("update parameter success\n") +``` + +# 安全管理 + +## 白名单列表 +使用以下代码可以查询实例白名单列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +result, err := client.GetSecurityIps(instanceId) +if err != nil { + fmt.Printf("get securityIp list error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Printf("get securityIp list success\n") +``` + +## 更新白名单 +使用以下代码可以更新一个实例下的白名单列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.UpdateSecurityIpsArgs{ +SecurityIps: []string{ + "%", + "192.0.0.1", + "192.0.0.2", + }, +} +er := client.UpdateSecurityIps(instanceId, args) +if er != nil { + fmt.Printf("update securityIp list error: %+v\n", er) + return +} +fmt.Printf("update securityIp list success\n") +``` + +# 备份管理 +## 获取备份列表 +使用以下代码可以获取一个实例下的备份列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +resp, err := client.GetBackupList(instanceId) +if err != nil { + fmt.Printf("get backup list error: %+v\n", err) + return +} +// 返回标记查询的起始位置 +fmt.Println("ddc usedSpaceInMB: ", resp.UsedSpaceInMB) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("ddc freeSpaceInMB: ", resp.FreeSpaceInMB) +// 获取参数列表信息 +for _, e := range resp.Snapshots { + fmt.Println("ddc snapshotId: ", e.SnapshotId) + fmt.Println("ddc snapshotSizeInBytes: ", e.SnapshotSizeInBytes) + fmt.Println("ddc snapshotType: ", e.SnapshotType) + fmt.Println("ddc snapshotStatus: ", e.SnapshotStatus) + fmt.Println("ddc snapshotStartTime: ", e.SnapshotStartTime) + fmt.Println("ddc snapshotEndTime: ", e.SnapshotEndTime) +} +``` + +## 创建备份 +使用以下代码创建实例备份。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +err := client.CreateBackup(instanceId) +if err != nil { + fmt.Printf("create backup error: %+v\n", err) + return +} +fmt.Printf("create backup success\n") +``` + +## 备份详情 +使用以下代码可以查询一个备份的详情。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +resp, err := client.GetBackupDetail(instanceId, snapshotId) +if err != nil { + fmt.Printf("get backup detail error: %+v\n", err) + return +} + +fmt.Println("ddc snapshotId: ", resp.Snapshot.SnapshotId) +fmt.Println("ddc instanceName: ", resp.Snapshot.SnapshotSizeInBytes) +fmt.Println("ddc engine: ", resp.Snapshot.SnapshotType) +fmt.Println("ddc engineVersion: ", resp.Snapshot.SnapshotStatus) +fmt.Println("ddc instanceStatus: ", resp.Snapshot.SnapshotStartTime) +fmt.Println("ddc cpuCount: ", resp.Snapshot.SnapshotEndTime) +fmt.Println("ddc downloadUrl: ", resp.Snapshot.DownloadUrl) +fmt.Println("ddc downloadExpires: ", resp.Snapshot.DownloadExpires) +``` + +## 设置备份 +使用以下代码设置实例的备份策略。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +args := &ddc.BackupPolicy{ + // 以英文半角逗号分隔的备份时日间,周日为第一天,取值0 + BackupDays: "0,1,2,3,5,6", + // 备份开始时间,使用UTC时间 + BackupTime: "17:00:00Z", + // 是否启用备份数据持久化 + Persistent: true, + // 持久化天数,范围7-730天;未启用则为0或不填 + ExpireInDays: 7, +} +err := client.ModifyBackupPolicy(instanceId, args) +if err != nil { + fmt.Printf("modify instance's backupPolicy error: %+v\n", err) + return +} +fmt.Printf("modify instance's backupPolicy success\n") +``` + +## binlog列表 +使用以下代码可以获取一个实例下的binlog列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" +// datetime UTC时间 +resp, err := client.GetBinlogList(instanceId, datetime) +if err != nil { + fmt.Printf("get binlog list error: %+v\n", err) + return +} +// 获取参数列表信息 +for _, e := range resp.Binlogs { + fmt.Println("ddc binlogId: ", e.BinlogId) + fmt.Println("ddc binlogSizeInBytes: ", e.BinlogSizeInBytes) + fmt.Println("ddc binlogStatus: ", e.BinlogStatus) + fmt.Println("ddc binlogStartTime: ", e.BinlogStartTime) + fmt.Println("ddc binlogEndTime: ", e.BinlogEndTime) +} +``` + +## binlog 详情 +使用以下代码可以查询一个binlog详情。 +```go +// import "github.com/baidubce/bce-sdk-go/services/ddc" + +resp, err := client.GetBinlogDetail(instanceId, binlog) +if err != nil { + fmt.Printf("get binlog detail error: %+v\n", err) + return +} + +fmt.Println("ddc binlogId: ", resp.Binlog.BinlogId) +fmt.Println("ddc binlogSizeInBytes: ", resp.Binlog.BinlogSizeInBytes) +fmt.Println("ddc binlogStatus: ", resp.Binlog.BinlogStatus) +fmt.Println("ddc binlogStartTime: ", resp.Binlog.BinlogStartTime) +fmt.Println("ddc binlogEndTime: ", resp.Binlog.BinlogEndTime) +fmt.Println("ddc downloadUrl: ", resp.Binlog.DownloadUrl) +fmt.Println("ddc downloadExpires: ", resp.Binlog.DownloadExpires) +``` + + +# 错误处理 + +GO语言以error类型标识错误,DDC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | DDC服务返回的错误 + +用户使用SDK调用DDC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// ddcClient 为已创建的DDC Client对象 +result, err := client.ListDdcInstance() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向DDC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当DDC服务端出现异常时,DDC服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +DDC GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +DDC GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the DDC go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the DDC go sdk") +``` + + + +首次发布: + + - 支持创建账号、更新账号密码、更新账号备注、更新账号权限、查询账号列表、查询特定账号信息、删除特定账号信息 +- 支持创建数据库、更新数据库备注、查询数据库列表、查询特定数据库信息、删除特定数据库信息 \ No newline at end of file diff --git a/bce-sdk-go/doc/DDCv2.md b/bce-sdk-go/doc/DDCv2.md new file mode 100644 index 0000000..79edd81 --- /dev/null +++ b/bce-sdk-go/doc/DDCv2.md @@ -0,0 +1,2831 @@ +# DDC-RDS服务 + +# 概述 + +本文档主要介绍DDC-RDS GO SDK的使用。在使用本文档前,您需要先了解DDC和RDS的一些基本知识,并已开通了DDC服务和RDS服务。 + +# 初始化 + +## 确认Endpoint + + +目前支持“华东-苏州”区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +SU | ddc.su.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云DDC-RDS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问DDC-RDS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DDC-RDS Client + +DDC-RDS Client是同时支持DDC和RDS服务的客户端,为开发者与DDC和RDS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建DDC-RDS Client + +通过AK/SK方式访问DDC-RDS,用户可以参考如下代码新建一个DDC-RDS Client: + +```go +import ( + ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个DDC-RDSClient + client, err := ddcrds.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。 +第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名`ddc.su.baidubce.com`作为DDC-RDS的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为苏州,则为`ddc.su.baidubce.com`。 + +### 使用STS创建DDC-RDS Client + +**申请STS token** + +DDC-RDS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问DDC-RDS,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建DDC-RDS Client** + +申请好STS后,可将STS Token配置到DDC-RDS Client中,从而实现通过STS Token创建DDC-RDS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建DDC-RDS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 + ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" //导入DDC-RDS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建DDC-RDS服务的Client对象,Endpoint使用默认值 + client, err := ddcrds.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "ddc.su.baidubce.com") + if err != nil { + fmt.Println("create v2 client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + client.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置DDC-RDS Client时,无论对应DDC-RDS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问DDC-RDS + +DDC-RDS支持HTTPS传输协议,您可以通过在创建DDC-RDS Client对象时指定的Endpoint中指明HTTPS的方式,在DDC-RDS GO SDK中使用HTTPS访问DDC-RDS服务: + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +ENDPOINT := "https://ddc.su.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +client, _ := ddcrds.NewClient(AK, SK, ENDPOINT) +``` + +## 配置DDC-RDS Client + +如果用户需要配置DDC-RDS Client的一些细节的参数,可以在创建DDC-RDS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 +### 切换地域 + +下面一段代码可以让客户端使用切换地域: +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +//创建DDC-RDS Client对象 +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddcrds.NewClient(AK, SK, ENDPOINT) + +// 切换到北京地域 +client.ConfigRegion("BJ") +``` + + +### 使用代理 + +下面一段代码可以让客户端使用代理访问DDC-RDS服务: + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +//创建DDC-RDS Client对象 +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddcrds.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.ConfigProxyUrl("127.0.0.1:8080") +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddcrds.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.ConfigRetry(bce.NewNoRetryPolicy()) + +// 配置连接超时时间为30秒 +client.ConfigConnectionTimeoutInMillis(30 * 1000) +``` + +### 配置生成签名字符串选项 + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +AK, SK := , +ENDPOINT := "ddc.su.baidubce.com" +client, _ := ddcrds.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.ConfigSignOptionHeadersToSign(headersToSign) + +// 配置签名的有效期为30秒 +client.ConfigSignOptionExpireSeconds(30) +``` + +**参数说明** + +用户使用GO SDK访问DDC-RDS时,创建的DDC-RDS Client对象的`Config`字段支持的所有参数如下表所示,可以通过 +`Config{Field}()`等方法设置配置项,例如:`ConfigRegion("BJ")` + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建DDC-RDS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# DDC-RDS管理 +DDC-RDS服务可以管理以下两种服务 +- 云数据库专属集群 DDC (Dedicated Database Cluster)是专业、高性能、高可靠的云数据库服务。云数据库 DDC 提供 Web 界面进行配置、操作数据库实例,还为您提供可靠的数据备份和恢复、完备的安全管理、完善的监控、轻松扩展等功能支持。相对于自建数据库,云数据库 DDC 具有更经济、更专业、更高效、更可靠、简单易用等特点,使您能更专注于核心业务。 +- 云数据库 RDS (Relational Database Service)是专业、高性能、高可靠的云数据库服务。云数据库 RDS 提供 Web 界面进行配置、操作数据库实例,还为您提供可靠的数据备份和恢复、完备的安全管理、完善的监控、轻松扩展等功能支持。相对于自建数据库,云数据库 RDS 具有更经济、更专业、更高效、更可靠、简单易用等特点,使您能更专注于核心业务。 + +# 资源池管理 + +## 查询资源池列表 +使用以下代码可以查询当前用户的资源池列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +result, err := client.ListPool(nil, "ddc") +if err != nil { + fmt.Printf("list pool error: %+v\n", err) + return +} + +for i := range result.Result { + pool := result.Result[i] + fmt.Println("ddc pool id: ", pool.PoolID) + fmt.Println("ddc pool vpc id: ", pool.VpcID) + fmt.Println("ddc pool engine: ", pool.Engine) + fmt.Println("ddc pool create time: ", pool.CreateTime) + fmt.Println("ddc pool hosts: ", pool.Hosts) +} +``` + +## 查询资源池余量 +使用以下代码可以查询当前指定资源池的资源余量(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +residual, err := client.GetResidual(poolId) +if err != nil { + fmt.Printf("get residual of pool error: %+v\n", err) + return +} +fmt.Println("get residual of pool success.") +for zoneName, residualByZone := range residual.Residual { + // 可用区名称 + fmt.Println("Zone name: ", zoneName) + // 单机版余量 + fmt.Printf("Single residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.Single.DiskInGb, residualByZone.Single.MemoryInGb, residualByZone.Single.CPUInCore) + // 只读资源余量 + fmt.Printf("Slave residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.Slave.DiskInGb, residualByZone.Slave.MemoryInGb, residualByZone.Slave.CPUInCore) + // 双机版余量 + fmt.Printf("HA residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.HA.DiskInGb, residualByZone.HA.MemoryInGb, residualByZone.HA.CPUInCore) +} +``` + +## 查询资源池套餐余量 +使用以下代码可以查询当前指定资源池的套餐余量(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// 指定套餐 +args := &ddcrds.GetFlavorCapacityArgs{ + CpuInCore: 2, + MemoryInGb: 4, + DiskInGb: 50, + // 指定亲和度 + Affinity: 2, +} +// 使用默认亲和度,默认为10 +// args := ddcrds.NewDefaultGetFlavorCapacityArgs(2,4,50) +capacityResult, err := client.GetFlavorCapacity(poolId, args) +if err != nil { + fmt.Printf("get flavor capacity of pool error: %+v\n", err) + return +} +fmt.Println("get flavor capacity of pool success.") +for zoneName, residualByZone := range capacityResult.Capacity { + // 可用区名称 + fmt.Println("Zone name: ", zoneName) + // 双机版余量 + fmt.Printf("HA capacity: %d\n", residualByZone.HA) + // 单机版余量 + fmt.Printf("Single capacity: %d\n", residualByZone.Single) + // 只读实例余量 + fmt.Printf("Slave capacity: %d\n", residualByZone.Slave) +} +``` + +# 部署集管理 + +## 创建部署集 +使用以下代码可以在指定资源池下创建一个新的部署集(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +args := &ddcrds.CreateDeployRequest{ + // 幂等 Token + ClientToken: "xxxyyyzzz", + // 部署集名称 + DeployName: "api-from-go", + // 亲和度阈值 取值范围【0-96】 + CentralizeThreshold: 23, +} +deploy,err := client.CreateDeploySet(poolId, args) +if err != nil { + fmt.Printf("create deploy set error: %+v\n", err) + return +} + +fmt.Println("create deploy set success.") +fmt.Println("returned deploy id: ", deploy.DeployID) +``` + +## 查询部署集列表 +使用以下代码可以查询指定资源池下的部署集列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +result, err := client.ListDeploySets(poolId, nil) +if err != nil { + fmt.Printf("list deploy set error: %+v\n", err) + return +} + +for i := range result.Result { + deploy := result.Result[i] + fmt.Println("ddc deploy id: ", deploy.DeployID) + fmt.Println("ddc deploy name: ", deploy.DeployName) + fmt.Println("ddc deploy strategy: ", deploy.Strategy) + fmt.Println("ddc deploy create time: ", deploy.CreateTime) + fmt.Println("ddc deploy centralizeThreshold: ", deploy.CentralizeThreshold) + fmt.Println("ddc instance ids: ", deploy.Instances) +} +``` + +## 查询特定部署集信息 +使用以下代码可以查询特定部署集的详细信息(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +deploy, err := client.GetDeploySet(poolId, deployId) +if err != nil { + fmt.Printf("get deploy set error: %+v\n", err) + return +} + +// 获取部署集的详细信息 +fmt.Println("ddc deploy id: ", deploy.DeployID) +fmt.Println("ddc deploy name: ", deploy.DeployName) +fmt.Println("ddc deploy strategy: ", deploy.Strategy) +fmt.Println("ddc deploy create time: ", deploy.CreateTime) +fmt.Println("ddc deploy centralizeThreshold: ", deploy.CentralizeThreshold) +fmt.Println("ddc instance ids: ", deploy.Instances) +``` +## 更新部署集 +使用以下代码更新部署集的部署策略和亲和度(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +args := &ddcrds.UpdateDeployRequest{ + // 幂等 Token + ClientToken: "xxxyyyzzz", + // 部署策略 支持集中部署(centralized)/完全打散(distributed) + Strategy: "distributed", + // 亲和度阈值 取值范围【0-96】,必须大于原亲和度阈值 + CentralizeThreshold: 30, +} +err := client.UpdateDeploySet(poolId, deployId, args) +if err != nil { + fmt.Printf("update deploy set error: %+v\n", err) + return +} + +fmt.Println("update deploy set success.") +``` + +## 删除部署集 +使用以下代码可以删除某个资源池下特定的部署集(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +err := DDC_CLIENT.DeleteDeploySet(poolId, deployId) +if err != nil { + fmt.Printf("delete deploy set error: %+v\n", err) + return +} +fmt.Printf("delete deploy set success\n") +``` + +# 实例管理 + +## 创建实例 +使用以下代码可以创建主实例。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.CreateRdsArgs{ + // 指定ddc的数据库引擎,取值mysql,必选 + Engine: "mysql", + // 指定ddc的数据库版本,必选 + EngineVersion: "5.6", + // 计费相关参数,PaymentTiming取值为 预付费:Prepaid,后付费:Postpaid;Reservation:支付方式为后支付时不需要设置,预支付时必须设置;必选 + // 目前仅支持预付费 + Billing: ddcrds.Billing{ + PaymentTiming: "Prepaid", + Reservation: ddcrds.Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + // 预付费时可指定自动续费参数 AutoRenewTime 和 AutoRenewTimeUnit + // 自动续费时长(续费单位为year 不大于3,续费单位为mouth 不大于9) + AutoRenewTime: 1, + // 自动续费单位("year";"month") + AutoRenewTimeUnit: "month", + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //批量创建云数据库 ddc 实例个数, 最大不超过10,默认1,可选 + PurchaseCount: 1, + //ddc实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //所属系列,Singleton:单机版,Basic:单机基础版,Standard:双机高可用版。仅SQLServer 2012sp3 支持单机基础版。默认Standard,可选 + Category: "Standard", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames:[]string{"cn-bj-a"}, + //vpc ShortId 如果不提供则属于默认vpc,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认true,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑 + IsDirectPay: true, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []ddcrds.SubnetMap{ + { + ZoneName: "cn-bj-a", + // 指定子网的ShortId + SubnetId: "subnet-short-id", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []ddcrds.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + // 部署集id 产品类型为DDC时必选,产品类型为RDS忽略该参数 + DeployId:"xxxyyy-123", + // 资源池id 产品类型为DDC时必选,产品类型为RDS忽略该参数 + PoolId:"xxxyzzzyy-123", + // 创建双机版主实例支持选择同步方式 Semi_sync:开启半同步/Async:异步 + SyncMode:"Semi_sync", +} +// 创建DDC数据库专属集群产品,需要传入产品类型参数ddc +result, err := client.CreateRds(args,"ddc") +if err != nil { + fmt.Printf("create ddc error: %+v\n", err) + return +} + +fmt.Println("create ddc success, orderId: ", result.OrderId) +for _, e := range result.InstanceIds { + fmt.Println("create ddc success, instanceId: ", e) +} + +// RDS +// 创建RDS数据库,需要传入产品类型参数rds +result, err = client.CreateRds(args,"rds") +if err != nil { + fmt.Printf("create ddc error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create ddc success, instanceId: ", e) +} +``` + +## 创建只读实例 +使用以下代码可以创建只读实例。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +args := &ddcrds.CreateReadReplicaArgs{ + //主实例ID,必选 + SourceInstanceId: "sourceInstanceId", + // 计费相关参数,只读实例只支持后付费Prepaid,必选 + Billing: ddcrds.Billing{ + PaymentTiming: "Prepaid", + Reservation: ddcrds.Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + // 预付费时可指定自动续费参数 AutoRenewTime 和 AutoRenewTimeUnit + // 自动续费时长(续费单位为year 不大于3,续费单位为mouth 不大于9) + AutoRenewTime: 1, + // 自动续费单位("year";"month") + AutoRenewTimeUnit: "month", + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //批量创建云数据库 只读实例个数, 目前rds产品只支持一次创建一个,必选 + PurchaseCount: 1, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames: []string{"cn-bj-a"}, + //与主实例 vpcId 相同,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认true,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: true, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []ddcrds.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []ddcrds.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + // 部署集id 产品类型为DDC时必选,产品类型为RDS忽略该参数 + DeployId:"xxxyyy-123", + // 资源池id 产品类型为DDC时必选,产品类型为RDS忽略该参数 与主实例保持一致 + PoolId:"xxxyzzzyy-123", + // RO组ID。(创建只读实例时) 可选 + // 如果不传,默认会创建一个RO组,并将该只读加入RO组中 + RoGroupId:"yyzzcc", + // RO组是否启用延迟剔除,默认为0。可选值0和1,0代表不开启,1代表开启(创建只读实例时)可选 + EnableDelayOff:"1", + // 延迟阈值,单位为秒。(创建只读实例时)可选 + DelayThreshold: "1", + // RO组最少保留实例数目。默认为1. (创建只读实例时)可选 + LeastInstanceAmount: "1", + // 只读实例在RO组中的读流量权重,取值范围为[0-100]。默认为50(创建只读实例时)可选 + RoGroupWeight: "50", +} +result, err := client.CreateReadReplica(args) +if err != nil { + fmt.Printf("create ddc readReplica error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create ddc readReplica success, instanceId: ", e) +} + +// RDS +result, err = client.CreateReadReplica(args) +if err != nil { + fmt.Printf("create rds readReplica error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create rds readReplica success, instanceId: ", e) +} +``` + +## 实例详情 + +使用以下代码可以查询指定实例的详情。 +```go +package main +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +result, err := client.GetDetail(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取实例详情信息 +fmt.Println("ddc instanceId: ", result.InstanceId) +fmt.Println("ddc instanceName: ", result.InstanceName) +fmt.Println("ddc engine: ", result.Engine) +fmt.Println("ddc engineVersion: ", result.EngineVersion) +fmt.Println("ddc instanceStatus: ", result.InstanceStatus) +fmt.Println("ddc cpuCount: ", result.CpuCount) +fmt.Println("ddc memoryCapacity: ", result.MemoryCapacity) +fmt.Println("ddc volumeCapacity: ", result.VolumeCapacity) +fmt.Println("ddc usedStorage: ", result.UsedStorage) +fmt.Println("ddc paymentTiming: ", result.PaymentTiming) +fmt.Println("ddc instanceType: ", result.InstanceType) +fmt.Println("ddc instanceCreateTime: ", result.InstanceCreateTime) +fmt.Println("ddc instanceExpireTime: ", result.InstanceExpireTime) +fmt.Println("ddc publicAccessStatus: ", result.PublicAccessStatus) +fmt.Println("ddc vpcId: ", result.VpcId) +fmt.Println("ddc Subnets: ", result.Subnets) +fmt.Println("ddc BackupPolicy: ", result.BackupPolicy) +fmt.Println("ddc RoGroupList: ", result.RoGroupList) +fmt.Println("ddc NodeMaster: ", result.NodeMaster) +// 主角色所在物理机主机名 +fmt.Println("master BBC hostname: ", result.NodeMaster.HostName) +fmt.Println("ddc NodeSlave: ", result.NodeSlave) +// 备角色所在物理机主机名 +fmt.Println("slave BBC hostname: ", result.NodeSlave.HostName) +fmt.Println("ddc NodeReadReplica: ", result.NodeReadReplica) +fmt.Println("ddc DeployId: ", result.DeployId) +fmt.Println("ddc SyncMode: ", result.SyncMode) +fmt.Println("ddc Category: ", result.Category) +fmt.Println("ddc ZoneNames: ", result.ZoneNames) +fmt.Println("ddc Endpoint: ", result.Endpoint) +fmt.Println("ddc vnetIp: ", result.Endpoint.VnetIp) +// 备库访问Ip +fmt.Println("ddc vnetIpBackup: ", result.Endpoint.VnetIpBackup) +// 拓扑信息 +fmt.Println("ddc topo: ", result.InstanceTopoForReadonly) + +// RDS +result, err := client.GetDetail(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取实例详情信息 +fmt.Println("rds instanceId: ", result.InstanceId) +fmt.Println("rds instanceName: ", result.InstanceName) +fmt.Println("rds engine: ", result.Engine) +fmt.Println("rds engineVersion: ", result.EngineVersion) +fmt.Println("rds instanceStatus: ", result.InstanceStatus) +fmt.Println("rds cpuCount: ", result.CpuCount) +fmt.Println("rds memoryCapacity: ", result.MemoryCapacity) +fmt.Println("rds volumeCapacity: ", result.VolumeCapacity) +fmt.Println("rds usedStorage: ", result.UsedStorage) +fmt.Println("rds paymentTiming: ", result.PaymentTiming) +fmt.Println("rds instanceType: ", result.InstanceType) +fmt.Println("rds instanceCreateTime: ", result.InstanceCreateTime) +fmt.Println("rds instanceExpireTime: ", result.InstanceExpireTime) +fmt.Println("rds publicAccessStatus: ", result.PublicAccessStatus) +fmt.Println("rds vpcId: ", result.VpcId) +fmt.Println("rds Subnets: ", result.Subnets) +fmt.Println("rds BackupPolicy: ", result.BackupPolicy) +fmt.Println("rds RoGroupList: ", result.RoGroupList) +fmt.Println("rds NodeMaster: ", result.NodeMaster) +fmt.Println("rds NodeSlave: ", result.NodeSlave) +fmt.Println("rds NodeReadReplica: ", result.NodeReadReplica) +fmt.Println("rds DeployId: ", result.DeployId) +fmt.Println("rds SyncMode: ", result.SyncMode) +fmt.Println("rds Category: ", result.Category) +fmt.Println("rds ZoneNames: ", result.ZoneNames) +fmt.Println("rds Endpoint: ", result.Endpoint) +// 自动续费规则 +if result.AutoRenewRule != nil { + fmt.Println("ddc renewTime: ", result.AutoRenewRule.RenewTime) + fmt.Println("ddc renewTimeUnit: ", result.AutoRenewRule.RenewTimeUnit) +} +``` + +## 实例列表 +使用以下代码可以查询实例列表信息,优先获取DDC实例列表,获取完成后再获取RDS实例列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC&RDS +args := &ddcrds.ListRdsArgs{ + // 批量获取列表的查询的起始位置,实例列表中Marker需要指定实例Id,可选 + // Marker: "marker", + // 指定每页包含的最大数量(主实例),最大数量不超过1000,缺省值为1000,可选 + MaxKeys: 1, +} +resp, err := client.ListRds(args) + +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("list marker: ", resp.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("list isTruncated: ", resp.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("list nextMarker: ", resp.NextMarker) +// 每页包含的最大数量 +fmt.Println("list maxKeys: ", resp.MaxKeys) + +// 获取instance的列表信息 +for _, e := range resp.Instances { + fmt.Println("instance productType: ", e.ProductType()) + fmt.Println("instanceId: ", e.InstanceId) + fmt.Println("instanceName: ", e.InstanceName) + fmt.Println("engine: ", e.Engine) + fmt.Println("engineVersion: ", e.EngineVersion) + fmt.Println("instanceStatus: ", e.InstanceStatus) + fmt.Println("cpuCount: ", e.CpuCount) + fmt.Println("memoryCapacity: ", e.MemoryCapacity) + fmt.Println("volumeCapacity: ", e.VolumeCapacity) + fmt.Println("usedStorage: ", e.UsedStorage) + fmt.Println("paymentTiming: ", e.PaymentTiming) + fmt.Println("instanceType: ", e.InstanceType) + fmt.Println("instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("publicAccessStatus: ", e.PublicAccessStatus) + fmt.Println("vpcId: ", e.VpcId) + fmt.Println("endpoint: ", e.Endpoint) + fmt.Println("vnetIp: ", e.Endpoint.VnetIp) + fmt.Println("vnetIpBackup: ", e.Endpoint.VnetIpBackup) + // 物理机信息 + fmt.Println("long BBC Id: ", e.LongBBCId) + fmt.Println("BBC hostname: ", e.HostName) + // 自动续费规则 + if e.AutoRenewRule != nil { + fmt.Println("renewTime: ", e.AutoRenewRule.RenewTime) + fmt.Println("renewTimeUnit: ", e.AutoRenewRule.RenewTimeUnit) + } +} +``` + +## 实例分页列表 +使用以下代码可以分页查询实例列表信息,支持添加筛选条件。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.ListPageArgs{ + // 页码 + PageNo: 1, + // 页大小 + PageSize: 5, + // 筛选条件 + // 筛选字段类型,各筛选条件只能单独筛选 + // 当KeywordType取值为type、status、dbType、zone时可在Filters数组中追加筛选项 + // all:匹配全部 + // instanceName:匹配实例名称 + // instanceId:匹配实例id; + // vnetIpBackup:备库ip; + // vnetIp:主库ip + Filters: []ddcrds.Filter{ + {KeywordType: "all", Keyword: "mysql"}, + {KeywordType: "zone", Keyword: "cn-bj-a"}, + }, +} +resp, err := DDCRDS_CLIENT.ListPage(args) + +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} + +// 返回分页页码 +fmt.Println("list pageNo: ", resp.Page.PageNo) +// 返回页大小 +fmt.Println("list pageSize: ", resp.Page.PageSize) +// 返回总数量 +fmt.Println("list totalCount: ", resp.Page.TotalCount) + +// 获取instance的列表信息 +for _, e := range resp.Page.Result { + fmt.Println("instanceId: ", e.InstanceId) + fmt.Println("instanceName: ", e.InstanceName) + fmt.Println("engine: ", e.Engine) + fmt.Println("engineVersion: ", e.EngineVersion) + fmt.Println("instanceStatus: ", e.InstanceStatus) + fmt.Println("cpuCount: ", e.CpuCount) + fmt.Println("memoryCapacity: ", e.MemoryCapacity) + fmt.Println("volumeCapacity: ", e.VolumeCapacity) + fmt.Println("usedStorage: ", e.UsedStorage) + fmt.Println("paymentTiming: ", e.PaymentTiming) + fmt.Println("instanceType: ", e.InstanceType) + fmt.Println("instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("publiclyAccessible: ", e.PubliclyAccessible) + fmt.Println("backup expireInDays: ", e.BackupPolicy.ExpireInDays) + fmt.Println("vpcId: ", e.VpcId) + fmt.Println("endpoint: ", e.Endpoint) + fmt.Println("vnetIp: ", e.Endpoint.VnetIp) + fmt.Println("vnetIpBackup: ", e.Endpoint.VnetIpBackup) + fmt.Println("long BBC Id: ", e.LongBBCId) + fmt.Println("bbc hostname: ", e.HostName) + // 自动续费规则 + if e.AutoRenewRule != nil { + fmt.Println("renewTime: ", e.AutoRenewRule.RenewTime) + fmt.Println("renewTimeUnit: ", e.AutoRenewRule.RenewTimeUnit) + } +} +``` + +## 获取磁盘信息 +使用以下代码可以获取指定实例下磁盘的详细信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +disk, err := client.GetDisk(instanceId) +if err != nil { + fmt.Printf("get disk of instance error: %+v\n", err) + return +} +fmt.Println("get disk of instance success.") +for _, diskItem := range disk.Response.Items { + fmt.Println("instance id: ", diskItem.InstanceID) + fmt.Println("instance role: ", diskItem.InstanceRole) + fmt.Println("disk disk partition: ", diskItem.DiskPartition) + fmt.Println("disk totle size in bytes: ", diskItem.TotalSize) + fmt.Println("disk used size in bytes: ", diskItem.UsedSize) + fmt.Println("disk report time: ", diskItem.ReportTime) +} +``` + +## 获取物理机资源信息 +使用以下代码可以获取指定实例所在物理机资源信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +machine, err := client.GetMachineInfo(instanceId) +if err != nil { + fmt.Printf("get machine info error: %+v\n", err) + return +} +fmt.Println("get machine info success.") +for _, machine := range machine.Response.Items { + fmt.Println("instance id: ", machine.InstanceID) + fmt.Println("instance role: ", machine.Role) + fmt.Println("cpu(core): ", machine.CPUInCore) + fmt.Println("cpu(core) free: ", machine.FreeCPUInCore) + fmt.Println("memory(MB): ", machine.MemSizeInMB) + fmt.Println("memory(MB) free: ", machine.FreeMemSizeInMB) + fmt.Println("disk info: ", machine.SizeInGB) + fmt.Println("----------------------") +} +``` + +## 删除实例 +使用以下代码可以批量删除实例,RDS产品将删除实例,DDC产品会将实例放入回收站。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// 多个实例间用英文半角逗号","隔开,最多可输入10个 +// DDC +err := client.DeleteRds(instanceIds) +if err != nil { + fmt.Printf("delete instance error: %+v\n", err) + return +} +fmt.Printf("delete instance success\n") + +// RDS +err := client.DeleteRds(instanceIds) +if err != nil { + fmt.Printf("delete instance error: %+v\n", err) + return +} +fmt.Printf("delete instance success\n") + +``` + +## 修改实例名称 +使用以下代码可以修改实例名称。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.UpdateInstanceNameArgs{ + // DDC实例名称,允许小写字母、数字,中文,长度限制为1~64 + InstanceName: "instanceName", +} +// DDC +err := client.UpdateInstanceName(instanceId, args) +if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return +} +fmt.Printf("update instance name success\n") + +// RDS +err := client.UpdateInstanceName(instanceId, args) +if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return +} +fmt.Printf("update instance name success\n") +``` + +## 主备切换 +使用以下代码可以进行主备切换,支持立即切换或者下一个操作窗口内切换(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.SwitchArgs{ + // 立即切换 + IsSwitchNow: true, +} +result, err := client.SwitchInstance(instanceId, args) +if err != nil { + fmt.Printf(" main standby switching of the instance error: %+v\n", err) + return +} +if result != nil { + fmt.Printf(" main standby switching of the instance success, taskId: %v\n", result.Result.TaskID) +} else { + fmt.Printf(" main standby switching of the instance success\n") +} +``` + +## 只读组列表 +使用以下代码可以查询只读组列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +resp, err := client.ListRoGroup(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取只读组信息 +for _, e := range resp.RoGroups { + fmt.Println("ddc roGroupId: ", e.RoGroupId) + fmt.Println("ddc vnetIp: ", e.VnetIp) +} +``` +> 注意: +> - 实例状态为Available,实例必须是主实例。 + + +## 更新只读组并分配权重 +用于更新只读组的信息,包括设置实例延迟剔除,设置只读实例权重等(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +replicaWeight := ddcrds.ReplicaWeight{ + // 只读实例ID + InstanceId: replicaId, + // 新权重 + Weight: 20, +} +args := &ddcrds.UpdateRoGroupWeightArgs{ + // 只读实例组名称 + RoGroupName: "testRo", + // RO组是否启用延迟剔除。需传入数字,取值为0或1,0代表不开启,可选 + EnableDelayOff: "0", + // 延迟阈值,单位为秒。需传入数字,可选,启用延迟剔除时必选 + DelayThreshold: "0", + // RO组最少保留实例数目。需传入数字,可选,启用延迟剔除时必选 + LeastInstanceAmount: "1", + // 是否重新进行负载均衡,需传入数字,取值为0或1,可选 + IsBalanceRoLoad: "0", + // 设置只读组的延迟参数,需传入数字,取值范围0-259200秒,开启延迟只读时可修改,可选 + MasterDelay: "0", + // 只读副本新权重数组 + ReplicaList: []ddcrds.ReplicaWeight{replicaWeight}, +} +err := client.UpdateRoGroupReplicaWeight(roGroupId, args, "ddc") +if err != nil { + fmt.Printf("update ddc roGroup replica weight error: %+v\n", err) + return +} +fmt.Println("update ddc roGroup replica weight success") +``` + +## 只读组发起重新负载均衡 +使用以下代码可以对只读组发起一次重新负载均衡(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +err := client.ReBalanceRoGroup(roGroupId, "ddc") +if err != nil { + fmt.Printf("reBalance ddc roGroup error: %+v\n", err) + return +} +fmt.Println("reBalance ddc roGroup success") +``` + +## 创建RDS代理实例 + +使用以下代码可以创建一个RDS代理实例(仅支持RDS) +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.CreateRdsProxyArgs{ + //主实例ID,必选 + SourceInstanceId: "sourceInstanceId", + // 计费相关参数,代理实例只支持后付费Postpaid,必选 + Billing: ddcrds.Billing{ + PaymentTiming: "Postpaid", + }, + // 代理实例节点数。取值范围2,4,6,8,16,必选 + NodeAmount: 2, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a",建议与主实例的可用区保持一致 + ZoneNames: []string{"cn-bj-a"}, + //与主实例 vpcId 相同,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []ddcrds.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []ddcrds.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateRdsProxy(args) +if err != nil { + fmt.Printf("create rds proxy error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create rds proxy success, instanceId: ", e) +} +``` + +> 注意: +> - 需要在云数据库 RDS 主实例的基础上进行创建 +> - 仅数据库类型为 MySQL 的主实例支持创建只读实例 +> - 代理实例套餐和主实例套餐绑定,主实例版本最低是MySQL 5.6 +> - 每个主实例最多可以创建1个代理实例 +> - 需与主实例在同一vpc中 +> - 代理实例只支持后付费方式购买 + +## RDS实例扩缩容 + +使用以下代码可以对RDS实例扩缩容操作。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.ResizeRdsArgs{ + // cpu核数 + CpuCount: 2, + // 内存大小,单位GB + MemoryCapacity: 8, + // 磁盘大小,单位GB,每5G递增 + VolumeCapacity: 20, + // 代理实例节点数,代理实例变配时此项必填 + NodeAmount: 2, + // 是否进行直接支付,默认true,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + // 是否立即变配 RDS只支持立即变配 + IsResizeNow: true, +} +orderIdResponse, err = client.ResizeRds(instanceId, args) +if err != nil { + fmt.Printf("resize ddc error: %+v\n", err) + return +} +fmt.Println("resize ddc success, orderId: ", orderIdResponse.OrderId) +``` + +> 注意: +> - 实例可选套餐详见(https://cloud.baidu.com/doc/RDS/s/9jwvz0wd3) +> - 主实例或只读实例变配时至少填写cpuCount、memoryCapacity、volumeCapacity其中的一个。 +> - 实例计费方式采用后付费时,可弹性扩缩容;采用预付费方式,不能进行缩容操作。 +> - 只有实例available状态时才可以进行扩缩容操作。 +> - 实例扩缩容之后会重启一次。 +> - 为异步接口,可通过查询实例详情接口查看instanceStatus是否恢复。 + +## 重启实例 + +使用以下代码可以重启实例。 +DDC实例支持用户决定是否延迟重启(下一个操作窗口重启) + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// 立即重启:实例ID为RDS实例ID或DDC实例ID +err := client.RebootInstance(instanceId) +if err != nil { + fmt.Printf("reboot error: %+v\n", err) + return +} + +// 延迟重启(仅支持DDC) +args := &ddcrds.RebootArgs{ + IsRebootNow: false, +} +result, err := client.RebootInstanceWithArgs(instanceId, args) +if err != nil { + fmt.Printf("reboot ddc error: %+v\n", err) + return +} +if result != nil { + fmt.Printf("reboot ddc success, taskId: %+v\n", result.TaskID) +} +``` + +## 修改同步模式 + +使用以下代码可以修改实例同步模式。 + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.ModifySyncModeArgs{ + //"Async"异步复制 + //"Semi_sync"半同步复制。 + SyncMode: "Async", +} +// instanceId为DDC实例ID或RDS实例ID +err := client.ModifySyncMode(instanceId, args) +if err != nil { + fmt.Printf("modify syncMode error: %+v\n", err) + return +} +fmt.Printf("modify syncMode success\n") +``` + +## 修改连接信息 + +使用以下代码可以修改RDS域名前缀(仅支持RDS)。 + +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.ModifyEndpointArgs{ + Address: "newAddress", +} +err := client.ModifyEndpoint(instanceId, args) +if err != nil { + fmt.Printf("modify endpoint error: %+v\n", err) + return +} +fmt.Printf("modify endpoint success\n") +``` + +> 注意: +> +> - 只传输域名前缀即可。域名前缀由小写字母和数字组成,以小写字母开头,长度在3-30之间。 + +## 开关公网访问 +使用以下代码可以修改RDS公网访问状态(仅支持RDS)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.ModifyPublicAccessArgs{ + // true or false + PublicAccess: true, +} +err := client.ModifyPublicAccess(instanceId, args) +if err != nil { + fmt.Printf("modify public access error: %+v\n", err) + return +} +fmt.Printf("modify public access success\n") +``` + +> 注意: +> +> - true:开启公网访问; false:关闭公网访问。 + +## 已创建实例自动续费(仅支持RDS) + +使用以下代码可以为已创建的预付费实例创建自动续费 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.AutoRenewArgs{ + // 自动续费时长(续费单位为year 不大于3,续费单位为month 不大于9)必选 + AutoRenewTime: 1, + // 自动续费单位("year";"month")必选 + AutoRenewTimeUnit: "month", + // 实例id集合,必须为预付费实例的Id列表 必选 + InstanceIds: []string{ + "rds-y9dJu77d", + "rds-aQFOoncr", + }, +} +err := client.AutoRenew(args, "rds") +if err != nil { + fmt.Printf("create auto renew error: %+v\n", err) + return +} +``` +> 注意: +> +> - 用于已创建的实例开启自动续费。 +> - 可以传入多个实例id,多个实例需保证在同一地域。 + +## 查询实例的维护时间窗口 +使用以下代码可以查询实例的维护时间窗口(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +maintainTime,err := client.GetMaintainTime(instanceId) +if err != nil { + fmt.Printf("get maintain time error: %+v\n", err) + return +} +fmt.Println("maintainTime duration", maintainTime.Duration) +fmt.Println("maintainTime period", maintainTime.Period) +fmt.Println("maintainTime startTime", maintainTime.StartTime) +fmt.Printf("get maintain time success\n") +``` + +> 注意: +> +> - startTime 为北京时间24小时制,例如14:00。 + +## 修改实例的维护时间窗口 +使用以下代码可以修改实例的维护时间窗口(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.MaintainTime{ + // 时长间隔,单位为小时 + Duration: 3, + // 1-0分别代表周一到周日 + Period: "1,2,3,4,5,6,0", + // 所有涉及的时间皆为北京时间24小时制 + StartTime: "14:00", +} +err := client.UpdateMaintainTime(instanceId, args) +if err != nil { + fmt.Printf("update maintain time error: %+v\n", err) +} +``` + +> 注意: +> +> - startTime 为北京时间24小时制,例如14:00。 + + +## 查询任务列表 +通过此接口可以获取到任务列表,管理已经执行过的任务,或者时间窗口内即将执行的任务(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.GetMaintainTaskListArgs{ + Marker: ddcrds.Marker{ + MaxKeys: 10, + }, + // 任务起始时间 必选 + StartTime: "2021-07-31 00:00:00", + // 结束时间 可选 + EndTime: "2021-08-10 00:00:00", + } +result, err := client.GetMaintainTaskList(args) +if err != nil { + fmt.Printf("get tasks error: %+v\n", err) + return +} +fmt.Println("get tasks success.") + +// 返回标记查询的起始位置 +fmt.Println("list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("list maxKeys: ", result.MaxKeys) +for _, task := range result.Result { + fmt.Println("task id: ", task.TaskID) + fmt.Println("task name: ", task.TaskName) + fmt.Println("task status: ", task.TaskStatus) + fmt.Println("instance id: ", task.InstanceID) + fmt.Println("instance name: ", task.InstanceName) + fmt.Println("instance region: ", task.Region) + fmt.Println("start time: ", task.StartTime) + fmt.Println("end time: ", task.EndTime) +} +``` + +> 注意: +> +> - StartTime 和 EndTime 格式为yyyy-MM-dd HH:mm:ss,例如2021-07-31 00:00:00。 + +## 查询任务详情 +通过此接口可以查询任务详情,可查询已经执行过的任务,或者时间窗口内即将执行的任务(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +result, err := client.GetMaintainTaskDetail(taskId) +if err != nil { + fmt.Printf("get task detail error: %+v\n", err) + return +} +fmt.Println("get task detail success.") +for _, task := range result.Tasks { + fmt.Println("task id: ", task.TaskID) + fmt.Println("task name: ", task.TaskName) + fmt.Println("instance instanceId: ", task.InstanceID) + fmt.Println("instance instanceName: ", task.InstanceName) + fmt.Println("instance task type: ", task.TaskType) + fmt.Println("task status: ", task.TaskStatus) + fmt.Println("instance create time: ", task.CreateTime) +} +``` + +## 立即执行任务 +通过此接口对维护时间窗口内的任务发起立即执行的命令(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +err := client.ExecuteMaintainTaskImmediately(taskId) +if err != nil { + fmt.Printf("execute task invoke error: %+v\n", err) + return +} +fmt.Println("execute task invoke success.") +``` + +> 常见错误: +> +> - task not found:任务列表中无此任务 +> - not in running status:任务已执行结束 +> - it had been updated before:任务已触发立即执行或取消 +> - in maintentime or not set maintentime yet:已处于维护时间窗口或者未设置维护时间窗口 + +## 撤销任务 +通过此接口对维护时间窗口内的任务发起撤销命令(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +err := client.CancelMaintainTask(taskId) +if err != nil { + fmt.Printf("cancel task invoke error: %+v\n", err) + return +} +fmt.Println("cancel task invoke success.") +``` + +> 常见错误: +> +> - task not found:任务列表中无此任务 +> - not in running status:任务已执行结束 +> - it had been updated before:任务已触发立即执行或取消 +> - in maintentime or not set maintentime yet:已处于维护时间窗口或者未设置维护时间窗口 + +## 获取回收站中的实例列表 +使用以下代码可以获取回收站中的实例列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// marker分页参数 +marker := &ddcrds.Marker{MaxKeys: 10} +instances, err := client.ListRecycleInstances(marker, "ddc") +if err != nil { + fmt.Printf("list recycler instances error: %+v\n", err) + return +} +for _, instance := range instances.Result { + fmt.Println("instanceId: ", instance.InstanceId) + fmt.Println("instanceName: ", instance.InstanceName) + fmt.Println("engine: ", instance.Engine) + fmt.Println("engineVersion: ", instance.EngineVersion) + fmt.Println("instanceStatus: ", instance.InstanceStatus) + fmt.Println("cpuCount: ", instance.CpuCount) + fmt.Println("memoryCapacity: ", instance.MemoryCapacity) + fmt.Println("volumeCapacity: ", instance.VolumeCapacity) + fmt.Println("usedStorage: ", instance.UsedStorage) + fmt.Println("instanceType: ", instance.InstanceType) + fmt.Println("instanceCreateTime: ", instance.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", instance.InstanceExpireTime) + fmt.Println("publicAccessStatus: ", instance.PublicAccessStatus) + fmt.Println("vpcId: ", instance.VpcId) +} +``` + +## 从回收站中批量恢复实例 +使用以下代码可以从回收站中批量恢复实例(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// 要恢复的实例Id列表 +instanceIds := []string{ + instanceId_1, + instanceId_2, +} +orderIdResponse, err := client.RecoverRecyclerInstances(instanceIds) +if err != nil { + fmt.Printf("recover recycler instances error: %+v\n", err) + return +} +fmt.Println("recover recycler instances success, orderId: ", orderIdResponse.OrderId) +``` + +## 从回收站中批量删除实例 +使用以下代码可以从回收站中批量删除实例,实例将被彻底删除(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// 要删除的实例Id列表 +instanceIds := []string{ + instanceId_1, + instanceId_2, +} +err := client.DeleteRecyclerInstances(instanceIds) +if err != nil { + fmt.Printf("delete recycler instances error: %+v\n", err) + return +} +fmt.Println("delete recycler instances success.") +``` + + +# 数据库管理 + +## 创建数据库 + +使用以下代码可以在某个主实例下创建一个新的数据库(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.CreateDatabaseArgs{ + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClientToken: "xxxyyyzzz", + // 数据库名称(由大小写字母、数字、下划线组成、字母开头,字母或数字结尾,最长64个字符) + DbName: "dbName", + // 数据库字符集(取值范围:utf8、gbk、latin1、utf8mb4) + CharacterSetName: "utf8", + // 数据库备注,最多256个字符(一个汉字等于三个字符) + Remark: "remark", +} +err := client.CreateDatabase(instanceId, args) +if err != nil { + fmt.Printf("create database error: %+v\n", err) + return +} + +fmt.Println("create database success.") +``` + +> 注意: +> - 实例状态为Available,实例必须是主实例。 + +## 更新数据库备注 + +使用以下代码可以更新数据库的备注(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.UpdateDatabaseRemarkArgs{ + // 数据库备注,最多256个字符(一个汉字等于三个字符) + Remark: "remark", +} +err = client.UpdateDatabaseRemark(instanceId, dbName, args) +if err != nil { + fmt.Printf("update database remark error: %+v\n", err) + return +} + +fmt.Println("update database remark success.") +``` + +## 查询特定数据库信息 + +使用以下代码可以查询特定数据库信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +result, err := client.GetDatabase(instanceId,dbName) +if err != nil { + fmt.Printf("get database error: %+v\n", err) + return +} + +// 获取account的列表信息 +fmt.Println("ddc dbName: ", result.DbName) +fmt.Println("ddc characterSetName: ", result.CharacterSetName) +// 数据库状态(创建中:Creating;可用中:Available;删除中:Deleting;已删除:Deleted) +fmt.Println("ddc dbStatus: ", result.DbStatus) +fmt.Println("ddc remark: ", result.Remark) +fmt.Println("ddc accountPrivileges: ", result.AccountPrivileges) +``` + +## 查询数据库列表 + +使用以下代码可以查询指定实例的数据库列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +result, err := client.ListDatabase(instanceId) +if err != nil { + fmt.Printf("list database error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, database := range result.Databases { + fmt.Println("ddc dbName: ", database.DbName) + fmt.Println("ddc characterSetName: ", database.CharacterSetName) + // 数据库状态(创建中:Creating;可用中:Available;删除中:Deleting;已删除:Deleted) + fmt.Println("ddc dbStatus: ", database.DbStatus) + fmt.Println("ddc remark: ", database.Remark) + fmt.Println("ddc accountPrivileges: ", database.AccountPrivileges) +} +``` + +## 获取数据表大小 + +使用以下代码可以获取指定库下满足条件的数据表的大小。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.GetTableAmountArgs{ + // 实例ID + InstanceId: instanceId, + // 指定数据库名称 + DbName: dbName, + // 模糊搜索值, 可选 + Pattern: search, +} +result, err := DDCRDS_CLIENT.GetTableAmount(args) +if err != nil { + fmt.Printf("get table amount error: %+v\n", err) + return +} +fmt.Printf("get table amount success.\n") +fmt.Println("ddc return amount ", result.ReturnAmount) +fmt.Println("ddc total amount ", result.TotalAmount) +for k, v := range result.Tables { + fmt.Println("ddc table ", k, " size: ", v) +} +``` + +## 获取数据库占用磁盘空间 + +使用以下代码可以获取指定实例下的数据库占用的磁盘空间大小以及剩余磁盘空间。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// dbName可选,不指定数据库时传入空字符串即可 +result, err := DDCRDS_CLIENT.GetDatabaseDiskUsage(instanceId, dbName) +if err != nil { + fmt.Printf("get database disk usage error: %+v\n", err) + return +} +fmt.Printf("get database disk usage success.\n") +fmt.Println("ddc rest disk size(byte) ", result.RestDisk) +fmt.Println("ddc used disk size(byte) ", result.UsedDisk) +for k, v := range result.Databases { + fmt.Println("ddc database ", k, " size(byte): ", v) +} +``` + +## 获取实例数据可恢复时间 + +使用以下代码可以获取实例数据所有的可恢复时间段。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +result, err := DDCRDS_CLIENT.GetRecoverableDateTime(instanceId) +if err != nil { + fmt.Printf("get recoverable datetimes error: %+v\n", err) + return +} +fmt.Printf("get recoverable datetimes success.\n") +for _, e := range result.RecoverableDateTimes { + fmt.Println("recover startTime: ", e.StartDateTime, " endTime: ", e.EndDateTime) +} +``` + +## 恢复数据库 + +使用以下代码可以按时间点恢复指定数据库或数据表到原有实例。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.RecoverInstanceArgs{ + // 可恢复时间点,从GetRecoverableDateTime()获取 + Datetime: "2021-05-25T03:28:30Z", + // RestoreMode 为database或table + RecoverData: []ddcrds.RecoverData{ + { + // 数据库名称 + DbName: dbName, + // 新库名 + NewDbName: newDbName, + RestoreMode: "database", + }, + { + DbName: dbName, + NewDbName: newDbName, + RestoreMode: "table", + // RestoreMode为table时RecoverTables为必选 + RecoverTables: []ddcrds.RecoverTable{ + { + // 表名 + TableName: tableName, + // 新表名 + NewTableName: newTableName, + }, + }, + }, + }, +} +taskResult, err := DDCRDS_CLIENT.RecoverToSourceInstanceByDatetime(instanceId, args) +if err != nil { + fmt.Printf("recover instance database error: %+v\n", err) + return +} +fmt.Printf("recover instance database success. taskId:%s\n", taskResult.TaskID) +``` + +## 删除特定数据库 + +使用以下代码可以删除特定数据库信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +err := client.DeleteDatabase(instanceId,dbName) +if err != nil { + fmt.Printf("delete database error: %+v\n", err) + return +} +fmt.Printf("delete database success\n") +``` + +## 延迟删除数据表:创建硬链接 + +使用以下代码可以为数据库下的指定数据表创建硬链接(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +err := client.LazyDropCreateHardLink(instanceId, dbName, tableName) +if err != nil { + fmt.Printf("[lazy drop] create hard link error: %+v\n", err) + return +} +fmt.Println("[lazy drop] create hard link success.") +``` + + +## 延迟删除数据表:删除硬链接 + +使用以下代码可以删除数据库下特定数据表创建的硬链接(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +result, err := client.LazyDropDeleteHardLink(instanceId, dbName, tableName) +if err != nil { + fmt.Printf("[lazy drop] delete hard link error: %+v\n", err) + return +} +fmt.Println("[lazy drop] delete hard link success.taskId:", result.TaskID) +``` +# 会话管理 + +## 执行KillSession任务 + +使用以下代码可以对指定数据库执行KillSession任务。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.KillSessionArgs{ + // 实例角色 + Role: "master", + // Session ID + SessionIds: []int{8661, 8662}, +} +result, err := client.KillSession(instanceId, args) +if err != nil { + fmt.Printf("start kill session task error: %+v\n", err) + return +} +fmt.Println("start kill session task success. TaskID:", result.TaskID) +``` + + +## 查看KillSession任务进度 + +使用以下代码可以查看指定数据库下已执行的KillSession任务的进度。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// taskId 为执行KillSession任务时返回的任务Id +result, err := client.GetKillSessionTask(instanceId, taskId) +if err != nil { + fmt.Printf("get kill session task error: %+v\n", err) + return +} +fmt.Println("get kill session task success.") +for _, task := range result.Tasks { + fmt.Println("sessionId: ", task.SessionID) + fmt.Println("task status: ", task.Status) +} +``` + +# 账号管理 + +## 创建账号 + +使用以下代码可以在某个主实例下创建一个新的账号。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.CreateAccountArgs{ + // 幂等性Token,使用 uuid 生成一个长度不超过64位的ASCII字符串,可选参数 + ClientToken: "xxxyyyzzz", + // 账号名称,由小写字母、数字、下划线组成、字母开头,字母或数字结尾,最长16个字符,不能为保留关键字,必选 + AccountName: "accountName", + // 账号的密码,由字母、数字和特殊字符(!@#%^_)中的至少两种组成,长度8-32位,必选 + Password: "password", + // 账号权限类型,Common:普通帐号,Super:super账号。可选,默认为 Common + AccountType: "Common", + // 权限设置,可选 + DatabasePrivileges: []ddcrds.DatabasePrivilege{ + { + // 数据库名称 + DbName: "user_photo_001", + // 授权类型。ReadOnly:只读,ReadWrite:读写 + AuthType: "ReadOnly", + }, + }, + // 帐号备注,最多256个字符(一个汉字等于三个字符),可选 + Desc: "账号user1", +} +// DDC产品 +err := client.CreateAccount(instanceId, args) +if err != nil { + fmt.Printf("create account error: %+v\n", err) + return +} +// RDS产品 +err = client.CreateAccount(instanceId, args) +if err != nil { + fmt.Printf("create account error: %+v\n", err) + return +} + +fmt.Println("create account success.") +``` +## 更新账号密码 + +使用以下代码可以更新账号的密码(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.UpdateAccountPasswordArgs{ + // 密码,由字母、数字和特殊字符(!@#%^_)中的至少两种组成,长度8-32位,必选 + Password: "password", +} +// DDC +err := client.UpdateAccountPassword(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account password error: %+v\n", err) + return +} + +fmt.Println("update account password success.") +``` + +## 更新账号备注 + +使用以下代码可以更新账号的备注(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.UpdateAccountDescArgs{ + // 帐号备注,最多256个字符(一个汉字等于三个字符),可选 + Desc: "desc", +} +err := client.UpdateAccountDesc(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account desc error: %+v\n", err) + return +} + +fmt.Println("update account desc success.") +``` + +## 更新账号权限 + +使用以下代码可以更新账号的权限(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +databasePrivileges := []ddcrds.DatabasePrivilege{ + { + DbName: "hello", + // 授权类型。ReadOnly:只读,ReadWrite:读写 + AuthType: "ReadOnly", + }, +} + +args := &ddcrds.UpdateAccountPrivilegesArgs{ + DatabasePrivileges: databasePrivileges, +} +// DDC +err = client.UpdateAccountPrivileges(instanceId, accountName, args) +if err != nil { + fmt.Printf("update account privileges error: %+v\n", err) + return +} + +fmt.Println("update account privileges success.") +``` + +## 查询特定账号信息 + +使用以下代码可以查询特定账号信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC产品 +result, err := client.GetAccount(instanceId,accountName) +if err != nil { + fmt.Printf("get account error: %+v\n", err) + return +} + +// 获取account的信息 +fmt.Println("ddc accountName: ", result.AccountName) +fmt.Println("ddc desc: ", result.Desc) +// 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) +fmt.Println("ddc accountStatus: ", result.Status) +// 账号类型(super账号:Super;普通账号:Common) +fmt.Println("ddc accountType: ", result.AccountType) +fmt.Println("ddc databasePrivileges: ", result.DatabasePrivileges) + +// RDS产品 +result, err = client.GetAccount(instanceId,accountName) + if err != nil { + fmt.Printf("get account error: %+v\n", err) + return +} + +// 获取account的信息 +fmt.Println("ddc accountName: ", result.AccountName) +fmt.Println("ddc desc: ", result.Desc) +// 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) +fmt.Println("ddc accountStatus: ", result.Status) +// 账号类型(super账号:Super;普通账号:Common) +fmt.Println("ddc accountType: ", result.AccountType) +fmt.Println("ddc databasePrivileges: ", result.DatabasePrivileges) +``` + +## 查询账号列表 + +使用以下代码可以查询指定实例的账号列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +result, err := client.ListAccount(instanceId) + if err != nil { + fmt.Printf("list account error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, account := range result.Accounts { + fmt.Println("ddc accountName: ", account.AccountName) + fmt.Println("ddc desc: ", account.Desc) + // 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) + fmt.Println("ddc accountStatus: ", account.Status) + // 账号类型(super账号:Super;普通账号:Common) + fmt.Println("ddc accountType: ", account.AccountType) + fmt.Println("ddc databasePrivileges: ", account.DatabasePrivileges) +} + +// RDS +result, err = client.ListAccount(instanceId) + if err != nil { + fmt.Printf("list account error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, account := range result.Accounts { + fmt.Println("ddc accountName: ", account.AccountName) + fmt.Println("ddc desc: ", account.Desc) + // 账号状态(创建中:Creating;可用中:Available;更新中:Updating;删除中:Deleting;已删除:Deleted) + fmt.Println("ddc accountStatus: ", account.Status) + // 账号类型(super账号:Super;普通账号:Common) + fmt.Println("ddc accountType: ", account.AccountType) + fmt.Println("ddc databasePrivileges: ", account.DatabasePrivileges) +} +``` + +## 删除特定账号信息 + +使用以下代码可以删除特定账号信息。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +err := client.DeleteAccount(instanceId,accountName) + if err != nil { + fmt.Printf("delete account error: %+v\n", err) + return +} +fmt.Printf("delete account success\n") + +// RDS +err = client.DeleteAccount(instanceId,accountName) + if err != nil { + fmt.Printf("delete account error: %+v\n", err) + return +} +fmt.Printf("delete account success\n") +``` + +# 参数管理 + +## 实例参数列表 +使用以下代码可以查询实例参数列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +resp, err := client.ListParameters(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取参数列表信息 +for _, e := range resp.Parameters { + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc defaultValue: ", e.DefaultValue) + fmt.Println("ddc value: ", e.Value) + fmt.Println("ddc pendingValue: ", e.PendingValue) + fmt.Println("ddc type: ", e.Type) + fmt.Println("ddc dynamic: ", e.Dynamic) + fmt.Println("ddc modifiable: ", e.Modifiable) + fmt.Println("ddc allowedValues: ", e.AllowedValues) + fmt.Println("ddc desc: ", e.Desc) +} + +// RDS +resp, err := client.ListParameters(instanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取参数列表信息 +for _, e := range resp.Parameters { + fmt.Println("rds name: ", e.Name) + fmt.Println("rds defaultValue: ", e.DefaultValue) + fmt.Println("rds value: ", e.Value) + fmt.Println("rds pendingValue: ", e.PendingValue) + fmt.Println("rds type: ", e.Type) + fmt.Println("rds dynamic: ", e.Dynamic) + fmt.Println("rds modifiable: ", e.Modifiable) + fmt.Println("rds allowedValues: ", e.AllowedValues) + fmt.Println("rds desc: ", e.Desc) +} +``` + +## 修改实例参数 +使用以下代码可以修改云数据库 DDC 的参数配置。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.UpdateParameterArgs{ + Parameters: []ddcrds.KVParameter{ + { + Name: "connect_timeout", + Value: "15", + }, + }, + // 执行参数修改任务的方式,默认为0; 0 为立刻执行,1 为时间窗口执行 + WaitSwitch: 1, +} +// DDC +// Etag传入空字符串即可 +result, er := DDCRDS_CLIENT.UpdateParameter(instanceId, etag, args) +ExpectEqual(t.Errorf, nil, er) +if result != nil { + fmt.Println("update parameter task success: ", result.Result.TaskID) +} else { + fmt.Println("update parameter task success.") +} + +// RDS +// RDS修改参数时需要匹配Etag +res, err := client.ListParameters(e.InstanceId) +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +_, err := client.UpdateParameter(instanceId, res.Etag, args) +if err != nil { + fmt.Printf("update parameter: %+v\n", err) + return +} +fmt.Println("update parameter success.") +``` + +# 安全管理 + +## 白名单列表 +使用以下代码可以查询实例白名单列表。 +```go +import ( + "encoding/json" + ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +) + +// DDC +result, err := client.GetSecurityIps(instanceId) +if err != nil { + fmt.Printf("get securityIp list error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Printf("get securityIp list success\n") + +// RDS +result, err := client.GetSecurityIps(instanceId) +if err != nil { + fmt.Printf("get securityIp list error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Printf("get securityIp list success\n") +``` + +## 更新白名单 +使用以下代码可以更新一个实例下的白名单列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.UpdateSecurityIpsArgs{ +SecurityIps: []string{ + "%", + "192.0.0.1", + "192.0.0.2", + }, +} +// DDC +// Etag传入空字符串即可 +er := client.UpdateSecurityIps(instanceId, "",args) +if er != nil { + fmt.Printf("update securityIp list error: %+v\n", er) + return +} +fmt.Printf("update securityIp list success\n") + +// RDS +// Etag传入v0 +er = client.UpdateSecurityIps(instanceId, "v0",args) +if er != nil { + fmt.Printf("update securityIp list error: %+v\n", er) + return +} +fmt.Printf("update securityIp list success\n") +``` + +## 获取VPC下的安全组 +使用以下代码可以获取指定VPC下的安全组列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// vpcId := "vpc-j1vaxw1cx2mw" +securityGroups, err := client.ListSecurityGroupByVpcId(vpcId) +if err != nil { + fmt.Printf("list security group by vpcId error: %+v\n", err) + return +} +for _, group := range *securityGroups { + fmt.Println("securityGroup id: ", group.SecurityGroupID) + fmt.Println("name: ", group.Name) + fmt.Println("description: ", group.Description) + fmt.Println("associateNum: ", group.AssociateNum) + fmt.Println("createdTime: ", group.CreatedTime) + fmt.Println("version: ", group.Version) + fmt.Println("defaultSecurityGroup: ", group.DefaultSecurityGroup) + fmt.Println("vpc name: ", group.VpcName) + fmt.Println("vpc id: ", group.VpcShortID) + fmt.Println("tenantId: ", group.TenantID) +} +fmt.Println("list security group by vpcId success.") +``` + +## 获取实例已绑定安全组 +使用以下代码可以获取指定实例已绑定的安全组列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// instanceId := "ddc-m1h4mma5" +result, err := client.ListSecurityGroupByInstanceId(instanceId) +if err != nil { + fmt.Printf("list security group by instanceId error: %+v\n", err) + return +} +for _, group := range result.Groups { + fmt.Println("securityGroupId: ", group.SecurityGroupID) + fmt.Println("securityGroupName: ", group.SecurityGroupName) + fmt.Println("securityGroupRemark: ", group.SecurityGroupRemark) + fmt.Println("projectId: ", group.ProjectID) + fmt.Println("vpcId: ", group.VpcID) + fmt.Println("vpcName: ", group.VpcName) + fmt.Println("inbound: ", group.Inbound) + fmt.Println("outbound: ", group.Outbound) +} +fmt.Println("list security group by instanceId success.") +``` + +## 绑定安全组 +使用以下代码可以批量将指定的安全组绑定到实例上(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// instanceIds := []string{ +// "ddc-mjafcdu0", +// } +// securityGroupIds := []string{ +// "g-iutg5rtcydsk", +// } +args := &ddcrds.SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, +} + +err := client.BindSecurityGroups(args) +if err != nil { + fmt.Printf("bind security groups to instances error: %+v\n", err) + return +} +fmt.Println("bind security groups to instances success.") +``` +> 注意: +> - 实例状态必须为Available。 +> - 实例ID最多可以传入10个。 +> - 安全组ID最多可以传入10个。 +> - 每个实例最多可以绑定10个安全组。 + +## 解绑安全组 +使用以下代码可以从实例上批量解绑指定的安全组(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// instanceIds := []string{ +// "ddc-mjafcdu0", +// } +// securityGroupIds := []string{ +// "g-iutg5rtcydsk", +// } +args := &ddcrds.SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, +} + +err := client.UnBindSecurityGroups(args) +if err != nil { + fmt.Printf("unbind security groups to instances error: %+v\n", err) + return +} +fmt.Println("unbind security groups to instances success.") +``` +> 注意: +> - 实例状态必须为Available。 +> - 当前版本实例ID最多可以传入1个。 +> - 安全组ID最多可以传入10个。 + +# 备份管理 +## 获取备份列表 +使用以下代码可以获取一个实例下的备份列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +args := &ddcrds.GetBackupListArgs{} +resp, err := client.GetBackupList(instanceId, args) +if err != nil { + fmt.Printf("get backup list error: %+v\n", err) + return +} +// 返回标记查询的起始位置 +fmt.Println("ddc usedSpaceInMB: ", resp.UsedSpaceInMB) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("ddc freeSpaceInMB: ", resp.FreeSpaceInMB) +// 获取参数列表信息 +for _, e := range resp.Backups { + fmt.Println("ddc snapshotId: ", e.SnapshotId) + fmt.Println("ddc snapshotSizeInBytes: ", e.SnapshotSizeInBytes) + fmt.Println("ddc snapshotType: ", e.SnapshotType) + fmt.Println("ddc snapshotStatus: ", e.SnapshotStatus) + fmt.Println("ddc snapshotStartTime: ", e.SnapshotStartTime) + fmt.Println("ddc snapshotEndTime: ", e.SnapshotEndTime) +} + +// RDS +resp, err = client.GetBackupList(instanceId, args) +if err != nil { + fmt.Printf("get backup list error: %+v\n", err) + return +} +// 返回标记查询的起始位置 +fmt.Println("rds usedSpaceInMB: ", resp.UsedSpaceInMB) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("rds freeSpaceInMB: ", resp.FreeSpaceInMB) +// 获取参数列表信息 +for _, e := range resp.Backups { + fmt.Println("rds snapshotId: ", e.SnapshotId) + fmt.Println("rds snapshotSizeInBytes: ", e.SnapshotSizeInBytes) + fmt.Println("rds snapshotType: ", e.SnapshotType) + fmt.Println("rds snapshotStatus: ", e.SnapshotStatus) + fmt.Println("rds snapshotStartTime: ", e.SnapshotStartTime) + fmt.Println("rds snapshotEndTime: ", e.SnapshotEndTime) +} +``` + +## 创建备份 +使用以下代码创建实例备份(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +err := client.CreateBackup(instanceId) +if err != nil { + fmt.Printf("create backup error: %+v\n", err) + return +} +fmt.Printf("create backup success\n") +``` + +## 查询实例备份状态 +使用以下代码可以查询当前实例是否正在备份以及备份开始的时间(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +backupStatusResult, err := client.GetInstanceBackupStatus(instanceId) +if err != nil { + fmt.Printf("get backup status error: %+v\n", err) + return +} +fmt.Println("get backup status success.") +fmt.Println("instance is backuping: ", backupStatusResult.IsBackuping) +if backupStatusResult.IsBackuping { + fmt.Println("instance backup start time: ", backupStatusResult.SnapshotStartTime) +} +``` + +## 备份详情 +使用以下代码可以查询一个备份的详情(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +resp, err := client.GetBackupDetail(instanceId, snapshotId) +if err != nil { + fmt.Printf("get backup detail error: %+v\n", err) + return +} + +fmt.Println("ddc snapshotId: ", resp.Snapshot.SnapshotId) +fmt.Println("ddc snapshotSizeInBytes: ", resp.Snapshot.SnapshotSizeInBytes) +fmt.Println("ddc snapshotType: ", resp.Snapshot.SnapshotType) +fmt.Println("ddc snapshotStatus: ", resp.Snapshot.SnapshotStatus) +fmt.Println("ddc snapshotStartTime: ", resp.Snapshot.SnapshotStartTime) +fmt.Println("ddc snapshotEndTime: ", resp.Snapshot.SnapshotEndTime) +fmt.Println("ddc downloadUrl: ", resp.Snapshot.DownloadUrl) +fmt.Println("ddc downloadExpires: ", resp.Snapshot.DownloadExpires) +``` + +## 设置备份策略 +使用以下代码设置实例的备份策略(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +args := &ddcrds.BackupPolicy{ + // 以英文半角逗号分隔的备份时日间,周日为第一天,取值0 + BackupDays: "0,1,2,3,5,6", + // 备份开始时间,使用UTC时间 + BackupTime: "17:00:00Z", + // 是否启用备份数据持久化 + Persistent: true, + // 持久化天数,范围7-730天;未启用则为0或不填 + ExpireInDays: 7, +} +err := client.ModifyBackupPolicy(instanceId, args) +if err != nil { + fmt.Printf("modify instance's backupPolicy error: %+v\n", err) + return +} +fmt.Printf("modify instance's backupPolicy success\n") +``` + +## binlog列表 +使用以下代码可以获取一个实例下的binlog列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// DDC +// datetime UTC时间 +// 获取了两天前的日志备份 +datetime := time.Now(). + AddDate(0, 0, -2). + Format("2006-01-02T15:04:05Z") +resp, err := client.GetBinlogList(instanceId, datetime) +if err != nil { + fmt.Printf("get binlog list error: %+v\n", err) + return +} +// 获取binlog列表信息 +for _, e := range resp.Binlogs { + fmt.Println("ddc binlogId: ", e.BinlogId) + fmt.Println("ddc binlogSizeInBytes: ", e.BinlogSizeInBytes) + fmt.Println("ddc binlogStatus: ", e.BinlogStatus) + fmt.Println("ddc binlogStartTime: ", e.BinlogStartTime) + fmt.Println("ddc binlogEndTime: ", e.BinlogEndTime) +} +``` + +## binlog 详情 +使用以下代码可以查询一个binlog详情(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +resp, err := client.GetBinlogDetail(instanceId, binlogId) +if err != nil { + fmt.Printf("get binlog detail error: %+v\n", err) + return +} + +fmt.Println("ddc binlogId: ", resp.Binlog.BinlogId) +fmt.Println("ddc binlogSizeInBytes: ", resp.Binlog.BinlogSizeInBytes) +fmt.Println("ddc binlogStatus: ", resp.Binlog.BinlogStatus) +fmt.Println("ddc binlogStartTime: ", resp.Binlog.BinlogStartTime) +fmt.Println("ddc binlogEndTime: ", resp.Binlog.BinlogEndTime) +fmt.Println("ddc downloadUrl: ", resp.Binlog.DownloadUrl) +fmt.Println("ddc downloadExpires: ", resp.Binlog.DownloadExpires) +``` + +# 日志管理 + +## 日志列表 +使用以下代码可以获取一个实例下的错误日志或者慢日志列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// import "time" + +// datetime UTC时间 +// 获取两天前的错误日志,传入日期即可 +date := time.Now(). +AddDate(0, 0, -2). +Format("2006-01-02") +args := &ddcrds.ListLogArgs{ + // 日志类型 错误日志为error 慢日志为slow + LogType: "error", + Datetime: date, +} +logs, err := client.ListLogByInstanceId(instanceId, args) +if err != nil { + fmt.Printf("list logs of instance error: %+v\n", err) + return +} +fmt.Println("list logs of instance success.") +for _, log := range *logs { + fmt.Println("id: ", log.LogID) + fmt.Println("size: ", log.LogSizeInBytes) + fmt.Println("start time: ", log.LogStartTime) + fmt.Println("end time: ", log.LogEndTime) +} +``` + +## 日志详情 +使用以下代码可以查询日志的详细信息,包括该日志文件有效的下载链接(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.GetLogArgs{ + // 下载链接有效时间,单位为秒 + ValidSeconds: 20, +} +logId := "errlog.202103091300" +log, err := client.GetLogById(instanceId, logId, args) +if err != nil { + fmt.Printf("get log detail of instance error: %+v\n", err) + return +} +fmt.Println("list logs of instances success.") +fmt.Println("id: ", log.LogID) +fmt.Println("size: ", log.LogSizeInBytes) +fmt.Println("start time: ", log.LogStartTime) +fmt.Println("end time: ", log.LogEndTime) +// 日志文件下载链接 +fmt.Println("download url: ", log.DownloadURL) +// 下载链接截止该时间有效 +fmt.Println("download url expires: ", log.DownloadExpires) +``` + +## 访问日志 +通过此接口可以获取访问日志的下载链接,应获取昨天或者更早的⽇期的访问日志,当⽇的⽇志在第⼆天凌晨打包(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// import "time" + +downloadInfo, err := client.GetAccessLog(date) +if err != nil { + fmt.Printf("get access logs error: %+v\n", err) + return +} +fmt.Println("get access logs success.") +fmt.Println("mysql access logs link: ", downloadInfo.Downloadurl.Mysql) +fmt.Println("bbc access logs link: ", downloadInfo.Downloadurl.Bbc) +fmt.Println("bos access logs link: ", downloadInfo.Downloadurl.Bos) +``` +## 访问快照日志详情 +通过此接口可以获取访问快照日志的详情列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// import "time" + +args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, +} +result, err := client.SnapshotAccessDetail(args) +if err != nil { + fmt.Printf("get snapshot access detail error: %+v\n", err) + return +} +fmt.Printf("get snapshot access detail success\n %+v", result) +``` + +## 访问binlog日志详情 +通过此接口可以获取访问binlog日志的详情列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +// import "time" + +args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, +} +result, err := client.BinlogAccessDetail(args) +if err != nil { + fmt.Printf("get binlog access detail error: %+v\n", err) + return +} +fmt.Printf("get binlog access detail success\n %+v", result) +``` + +## 错误日志 +通过此接口可以获取错误日志(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.GetErrorLogsArgs{ + // 实例ID,必填参数 + InstanceId: "ddc-mp8lme9w", + // 开始时间,UTC 格式,必填参数 + StartTime: "2021-08-16T02:28:51Z", + // 结束时间,UTC 格式,必填参数 + EndTime: "2021-08-17T02:28:51Z", + // 分页页码,必填参数 + PageNo: 1, + // 分页大小,取值范围:[1-2000],必填参数 + PageSize: 10, + // 实例角色,取值:master(主实例-主节点)、backup(主实例-备节点)、slave(只读实例),必填参数 + Role: "master", + // 搜索关键词,选填参数 + KeyWord: "Aborted", +} + +errorLogsResponse, err := client.GetErrorLogs(args) +if err != nil { + fmt.Printf("get error logs error: %+v\n", err) + return +} +fmt.Println("get error logs success.") +fmt.Println("error logs count: ", errorLogsResponse.Count) +for _, errorLog := range errorLogsResponse.ErrorLogs { + fmt.Println("=================================================") + fmt.Println("error log instanceId: ", errorLog.InstanceId) + // 采集时间,本地时间 + fmt.Println("error log executeTime: ", errorLog.ExecuteTime) + fmt.Println("error log logLevel: ", errorLog.LogLevel) + // 日志内容 + fmt.Println("error log logText: ", errorLog.LogText) +} +``` + +## 慢日志 +通过此接口可以获取慢日志(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.GetSlowLogsArgs{ + // 实例ID,必填参数 + InstanceId: "ddc-mp8lme9w", + // 开始时间,UTC 格式,必填参数 + StartTime: "2021-08-16T02:28:51Z", + // 结束时间,UTC 格式,必填参数 + EndTime: "2021-08-17T02:28:51Z", + // 分页页码,必填参数 + PageNo: 1, + // 分页大小,取值范围:[1-2000],必填参数 + PageSize: 10, + // 实例角色,取值:master(主实例-主节点)、backup(主实例-备节点)、slave(只读实例),必填参数 + Role: "master", + // 数据库名列表,选填参数 + DbName: []string{"baidu_dba"}, + // 用户名列表,选填参数 + UserName: []string{"_root"}, + // 客户端IP列表,选填参数 + HostIp: []string{"localhost"}, + // SQL 语句,选填参数 + Sql: "update heartbeat set id=?, value=?", +} + +slowLogsResponse, err := client.GetSlowLogs(args) +if err != nil { + fmt.Printf("get slow logs error: %+v\n", err) + return +} +fmt.Println("get slow logs success.") +fmt.Println("slow logs count: ", slowLogsResponse.Count) +for _, slowLog := range slowLogsResponse.SlowLogs { + fmt.Println("=================================================") + fmt.Println("slow log instanceId: ", slowLog.InstanceId) + fmt.Println("slow log userName: ", slowLog.UserName) + fmt.Println("slow log dbName: ", slowLog.DbName) + fmt.Println("slow log hostIp: ", slowLog.HostIp) + // 执行时长,单位:秒 + fmt.Println("slow log queryTime: ", slowLog.QueryTime) + // 加锁时长,单位:秒 + fmt.Println("slow log lockTime: ", slowLog.LockTime) + // 解析行数 + fmt.Println("slow log rowsExamined: ", slowLog.RowsExamined) + // 返回行数 + fmt.Println("slow log rowsSent: ", slowLog.RowsSent) + fmt.Println("slow log sql: ", slowLog.Sql) + // 执行时间 + fmt.Println("slow log executeTime: ", slowLog.ExecuteTime) +} +``` + +# 其他 +## VPC列表 +使用以下代码可以查询vpc列表(仅支持DDC)。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +resp, err := client.ListVpc("ddc") +if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return +} +// 获取vpc列表信息 +for _, e := range* resp { + fmt.Println("ddc vpcId: ", e.VpcId) + fmt.Println("ddc shortId: ", e.ShortId) + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc cidr: ", e.Cidr) + fmt.Println("ddc status: ", e.Status) + fmt.Println("ddc createTime: ", e.CreateTime) + fmt.Println("ddc description: ", e.Description) + fmt.Println("ddc defaultVpc: ", e.DefaultVpc) + fmt.Println("ddc ipv6Cidr: ", e.Ipv6Cidr) + fmt.Println("ddc auxiliaryCidr: ", e.AuxiliaryCidr) + fmt.Println("ddc relay: ", e.Relay) +} +``` + +## 可用区列表 +使用以下代码可以获取可用区列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" +resp, err := client.GetZoneList("ddc") +if err != nil { + fmt.Printf("get zone list error: %+v\n", err) + return +} +for _, e := range resp.Zones { + fmt.Println("ddc zoneNames: ", e.ZoneNames) + fmt.Println("ddc apiZoneNames: ", e.ApiZoneNames) + fmt.Println("ddc available: ", e.Available) + fmt.Println("ddc defaultSubnetId: ", e.DefaultSubnetId) +} + +// RDS +resp, err = client.GetZoneList("rds") +if err != nil { + fmt.Printf("get zone list error: %+v\n", err) + return +} +for _, e := range resp.Zones { + fmt.Println("rds zoneNames: ", e.ZoneNames) + fmt.Println("rds apiZoneNames: ", e.ApiZoneNames) + fmt.Println("rds available: ", e.Available) + fmt.Println("rds defaultSubnetId: ", e.DefaultSubnetId) +} +``` + +## 子网列表 +使用以下代码可以获取一个实例下的子网列表。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +args := &ddcrds.ListSubnetsArgs{} +resp, err := client.ListSubnets(args, "ddc") +if err != nil { + fmt.Printf("get subnet list error: %+v\n", err) + return +} +for _, e := range resp.Subnets { + fmt.Println("ddc name: ", e.Name) + fmt.Println("ddc longId: ", e.LongId) + fmt.Println("ddc zoneName: ", e.ZoneName) + fmt.Println("ddc shortId: ", e.ShortId) + fmt.Println("ddc vpcId: ", e.VpcId) + fmt.Println("ddc vpcShortId: ", e.VpcShortId) + fmt.Println("ddc az: ", e.Az) + fmt.Println("ddc cidr: ", e.Cidr) + fmt.Println("ddc createdTime: ", e.CreatedTime) + fmt.Println("ddc updatedTime: ", e.UpdatedTime) +} + +// RDS +args = &ddcrds.ListSubnetsArgs{} +resp, err := client.ListSubnets(args, "rds") +if err != nil { + fmt.Printf("get subnet list error: %+v\n", err) + return +} +for _, e := range resp.Subnets { + fmt.Println("rds name: ", e.Name) + fmt.Println("rds longId: ", e.LongId) + fmt.Println("rds zoneName: ", e.ZoneName) + fmt.Println("rds shortId: ", e.ShortId) + fmt.Println("rds vpcId: ", e.VpcId) + fmt.Println("rds vpcShortId: ", e.VpcShortId) + fmt.Println("rds az: ", e.Az) + fmt.Println("rds cidr: ", e.Cidr) + fmt.Println("rds createdTime: ", e.CreatedTime) + fmt.Println("rds updatedTime: ", e.UpdatedTime) +} +``` + +## 实例版本回滚 +使用以下代码可以回滚实例版本。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.InstanceVersionRollBackArg{ + // 是否维护时间窗口执行 + WaitSwitch: true, +} +// DDC +result, err := client.InstanceVersionRollBack(instanceId, args) +if err != nil { + fmt.Printf("rollback instance version faild: %+v\n", err) + return +} +fmt.Printf("rollback instance version success. taskId:%s\n", result.TaskID) +``` + + +## 实例版本升级 +使用以下代码可以升级实例版本。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.InstanceVersionUpgradeArg{ + // 是否立即升级 + IsUpgradeNow: false, +} +// DDC +result, err := client.InstanceVersionUpgrade(instanceId, args) +if err != nil { + fmt.Printf("upgrade instance version faild: %+v\n", err) + return +} +fmt.Printf("upgrade instance version success. taskId:%s\n", result.TaskID) +``` + +## 获取只读实例的主从同步延迟 +使用以下代码可以获取只读实例的主从同步延迟。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +// DDC +result, err := client.GetInstanceSyncDelay(instanceId) +if err != nil { +fmt.Printf("get readonly instance syncDelay and syncStatus faild: %+v\n", err) + return +} +fmt.Println("get readonly instance syncDelay and syncStatus success.") +if result.Success { +fmt.Println("instance is SyncDelay: ", result.Result.SyncDelay) +fmt.Println("instance is SyncStatus: ", result.Result.SyncStatus) +} +``` + +## 开关只读实例主从同步延迟 +使用以下代码可以开关只读实例主从同步延迟。 +```go +// import ddcrds "github.com/baidubce/bce-sdk-go/services/ddc/v2" + +args := &ddcrds.InstanceSyncDelayReplicationArg{ + // 开启只读实例主从同步延迟,开启start,关闭stop + Action: "start", +} +// DDC +result, err := client.InstanceSyncDelayReplication(instanceId, args) +if err != nil { +fmt.Printf("instance syncDelay replication faild: %+v\n", err) +return +} +fmt.Printf("instance syncDelay replication success. success:%s\n", result.Success) +``` + + +# 错误处理 + +GO语言以error类型标识错误,DDC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | DDC服务返回的错误 + +用户使用SDK调用DDC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// ddcClient 为已创建的DDC Client对象 +result, err := client.ListDdcInstance() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向DDC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当DDC服务端出现异常时,DDC服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +DDC GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +DDC GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the DDC go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the DDC go sdk") +``` + +首次发布: + +- 支持创建账号、更新账号密码、更新账号备注、更新账号权限、查询账号列表、查询特定账号信息、删除特定账号信息 +- 支持创建数据库、更新数据库备注、查询数据库列表、查询特定数据库信息、删除特定数据库信息 +- 兼容RDS SDK中现有功能 \ No newline at end of file diff --git a/bce-sdk-go/doc/DNS.md b/bce-sdk-go/doc/DNS.md new file mode 100644 index 0000000..c42b891 --- /dev/null +++ b/bce-sdk-go/doc/DNS.md @@ -0,0 +1,463 @@ +# 公网DNS服务 + +# 概述 + +本文档主要介绍公网DNS GO SDK的使用。在使用本文档前,您需要先了解公网DNS的一些基本知识,并已开通了公网DNS服务。若您还不了解公网DNS,可以参考[产品描述](https://cloud.baidu.com/doc/DNS/s/Ajwvywvx3 )和[操作指南](https://cloud.baidu.com/doc/DNS/s/yjxkakdj4) 。 + +# 初始化 + +## 确认Endpoint + +内网DNS API 的服务域名为:dns.baidubce.com + +API支持HTTP和HTTPS两种调用方式。为了提升数据的安全性,建议通过HTTPS调用。 + + +## 获取密钥 + +要使用百度云公网DNS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问DNS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DNS Client + +DNS Client是公网DNS服务的客户端,为开发者与公网DNS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建DNS Client + +通过AK/SK方式访问DNS,用户可以参考如下代码新建一个DNS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/dns" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个DNSClient + DNSClient, err := DNS.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为公网DNS的服务地址。 + +### 使用STS创建DNS Client + +**申请STS token** + +DNS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问DNS,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建DNS Client** + +申请好STS后,可将STS Token配置到DNS Client中,从而实现通过STS Token创建DNS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建DNS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/dns" //导入DNS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建DNS服务的Client对象,Endpoint使用默认值 + DNSClient, err := DNS.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "dns.baidubce.com") + if err != nil { + fmt.Println("create dns client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + DNSClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置DNS Client时,STS的Endpoint需配置为http://sts.bj.baidubce.com。 + +# 配置HTTPS协议访问DNS + +DNS支持HTTPS传输协议,您可以通过在创建DNS Client对象时指定的Endpoint中指明HTTPS的方式,在DNS GO SDK中使用HTTPS访问DNS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dns" + +ENDPOINT := "https://dns.baidubce.com " //指明使用HTTPS协议 +AK, SK := , +DNSClient, _ := DNS.NewClient(AK, SK, ENDPOINT) +``` + +## 配置DNS Client + +如果用户需要配置DNS Client的一些细节的参数,可以在创建DNS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问DNS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dns" + +//创建DNS Client对象 +AK, SK := , +ENDPOINT := "dns.baidubce.com" +client, _ := DNS.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dns" + +AK, SK := , +ENDPOINT := "dns.baidubce.com" +client, _ := DNS.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/dns" + +AK, SK := , +ENDPOINT := "dns.baidubce.com" +client, _ := DNS.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问DNS时,创建的DNS Client对象的`Config`字段支持的所有参数如下表所示: + +| 配置项名称 | 类型 | 含义 | +|---------------------------|-----------------------|-------------------------| +| Endpoint | string | 请求服务的域名 | +| ProxyUrl | string | 客户端请求的代理地址 | +| Region | string | 请求资源的区域 | +| UserAgent | string | 用户名称,HTTP请求的User-Agent头 | +| Credentials | \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 | +| SignOption | \*auth.SignOptions | 认证字符串签名选项 | +| Retry | RetryPolicy | 连接重试策略 | +| ConnectionTimeoutInMillis | int | 连接超时时间,单位毫秒,默认20分钟 | + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建DNS Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +| 名称 | 类型 | 含义 | +|---------------|---------------------|-----------------------------| +| HeadersToSign | map[string]struct{} | 生成签名字符串时使用的HTTP头 | +| Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 | +| ExpireSeconds | int | 签名字符串的有效期 | + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 添加域名 + +```go +args := &CreateZoneRequest{ + Name: "sdkDNS.com", +} +err := DNSClient.CreateZone(args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询域名列表 + +```go +args := &ListZoneRequest{ + Marker: "123", +} +result, err := DNSClient.ListZone(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 删除域名 + +```go +err := DNSClient.DeleteZone("sdkDNS.com", "") +ExpectEqual(t.Errorf, nil, err) +``` + + +## 购买付费版域名 + +```go +args := &CreatePaidZoneRequest{ + Name: []string{"sdkDNS.com"}, + ProductVersion: "discount", + Billing: Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ + ReservationLength: 1, + }, + }, +} +err := DNSClient.CreatePaidZone(args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 免费版域名升级成普惠版 + +```go +args := &UpgradeZoneRequest{ + Name: []string{"sdkDNS.com"}, + Billing: Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ + ReservationLength: 1, + }, + }, +} +err := DNSClient.UpgradeZone(args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 域名续费 + +```go +args := &RenewZoneRequest{ + Billing: Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ + ReservationLength: 1, + }, + }, +} +err := DNSClient.RenewZone("sdkDNS.com", args, "") +ExpectEqual(t.Errorf, nil, err) +``` + + + +## 添加解析记录 + +```go +args := &CreateRecordRequest{ + Rr: "www", + Type: "A", + Value: "192.168.1.1", + } +err := DNSClient.CreateRecord("sdkDNS.com", args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询解析记录列表 + +```go +args := &ListRecordRequest{} +result, err := DNSClient.ListRecord("sdkDNS.com", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 修改解析记录 + +```go +args := &UpdateRecordRequest{ + Rr: "www", + Type: "A", + Value: "192.168.1.2", +} +err := DNSClient.UpdateRecord("sdkDNS.com", "1234", args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 开启解析记录 + +```go +err := DNSClient.UpdateRecordEnable("sdkDNS.com", "1234", "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 暂停解析记录 + +```go +err := DNSClient.UpdateRecordDisable("sdkDNS.com", "1234", "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除解析记录 + +```go +err := DNSClient.DeleteRecord("sdkDNS.com", "123", "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 添加线路组 + +```go +args := &AddLineGroupRequest{ + Name: "lineGroupName", + Lines: []string{"yunnan.ct"}, +} +err := DNSClient.AddLineGroup(args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 更新线路组 + +```go +args := &UpdateLineGroupRequest{ + Name: "lineGroupName", + Lines: []string{"yunnan.ct"}, +} +err := DNSClient.UpdateLineGroup("lineId", args, "") +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询线路组列表 + +```go +args := &ListLineGroupRequest{ + Marker: "123", +} +result, err := DNSClient.ListLineGroup(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 删除线路组 + +```go +err := DNSClient.DeleteLineGroup("lineId", "") +ExpectEqual(t.Errorf, nil, err) +``` + + + +# 错误处理 + +GO语言以error类型标识错误,DNS支持两种错误见下表: + +| 错误类型 | 说明 | +|-----------------|------------| +| BceClientError | 用户操作产生的错误 | +| BceServiceError | DNS服务返回的错误 | + +用户使用SDK调用DNS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + + +## 客户端异常 + +客户端异常表示客户端尝试向DNS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当DNS服务端出现异常时,DNS服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[DNS错误码](https://cloud.baidu.com/doc/DNS/s/lkk5elv58) + +## SDK日志 + +DNS GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +DNS GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the DNS go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the DNS go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/DOC.md b/bce-sdk-go/doc/DOC.md new file mode 100644 index 0000000..932d91e --- /dev/null +++ b/bce-sdk-go/doc/DOC.md @@ -0,0 +1,205 @@ +# DOC 服务 + +# 概述 + +本文档主要介绍 DOC GO SDK 的使用。在使用本文档前,您需要先了解 DOC 的一些基本知识,并已开通了 DOC 服务。若您还不了解 DOC,可以参考[产品描述](https://cloud.baidu.com/doc/DOC/s/Djwvypqoi)和 [API 参考](https://cloud.baidu.com/doc/DOC/s/Cjwvypy6e)。 + +# 初始化 + +## 确认Endpoint + +DOC 为全局服务,服务域名是 `doc.bj.baidubce.com`。 DOC API 支持 HTTP 和 HTTPS 两种协议。为了提升数据的安全性,建议使用HTTPS协议。SDK 默认使用 HTTPS 协议。 + +## 获取密钥 + +要使用百度云 DOC,您需要拥有一个有效的 AK(Access Key ID) 和 SK(Secret Access Key) 用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问 DOC 做签名验证。 + +可以通过如下步骤获得并了解您的 AK/SK 信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DOC Client + +DOC Client 是 DOC 服务的客户端,为开发者与 DOC 服务进行交互提供了一系列的方法。 + +### 使用 AK/SK 新建 DOC Client + +通过 AK/SK 方式访问 DOC,用户可以参考如下代码新建一个 DOC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/doc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 初始化一个DocClient + docClient, err := doc.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《[如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb)》。 + +## 配置 DOC Client + +如果用户需要配置 DOC Client 的一些细节的参数,可以在创建 DOC Client 对象之后,使用该对象的导出字段 `Config` 进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问 DOC 服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/doc" + +//创建 DOC Client对象 +AK, SK := , +client, _ := doc.NewClient(AK, SK) + +//代理使用本地的 8080 端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/doc" + +AK, SK := , +client, _ := doc.NewClient(AK, SK) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/doc" + +AK, SK := , +client, _ := doc.NewClient(AK, SK) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用 GO SDK 访问 DOC 时,创建的 DOC Client 对象的 `Config` 字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials` 字段使用 `auth.NewBceCredentials` 与 `auth.NewSessionBceCredentials` 函数创建,默认使用前者。 + 2. `SignOption` 字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry` 字段指定重试策略,目前支持两种:`NoRetryPolicy` 和 `BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 文档服务 +文档接口流程如下 +![百度云文档接口](https://doc.bce.baidu.com/bce-documentation/DOC/wendangjiekou_1.png) + +## 注册文档 +注册文档接口用于生成文档的唯一标识documentId、用于存储源文档文件的 BOS Bucket 相关信息。注册成功后,对应文档状态为 `UPLOADING`,对应 Bucket 对用户开放写权限,对于用户的 BOS 空间不可见。 + +注册文档是文档三步创建法(注册文档、上传BOS、发布文档)的第一步。 + +如下代码可以注册一个文档: + +```go +regParam := &api.RegDocumentParam{ + Title: , + Format: , +} + +if res, err := docClient.RegisterDocument(regParam); err != nil { + fmt.Println("failed to register document:", err) +} else { + fmt.Println("register document success, id:", res.DocumentId) +} +``` + +## 发布文档 +用于对已完成注册和 BOS 上传的文档进行发布处理。仅对状态为 `UPLOADING` 的文档有效。处理过程中,文档状态为 `PROCESSING`;处理完成后,状态转为 `PUBLISHED`。 + +发布文档是文档三步创建法(注册文档、上传BOS、发布文档)的第三步。 + +```go +err = docClient.PublishDocument() +``` + +## 查询文档 +通过文档的唯一标识 documentId 查询指定文档的详细信息。 + +```go +qRes, err := docClient.QueryDocument(, &api.QueryDocumentParam{Https: false}) +``` + +## 文档列表 +查询所有文档,以列表形式返回,支持用文档状态作为筛选条件进行筛选。 + +```go +listParam := &api.ListDocumentsParam{ + Status: api.DOC_STATUS_PUBLISHED, + MaxSize: 2, + } +res, err := docClient.ListDocuments(listParam) +``` + +## 阅读文档 +通过文档的唯一标识 documentId 获取指定文档的阅读信息,以便在 PC/Android/iOS 设备上阅读。仅对状态为 `PUBLISHED` 的文档有效。 +```go +rRes, err := docClient.ReadDocument(, &api.ReadDocumentParam{ExpireInSeconds: 3600}) +``` + +## 查询文档转码结果图片列表 +对于转码结果类型为图片的文档,通过本接口可以在文档转码完成后,获取转码结果图片的URL列表。 + +```go +if res, err := docClient.GetImages(); err != nil { + fmt.Println("failed to get images list:", err) +} else { + fmt.Println("get images list success, images count:", len(res.Images)) +} +``` + +## 删除文档 +删除文档,仅对状态 status 不是 `PROCESSING` 时的文档有效,清除文档占用的存储空间。 + +```go +err = docClient.DeleteDocument() +``` + +> **注意:** +> 文档一经删除,无法通过查询文档/文档列表等接口获取,并且无法阅读、下载,请谨慎操作。 \ No newline at end of file diff --git a/bce-sdk-go/doc/DTS.md b/bce-sdk-go/doc/DTS.md new file mode 100644 index 0000000..b70d49b --- /dev/null +++ b/bce-sdk-go/doc/DTS.md @@ -0,0 +1,684 @@ +# DTS服务 + +# 概述 + +本文档主要介绍DTS GO SDK的使用。在使用本文档前,您需要先了解DTS的一些基本知识,并已开通了DTS服务。若您还不了解DTS,可以参考[产品描述](https://cloud.baidu.com/doc/DTS/s/ujwvyzdzg)和[操作指南](https://cloud.baidu.com/doc/DTS/s/Qjwvz0ikk)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +DTS 是全局产品,不需要区分多地域,仅有一个 Endpoint。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +所有区域 | dts.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云DTS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问DTS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建DTS Client + +DTS Client是DTS服务的客户端,为开发者与DTS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建DTS Client + +通过AK/SK方式访问DTS,用户可以参考如下代码新建一个DTS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/dts" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个DTSClient + dtsClient, err := dts.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。 +第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + + +### 使用STS创建DTS Client + +**申请STS token** + +DTS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问DTS,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建DTS Client** + +申请好STS后,可将STS Token配置到DTS Client中,从而实现通过STS Token创建DTS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建DTS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/dts" //导入DTS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建DTS服务的Client对象,Endpoint使用默认值 + dtsClient, err := dts.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "dts.baidubce.com") + if err != nil { + fmt.Println("create dts client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + dtsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置DTS Client时,无论对应DTS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问DTS + +DTS支持HTTPS传输协议,您可以通过在创建DTS Client对象时指定的Endpoint中指明HTTPS的方式,在DTS GO SDK中使用HTTPS访问DTS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +ENDPOINT := "https://dts.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +dtsClient, _ := dts.NewClient(AK, SK, ENDPOINT) +``` + +## 配置DTS Client + +如果用户需要配置DTS Client的一些细节的参数,可以在创建DTS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问DTS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +//创建DTS Client对象 +AK, SK := , +ENDPOINT := "dts.baidubce.com" +client, _ := dts.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +AK, SK := , +ENDPOINT := "dts.baidubce.com" +client, _ := dts.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +AK, SK := , +ENDPOINT := "dts.baidubce.com" +client, _ := dts.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问DTS时,创建的DTS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建DTS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# DTS管理 + +DTS(Data Transmission Service)提供数据迁移、数据同步、数据订阅于一体的数据库数据传输服务,帮助您在业务不停服的前提下轻松完成数据库迁移,利用实时同步通道轻松构建异地容灾的高可用数据库架构。 + +## 创建DTS任务 + +使用以下代码可以创建一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.CreateDtsArgs{ + // 幂等性Token,是一个长度不超过64位的ASCII字符串,选填参数(关于幂等性,可以参考下面专门介绍幂等性的章节内容) + ClientToken: "aff0ea1548d30a5c711382b0cca7b45faff0ea1548d30a5c711382b0cca7b45f", + // 付费类型(后付费:postpay;),目前仅支持后付费 + ProductType: "postpay", + // 任务类型(数据传输任务:migration;),目前仅支持数据传输任务 + Type: "migration", + // 链路规格,取值:Samll、Medium、Large、Xlarge + Standard: "Large", + // 源端类型(百度智能云数据库:bcerds;自建数据存储:public;) + SourceInstanceType: "bcerds", + // 目标端类型(百度智能云数据库:bcerds;自建数据存储:public;) + TargetInstanceType: "bcerds", + // 跨地域标识(当源端、目标端类型均为百度智能云数据库且跨地域时:1;其他情况:0) + CrossRegionTag: 1, + // 同步方向(单向同步:single;双向同步:bidirect),目前仅支持单向同步 + DirectionType: "single", +} +result, err := client.CreateDts(args) +if err != nil { + fmt.Printf("create dts error: %+v\n", err) + return +} + +for _, e := range result.DtsTasks { + fmt.Println("create dts success, task id: ", e.DtsId) +} +``` + +## 删除DTS任务 + +使用以下代码可以删除一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +err := client.DeleteDts(dtsId) +if err != nil { + fmt.Printf("delete dts error: %+v\n", err) + return +} + +fmt.Println("delete dts success\n") +``` + +## 查看DTS任务详情 + +使用以下代码可以查看一个DTS任务详情 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +result, err := client.GetDetail(dtsId) +if err != nil { + fmt.Printf("get dts detail error: %+v\n", err) + return +} + +fmt.Println("dts taskName: ",result.TaskName) +fmt.Println("dts status: ",result.Status) +fmt.Println("dts region: ",result.Region) +fmt.Println("dts createTime: ",result.CreateTime) +// 若 result.DtsIdPos 为空字符串,则表示该任务是单向同步任务,否则为双向同步任务 +if result.DtsIdPos != "" { + // 正向数据流 ID + fmt.Println("dts dtsIdPos: ", result.DtsIdPos) + // 反向数据流 ID + fmt.Println("dts dtsIdNeg: ", result.DtsIdNeg) + // 正向数据流状态 + fmt.Println("dts dtsTaskPos status: ", result.DtsTaskPos.Status) + // 反向数据流状态 + fmt.Println("dts dtsTaskNeg status: ", result.DtsTaskNeg.Status) +} +``` + +## 查询DTS任务列表 + +使用以下代码可以查看DTS任务列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.ListDtsArgs{ + // 任务类型(单向同步类型:migration;双向同步类型:bidirect) + Type: "migration", + // 分页参数,初次请求无需设置,后续请求使用上次请求响应中的nextMarker + Marker: "dtsmb0p9j8hcb7as36gx", + // 分页参数,每页数据条数,默认为 10 + MaxKeys: 10, +} +result, err := client.ListDts(args) +if err != nil { + fmt.Printf("get dts list error: %+v\n", err) + return +} +// 是否截断(true:截断,表示还有下一页数据;false:最后一页数据;) +fmt.Println("isTruncated: ", result.IsTruncated) +// 分页参数,下一页标记 +fmt.Println("nextMarker: ", result.NextMarker) +for _, e := range result.Task { + fmt.Println("dtsId: ", e.DtsId) + fmt.Println("taskName: ", e.TaskName) + fmt.Println("status: ", e.Status) +} +``` + +## 分页查询DTS任务列表 + +使用以下代码可以查看DTS任务列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.ListDtsWithPageArgs{ + // 任务类型数组(单向同步类型:migration;双向同步类型:bidirect) + Types: []string{"migration"}, + // 过滤条件数组 + Filters: []dts.ListFilter{ + { + // 搜索关键字,支持前缀模糊匹配 + Keyword: "he", + // 搜索关键字类型,取值:dtsId、taskName、status、srcConnection.dbType、dtsConnection.dbType + KeywordType: "taskName", + }, + }, + // 分页页码,从 1 开始 + PageNo: 1, + // 分页大小,最大值 100 + PageSize: 10, + // 排序方式,取值:asc(升序)、desc(降序) + Order: "desc", + // 排序字段,取值:dtsId、taskName、createTime + OrderBy: "createTime", +} +result, err := client.ListDtsWithPage(args) +if err != nil { + fmt.Printf("get dts list with page error: %+v\n", err) + return +} + +for _, e := range result.Result { + fmt.Println("dtsId: ", e.DtsId) + fmt.Println("taskName: ", e.TaskName) + fmt.Println("status: ", e.Status) +} +``` + +## 配置DTS任务 + +使用以下代码可以配置一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +dataType :=[]string{"schema","base"} +srcConnection := dts.Connection{ + Region: "public", + DbType: "mysql", + DbUser: "usrname", + DbPass: "password", + DbPort: 3306, + DbHost: "180.76.120.207", + InstanceId: "rds-TOzVOznv", + InstanceType: "public", +} +dstConnection := dts.Connection{ + Region: "public", + DbType: "mysql", + DbUser: "usrname", + DbPass: "password", + DbPort: 3306, + DbHost: "180.76.120.207", + InstanceId: "rds-TOzVOznv", + InstanceType: "public", +} +schema := dts.Schema{ + Type: "db", + Src: "db1", + Dst: "db2", + Where: "", +} +schemaMapping := []dts.Schema{schema} +args := &dts.ConfigArgs{ + TaskName: "test", + DataType: dataType, + Type: "migration", + SrcConnection: srcConnection, + DstConnection: dstConnection, + SchemaMapping: schemaMapping, +} +result, err := client.ConfigDts("dtsmro61533558", args) +if err != nil { + fmt.Printf("config dts error: %+v\n", err) + return +} + +fmt.Println("config dts success, dtsId: ", result.DtsId) +``` + +## 前置检查 + +使用以下代码可以对一个DTS任务前置检查 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +result, err := client.PreCheck(taskId) +if err != nil { + fmt.Printf("preCheck dts error: %+v\n", err) + return +} + +fmt.Println("result success: ",result.Success) +``` + +## 查看前置检查结果 + +使用以下代码可以查看前置检查结果 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +result, err := client.GetPreCheck(taskId) +if err != nil { + fmt.Printf("get dts preCheck result error: %+v\n", err) + return +} + +fmt.Println("result success: ", result.Success) +for _, e := range result.Result { + fmt.Println("name: ", e.Name, "status: ", e.Status, "Message: ", e.Message, "Subscription: ", e.Subscription) +} +``` + +## 强制通过预检查(即前置检查) + +使用以下代码可以尝试强制通过一个DTS任务预检查,根据响应结果可以检查强制通过预检查操作是否成功 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +response, err := client.SkipPreCheck(taskId) +if err != nil { + fmt.Printf("skip preCheck dts error: %+v\n", err) + return +} + +// 若 success 为 true,表示强制通过预检查操作成功;否则,表示强制通过预检查操作失败; +fmt.Println("response success: ",response.Success) +// 强制通过预检查操作失败时失败原因 +fmt.Println("response result: ",response.Result) +``` + +## 启动DTS任务 + +使用以下代码可以启动一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +err := client.StartDts(taskId) +if err != nil { + fmt.Printf("start dts error: %+v\n", err) + return +} + +fmt.Println("start dts success\n") +``` + +## 暂停DTS任务 + +使用以下代码可以暂停一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +err := client.PauseDts(taskId) +if err != nil { + fmt.Printf("pause dts error: %+v\n", err) + return +} + +fmt.Println("pause dts success\n") +``` + +## 结束DTS任务 + +使用以下代码可以结束一个DTS任务 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +err := client.ShutdownDts(taskId) +if err != nil { + fmt.Printf("shut down dts error: %+v\n", err) + return +} + +fmt.Println("shutdown dts success\n") +``` + +## 更新任务名称 + +使用以下代码可以更新任务名称 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.UpdateTaskNameArgs { + TaskName: "go-sdkkk", +} +err := client.UpdateTaskName(taskId, args) +if err != nil { + fmt.Printf("update task name error: %+v\n", err) + return +} + +fmt.Println("update task name success\n") +``` + +## 变更链路规格 + +使用以下代码可以变更链路规格 +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.ResizeTaskStandardArgs { + // 幂等性Token,是一个长度不超过64位的ASCII字符串,选填参数(关于幂等性,可以参考下面专门介绍幂等性的章节内容) + ClientToken: "aff0ea1548d30a5c711382b0cca7b45faff0ea1548d30a5c711382b0cca7b45f", + // 链路规格,取值:Samll、Medium、Large、Xlarge + Standard: "Xlarge", +} +response, err := client.ResizeTaskStandard(taskId, args) +if err != nil { + fmt.Printf("resize task standard error: %+v\n", err) + return +} + +fmt.Println("response orderId: ", response.OrderId) +``` + +## 查询数据库Schema + +使用以下代码可以查询数据库Schema +```go +// import "github.com/baidubce/bce-sdk-go/services/dts" + +args := &dts.GetSchemaArgs { + Connection: dts.Connection{ + InstanceType: "bcerds", + DbType: "mysql", + InstanceId: "rdsm97xpxxxxu", + Region: "bj", + FieldWhitelist: "", + FieldBlacklist: "", + }, +} +response, err := client.GetSchema(args) +if err != nil { + fmt.Printf("get schema error: %+v\n", err) + return +} +fmt.Println("response success: ", response.Success) +fmt.Println("response result: ", response.Result) +``` + +# 错误处理 + +GO语言以error类型标识错误,DTS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | DTS服务返回的错误 + +用户使用SDK调用DTS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// dtsClient 为已创建的DTS Client对象 +result, err := client.ListDts() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向DTS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当DTS服务端出现异常时,DTS服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +DTS GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +DTS GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the DTS go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the DTS go sdk") +``` + +# 幂等性 + +## 幂等性概述 +Go SDK 在调用API时,很容易出现由于网络等问题导致客户端没收到响应连接就中断的情况。此时客户端无法得知服务器端是否收到了请求,重试又可能导致问题。例如一个创建实例的请求被多次发送就可能出现重复创建。对此,加入幂等性的机制来加以应对。 + +幂等性的意思是无论同一个请求被重复发送多次,其结果都和发送一次一样。 + +Go SDK 采用clientToken机制来保证API调用的幂等性,clientToken 是一个长度不超过64位的ASCII字符串,通常放在创建、删除类操作的参数中。 + + + +首次发布: + + - 支持创建DTS任务、删除DTS任务、配置DTS任务、查看DTS任务详情、查看DTS任务列表、前置检查、查看前置检查结果、启动DTS任务、暂停DTS任务、结束DTS任务 \ No newline at end of file diff --git a/bce-sdk-go/doc/ECCR.md b/bce-sdk-go/doc/ECCR.md new file mode 100644 index 0000000..8d00960 --- /dev/null +++ b/bce-sdk-go/doc/ECCR.md @@ -0,0 +1,282 @@ +# CCR服务 企业版 + +# 概述 + +本文档主要介绍CCR企业版 GO SDK的使用。在使用本文档前,您需要先了解CCR的一些基本知识,并已开通了CCR服务。若您还不了解CCR,可以参考[产品描述](https://cloud.baidu.com/doc/CCR/s/qk8gwqs4a)和[操作指南](https://cloud.baidu.com/doc/CCR/s/skw63yms7)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[CCR服务域名](https://cloud.baidu.com/doc/CCR/s/Fjwvy1fl4)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/CCR/s/Fjwvy1fl4)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|----------------------|--- +BJ | ccr.bj.baidubce.com | HTTP and HTTPS +GZ | ccr.gz.baidubce.com | HTTP and HTTPS +SU | ccr.su.baidubce.com | HTTP and HTTPS +HKG| ccr.hkg.baidubce.com | HTTP and HTTPS +FWH| ccr.fwh.baidubce.com | HTTP and HTTPS +BD | ccr.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云CCR,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问CCR做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建CCR Client + +CCR企业版 Client是CCR服务的客户端,为开发者与CCR服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建CCR Client + +通过AK/SK方式访问CCR,用户可以参考如下代码新建一个CCR Client: +```go +import ( + "github.com/baidubce/bce-sdk-go/services/eccr" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + AK, SK := , + + //用户指定的endpoint + ENDPOINT := "endpoint" + + // 初始化一个CCRClient + ccrClient, err := eccr.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`AK`对应控制台中的“Access Key ID”,`SK`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为CCR的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`ccr.bj.baidubce.com`。 + +### 使用STS创建CCR Client + +**申请STS token** + +CCR可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问CCR,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建CCR Client** + +申请好STS后,可将STS Token配置到CCR Client中,从而实现通过STS Token创建CCR Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建CCR Client对象: +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/eccr" //导入CCR服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建CCR服务的Client对象,Endpoint使用默认值 + ccrClient, err := eccr.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "ccr.bj.baidubce.com") + if err != nil { + fmt.Println("create ccr client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + ccrClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置CCR Client时,无论对应CCR服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问CCR + +CCR支持HTTPS传输协议,您可以通过在创建CCR Client对象时指定的Endpoint中指明HTTPS的方式,在CCR GO SDK中使用HTTPS访问CCR企业版服务: +```go +// import "github.com/baidubce/bce-sdk-go/services/ccr" +AK, SK := , +ENDPOINT := "https://ccr.bj.baidubce.com" //指明使用HTTPS协议 + +ccrClient, _ := eccr.NewClient(AK, SK, ENDPOINT) +``` + +## 配置CCR Client + +如果用户需要配置CCR Client的一些细节的参数,可以在创建CCR Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问CCR服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/ccr" + +//创建CCR Client对象 +AK, SK := , +ENDPOINT := "ccr.bj.baidubce.com" + +ccrClient, _ := eccr.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +ccrClient.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/ccr" + +AK, SK := , +ENDPOINT := "ccr.bj.baidubce.com" + +ccrClient, _ := eccr.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +ccrClient.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +ccrClient.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/ccr" + +AK, SK := , +ENDPOINT := "ccr.bj.baidubce.com" + +ccrClient, _ := eccr.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +ccrClient.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +ccrClient.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问CCR时,创建的CCR Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建CCR Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# CCR管理 + +百度智能云容器镜像服务(Cloud Container Registry,简称CCR)是面向容器镜像、Helm Chart等符合OCI规范的云原生制品安全托管以及高效分发平台。CCR支持在多个地域创建独享托管服务,具备多种安全保障;支持同步容器镜像等云原生制品,与容器引擎CCE等服务无缝集成,助力企业提升云原生容器应用交付效率。 + +> 注意: +> - 企业版实例托管的云原生应用制品(如容器镜像、Helm Chart)存储在您的 BOS Bucket 中,根据实际使用情况将产生存储和流量费用。 + +## 列举CCR实例 +使用以下代码可以列举CCR实例列表。 +```go +args := &ListInstancesArgs{ + KeywordType: "clusterName", + Keyword: "", + PageNo: 1, + PageSize: 10, + } + +resp, err := ccrClient.ListInstances(args) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:"+ string(s)) +``` + +## 查询单个CCR实例详情 +列举CCR实例列表。 +```go +instanceID := "instance-id" +resp, err := ccrClient.GetInstanceDetail(instanceID) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` + +## 获取私有网络列表 +查询私有网络列表。 +```go +instanceID := "instance-id" +resp, err := ccrClient.ListPrivateNetworks(instanceID) +if err != nil { + fmt.Println(err.Error()) + return +} + +s, _ := json.MarshalIndent(resp, "", "\t") +fmt.Println("Response:" + string(s)) +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/EIP.md b/bce-sdk-go/doc/EIP.md new file mode 100644 index 0000000..e03607d --- /dev/null +++ b/bce-sdk-go/doc/EIP.md @@ -0,0 +1,579 @@ +# EIP服务 + +# 概述 + +本文档主要介绍EIP GO SDK的使用。在使用本文档前,您需要先了解EIP的一些基本知识,并已开通了EIP服务。若您还不了解EIP,可以参考[产品描述](https://cloud.baidu.com/doc/EIP/s/fjwvz2pyz)和[操作指南](https://cloud.baidu.com/doc/EIP/s/Sjwvz2scd)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[EIP服务域名](https://cloud.baidu.com/doc/EIP/s/Djwvz32x7)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | eip.bj.baidubce.com | HTTP and HTTPS +GZ | eip.gz.baidubce.com | HTTP and HTTPS +SU | eip.su.baidubce.com | HTTP and HTTPS +HKG| eip.hkg.baidubce.com| HTTP and HTTPS +FWH| eip.fwh.baidubce.com| HTTP and HTTPS +BD | eip.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云EIP,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问EIP做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建EIP Client + +EIP Client是EIP服务的客户端,为开发者与EIP服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建EIP Client + +通过AK/SK方式访问EIP,用户可以参考如下代码新建一个EIP Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/eip" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个EIPClient + eipClient, err := eip.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为EIP的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`eip.bj.baidubce.com`。 + +### 使用STS创建EIP Client + +**申请STS token** + +EIP可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问EIP,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建EIP Client** + +申请好STS后,可将STS Token配置到EIP Client中,从而实现通过STS Token创建EIP Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建EIP Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/eip" //导入EIP服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建EIP服务的Client对象,Endpoint使用默认值 + eipClient, err := eip.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "eip.bj.baidubce.com") + if err != nil { + fmt.Println("create eip client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + eipClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置EIP Client时,无论对应EIP服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问EIP + +EIP支持HTTPS传输协议,您可以通过在创建EIP Client对象时指定的Endpoint中指明HTTPS的方式,在EIP GO SDK中使用HTTPS访问EIP服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +ENDPOINT := "https://eip.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +eipClient, _ := eip.NewClient(AK, SK, ENDPOINT) +``` + +## 配置EIP Client + +如果用户需要配置EIP Client的一些细节的参数,可以在创建EIP Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问EIP服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +//创建EIP Client对象 +AK, SK := , +ENDPOINT := "eip.bj.baidubce.com" +client, _ := eip.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +AK, SK := , +ENDPOINT := "eip.bj.baidubce.com" +client, _ := eip.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +AK, SK := , +ENDPOINT := "eip.bj.baidubce.com" +client, _ := eip.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问EIP时,创建的EIP Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建EIP Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# EIP管理 + +弹性公网IP EIP (Elastic IP) 作为一个独立的商品为用户提供公网带宽服务。 + +EIP的主要用途包括: + +- 通过EIP实例,用户可以获取公网带宽服务。 +- 用户可灵活配置EIP实例的计费模式,包括按需按带宽付费、按需按流量付费和包年包月按带宽付费三种。 +- 用户可将EIP实例与任意BCC或BLB实例绑定或解绑,匹配用户的不同业务场景。 + +> 注意: +> - 申请的EIP可用于绑定到任意BLB实例或BCC实例。 +> - 创建EIP需要实名认证,若未通过实名认证可以前往[百度智能云官网控制台](https://console.bce.baidu.com/qualify/#/qualify/result)中的安全认证下的实名认证中进行认证。 + +## 申请EIP + +使用以下代码可以申请一个EIP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.CreateEipArgs{ + // 指定eip的名称 + Name: "sdk-eip", + // 指定eip的公网带宽 + BandWidthInMbps: 10, + // 指定eip的付费信息 + Billing: &eip.Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByTraffic", + }, + // 预付费资源可以设置是否自动续费 + AutoRenewTimeUnit: "month", + AutoRenewTime: 1, + // 指定eip的标签键值对列表 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateEip(args) +if err != nil { + fmt.Printf("create eip error: %+v\n", err) + return +} + +fmt.Println("create eip success, eip: ", result.Eip) +``` + +> 注意: +> - 公网带宽,单位为Mbps。对于prepay以及bandwidth类型的EIP,限制为为1~200之间的整数,对于traffic类型的EIP,限制为1~1000之前的整数。 +> - EIP的名称要求长度1~65个字节,字母开头,可包含字母数字-_/.字符。若不传该参数,服务会自动生成name。 + +## EIP带宽扩缩容 + +使用以下代码可以对指定EIP的带宽进行扩缩容操作。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.ResizeEipArgs{ + // 指定eip的最新公网带宽 + NewBandWidthInMbps: 20, +} +err = client.ResizeEip(eip, args) +if err != nil { + fmt.Printf("resize eip error: %+v\n", err) + return +} + +fmt.Println("resize eip success.") +``` + +> 注意: +> - 扩缩容是一个异步过程,可以通过查询EIP列表查看EIP扩缩容状态是否完成 +> - 变更后的公网带宽,单位为Mbps。对于预付费(prepay)以及按带宽(bandwidth)类型的EIP,限制为1~200之间的整数,对于按流量(traffic)类型的EIP,限制为1~1000之间的整数。 + +## 绑定EIP + +使用以下代码可以实现EIP的绑定。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.BindEipArgs{ + // 指定eip被绑定的实例类型 + InstanceType: "BCC", + // 指定eip被绑定的实例id + InstanceId: instanceId, +} +if err := client.BindEip(eip, args); err != nil { + fmt.Printf("eip bind bcc error: %+v\n", err) + return +} + +fmt.Printf("eip bind bcc success\n") +``` + +> 注意: +> - 可用于绑定EIP到任意BLB实例或BCC实例。 +> - 只有available状态的EIP支持绑定操作。 +> - 被绑定的实例不能存在任何已有EIP绑定关系。 +> - 被绑定的实例不能处于欠费状态。 + +## 解绑EIP + +使用以下代码可以实现EIP的解绑。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +if err := client.UnBindEip(eip, clientToken); err != nil { + fmt.Printf("eip unbind error: %+v\n", err) + return +} + +fmt.Printf("eip unbind success\n") +``` + +> 注意: +> - 解除指定EIP的绑定关系。 +> - 被解绑的EIP必须已经绑定到某个实例。 + +## 释放EIP + +使用以下代码可以释放指定的EIP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +err = client.DeleteEip(eip, clientToken) +if err != nil { + fmt.Printf("delete eip error: %+v\n", err) + return +} + +fmt.Printf("delete eip success\n") +``` + +> 注意: +> - 释放指定EIP,被释放的EIP无法找回 +> - 如果EIP被绑定到任意实例,需要先解绑才能释放 +> - 预付费购买的EIP如需提前释放,请通过工单进行 + +## 查询EIP列表 + +使用以下代码可以查询EIP列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.ListEipArgs{ + // 指定要查询的eip + Eip: eip, + // 指定eip绑定的实例类型 + InstanceType: "BCC", + // 指定批量获取列表的查询的起始位置 + Marker: marker, + // 指定每页包含的最大数量,最大数量不超过1000。缺省值为1000 + MaxKeys: maxKeys, + // 指定实例状态,仅支持AVAILABLE, BINDED, PAUSED三种状态的查询 + Status: status, +} +result, err := client.ListEip(args) +if err != nil { + fmt.Printf("list eip error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("eip list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("eip list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("eip list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("eip list maxKeys: ", result.MaxKeys) +// 获取eip的列表信息 +for _, e := range result.EipList { + fmt.Println("eip name: ", e.Name) + fmt.Println("eip value: ", e.Eip) + fmt.Println("eip status: ", e.Status) + fmt.Println("eip eipInstanceType: ", e.EipInstanceType) + fmt.Println("eip instanceType: ", e.InstanceType) + fmt.Println("eip instanceId: ", e.InstanceId) + fmt.Println("eip shareGroupId: ", e.ShareGroupId) + fmt.Println("eip bandWidthInMbps: ", e.BandWidthInMbps) + fmt.Println("eip paymentTiming: ", e.PaymentTiming) + fmt.Println("eip billingMethod: ", e.BillingMethod) + fmt.Println("eip createTime: ", e.CreateTime) + fmt.Println("eip expireTime: ", e.ExpireTime) +} +``` + +> 注意: +> - 可根据多重条件查询EIP列表。 +> - 如只需查询单个EIP的详情,只需提供eip参数即可。 +> - 如只需查询绑定到指定类型实例上的EIP,提供instanceType参数即可。 +> - 如只需查询指定实例上绑定的EIP的详情,提供instanceType及instanceId参数即可。 +> - 若不提供查询条件,则默认查询覆盖所有EIP。 +> - 返回结果为多重条件交集的查询结果,即提供多重条件的情况下,返回同时满足所有条件的EIP。 +> - 以上查询结果支持marker分页,分页大小默认为1000,可通过maxKeys参数指定。 + +## EIP续费 + +使用以下代码可以为指定的EIP进行续费操作,延长过期时间。 +```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.PurchaseReservedEipArgs{ + // 设置eip的续费信息 + Billing: &eip.Billing{ + Reservation: &eip.Reservation{ + ReservationTimeUnit: "Month", + ReservationLength: 1, + }, + }, +} +if err := client.PurchaseReservedEip(eip, args); err != nil { + fmt.Printf("renew eip error: %+v\n", err) + return +} + +fmt.Printf("renew eip success.") +``` + +> 注意: EIP扩缩容期间不能进行续费操作。 + +## EIP自动续费 +使用以下代码可以为指定的EIP开启自动续费操作 + ```go +// import "github.com/baidubce/bce-sdk-go/services/eip" + +args := &eip.StartAutoRenewArgs{ + // 预付费资源可以设置是否自动续费 + AutoRenewTimeUnit: "month", + AutoRenewTime: 1, +} +if err := client.StartAutoRenew(eip, args); err != nil { + fmt.Printf("start auto renew eip error: %+v\n", err) + return +} + +fmt.Printf("start auto renew eip success.") +``` + +> 注意: +> - 仅预付费资源可以开通自动续费操作。 +> - 设置续费单位若AutoRenewTimeUnit可以为"month"和"year"。 +> - 若AutoRenewTimeUnit设置为month,AutoRenewTime支持1-9;若AutoRenewTimeUnit设置为year,AutoRenewTime支持1-3 + +## EIP停止自动续费 +使用以下代码可以为指定的EIP停止自动续费操作 + ```go + // import "github.com/baidubce/bce-sdk-go/services/eip" + if err := client.StopAutoRenew(eip, ""); err != nil { + fmt.Printf("stop auto renew eip error: %+v\n", err) + return + } + + fmt.Printf("stop auto renew eip success.") + ``` + +# 错误处理 + +GO语言以error类型标识错误,EIP支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | EIP服务返回的错误 + +用户使用SDK调用EIP相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// eipClient 为已创建的EIP Client对象 +args := &eip.ListEipArgs{} +result, err := client.ListEip(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向EIP发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当EIP服务端出现异常时,EIP服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[EIP错误码](https://cloud.baidu.com/doc/EIP/s/Ljwvz33m6) + +## SDK日志 + +EIP GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +EIP GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the EIP go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the EIP go sdk") +``` + + +# 版本变更记录 + +## v0.9.5 [2019-09-24] + +首次发布: + + - 支持申请EIP、EIP带宽扩缩容、绑定EIP、解绑EIP、释放EIP、查询EIP列表、EIP续费接口。 \ No newline at end of file diff --git a/bce-sdk-go/doc/ENIC.md b/bce-sdk-go/doc/ENIC.md new file mode 100644 index 0000000..e322360 --- /dev/null +++ b/bce-sdk-go/doc/ENIC.md @@ -0,0 +1,631 @@ +# ENIC服务 + +# 概述 + +本文档主要介绍ENIC GO SDK的使用。在使用本文档前,您需要先了解ENIC的一些基本知识,并已开通了ENIC服务。若您还不了解ENIC,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/sjwvytvh0 )和[操作指南](https://cloud.baidu.com/doc/VPC/s/0jwvytzll) 。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[ENDPOINT服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw )的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/ )。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云ENIC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问ENIC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建ENIC Client + +ENIC Client是ENIC服务的客户端,为开发者与ENIC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建ENIC Client + +通过AK/SK方式访问ENI,用户可以参考如下代码新建一个ENIC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/eni" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个ENICClient + enicClient, err := eni.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为ENIC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建ENIC Client + +**申请STS token** + +ENIC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问ENIC,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建ENIC Client** + +申请好STS后,可将STS Token配置到ENIC Client中,从而实现通过STS Token创建ENIC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建ENIC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/eni" //导入ENIC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建ENIC服务的Client对象,Endpoint使用默认值 + enicClient, err := eni.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create enic client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + enicClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置ENIC Client时,无论对应ENIC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问ENIC + +ENIC支持HTTPS传输协议,您可以通过在创建ENIC Client对象时指定的Endpoint中指明HTTPS的方式,在ENIC GO SDK中使用HTTPS访问ENIC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eni" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +enicClient, _ := eni.NewClient(AK, SK, ENDPOINT) +``` + +## 配置ENIC Client + +如果用户需要配置ENIC Client的一些细节的参数,可以在创建ENIC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问ENIC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eni" + +//创建ENIC Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := eni.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/eni" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := eni.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/eni" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := eni.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问ENIC时,创建的ENIC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建ENIC Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 获取公共服务 + +使用以下代码获取公共服务 +```go +// import "github.com/baidubce/bce-sdk-go/services/eni" + +result, err := client.GetServices() + if err != nil { + fmt.Printf("list public services error: %+v\n", err) + return + } +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 创建ENIC + +```go +args := &eni.CreateEniArgs{ + Name: "name", + SubnetId: "SUBNET_ID", + SecurityGroupIds: []string{ + "g-eqhqsbs84yww", + }, + PrivateIpSet: []PrivateIp{ + { + Primary: true, + PrivateIpAddress: "192.168.0.54", + }, + }, + Ipv6PrivateIpSet: []PrivateIp{ + { + Primary: false, + PrivateIpAddress: "2400:da00:e003:0:1d2:100:0:1", + }, + }, + Description: "go sdk test", + ClientToken: getClientToken(), +} +result, err := client.CreateEni(args) +if err != nil { + fmt.Printf("create enic error: %+v\n", err) + return +} +fmt.Println("create enic success, enic: ", result.EniId) +``` + + +## 更新ENIC + +```go +args := &eni.UpdateEniArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + Name: "name", + Description: "description", + } +res, err := client.UpdateEni(args) +if err != nil { + fmt.Printf("update enic error: %+v\n", err) + return +} +fmt.Printf("update enic success\n") +``` + +## 查询ENIC列表 + +```go +args := &eni.ListEniArgs{ + VpcId: "VPC_ID", +} +res, err := client.ListEni(args) +if err != nil { + fmt.Printf("list enic error: %+v\n", err) + return +} +r, err := json.Marshal(res) +fmt.Println("list eni success: ", string(r)) +} +``` + +## 删除指定ENIC + +```go +args := &eni.DeleteEniArgs{ + EniId: "eniId", + ClientToken: getClientToken(), +} +err := client.DeleteEni(args) +if err != nil { + fmt.Printf("delete enic error: %+v\n", err) + return +} +fmt.Printf("delete enic success\n") +} +``` + +## 查询ENIC详情 + +```go +result, err := client.GetEniDetail("eniId") +if err != nil { + fmt.Printf("select enic error: %+v\n", err) + return +} +r, err := json.Marshal(result) +fmt.Println("select enic success", string(r)) +``` + +## 增加ENIC内网IP + +```go +args := &client.EniPrivateIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PrivateIpAddress: "192.168.0.53", +} +result, err := client.AddPrivateIp(args) +if err != nil { + fmt.Printf("add enic private ip error: %+v\n", err) + return +} +r, err := json.Marshal(result) +fmt.Println("add private ip success", string(r)) +``` + +## 批量增加ENIC内网IP + +```go +args := &client.EniBatchPrivateIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PrivateIpAddresses: []string{ + "192.168.0.28", + "192.168.0.29", + }, +} +result, err := client.BatchAddPrivateIp(args) +if err != nil { + fmt.Printf("batch add enic private ip error: %+v\n", err) + return +} +r, err := json.Marshal(result) +fmt.Println("batch add private ip success", string(r)) +``` + +## 跨子网批量增加ENIC内网IP + +```go +args := &client.EniBatchAddPrivateIpCrossSubnetArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + SubnetId: "subnetId", + PrivateIpAddressCount: 2 +} +result, err := client.BatchAddPrivateIpCrossSubnet(args) +if err != nil { + fmt.Printf("cross subnet and batch add enic private ip error: %+v\n", err) + return +} +r, err := json.Marshal(result) +fmt.Println("cross subnet and batch add private ip success", string(r)) +``` + +## 删除ENIC内网IP + +```go +args := &client.EniPrivateIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PrivateIpAddress: "192.168.0.53", +} +err := client.DeletePrivateIp(args) +if err != nil { + fmt.Printf("delete enic private ip error: %+v\n", err) + return +} +fmt.Println("delete private ip success") +``` + +## 批量删除ENIC内网IP + +```go +args := &client.EniBatchPrivateIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PrivateIpAddresses: []string{ + "192.168.0.34", + "192.168.0.35", + }, +} +err := client.BatchDeletePrivateIp(args) +if err != nil { + fmt.Printf("batch delete enic private ip error: %+v\n", err) + return +} +fmt.Println("batch delete private ip success") +``` + +## ENIC挂载云主机 + +```go +args := &client.EniInstance{ + EniId: "eniId", + ClientToken: getClientToken(), + InstanceId: "192.168.0.55", +} +err := client.AttachEniInstance(args) +if err != nil { + fmt.Printf("enic attach instance error: %+v\n", err) + return +} +fmt.Println("attach instance success") +``` + +## ENIC卸载云主机 + +```go +args := &EniInstance{ + EniId: "eniId", + ClientToken: getClientToken(), + InstanceId: "192.168.0.55", +} +err := client.DetachEniInstance(args) +if err != nil { + fmt.Printf("enic detach instance error: %+v\n", err) + return +} +fmt.Println("detach instance success") +``` + +## ENIC绑定EIP + +```go +args := &client.BindEniPublicIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PrivateIpAddress:"192.168.0.54", + PublicIpAddress:"ip", +} +err := client.BindEniPublicIp(args) +if err != nil { + fmt.Printf("enic bind public ip error: %+v\n", err) + return +} +fmt.Println("enic bind public ip success") +``` + +## ENIC解绑EIP + +```go +args := &client.UnBindEniPublicIpArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + PublicIpAddress:"ip", +} +err := client.UnBindEniPublicIp(args) +if err != nil { + fmt.Printf("enic unbind public ip error: %+v\n", err) + return +} +fmt.Println("enic unbind public ip success") +``` + +## ENIC更新普通安全组 + +```go +args := &client.UpdateEniSecurityGroupArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + SecurityGroupIds: []string{ + "sgId1", + "sgId2", + }, +} +err := client.UpdateEniSecurityGroup(args) +if err != nil { + fmt.Printf("enic update security group error: %+v\n", err) + return +} +fmt.Println("enic update security group success") +``` + +## ENIC更新企业安全组 + +```go +args := &client.UpdateEniEnterpriseSecurityGroupArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{ + "esgId1", + "esgId2", + }, +} +err := client.UpdateEniEnterpriseSecurityGroup(args) +if err != nil { + fmt.Printf("enic update enterprise security group error: %+v\n", err) + return +} +fmt.Println("enic update enterprise security group success") +``` + +## ENIC查询配额 + +```go +args := &client.EniQuoteArgs{ + EniId: "eniId", + ClientToken: getClientToken(), + InstanceId: "instanceId" +} +err := client.GetEniQuota(args) +if err != nil { + fmt.Printf("enic get quota info error: %+v\n", err) + return +} +fmt.Println("enic get quota info success") +``` + + +# 错误处理 + +GO语言以error类型标识错误,ENIC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | ENIC服务返回的错误 + +用户使用SDK调用ENIC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// enicClient 为已创建的ENIC Client对象 +args := &eni.ListEniArgs{ + VpcId: "VPC_ID", +} +res, err := client.ListEni(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向ENIC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当ENIC服务端出现异常时,ENIC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[ENIC错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +ENIC GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +ENIC GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the ENIC go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the ENIC go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/ESG.md b/bce-sdk-go/doc/ESG.md new file mode 100644 index 0000000..20ace6f --- /dev/null +++ b/bce-sdk-go/doc/ESG.md @@ -0,0 +1,498 @@ +# ESG服务 + +# 概述 + +本文档主要介绍ESG GO SDK的使用。在使用本文档前,您需要先了解ESG的一些基本知识,并已开通了ESG服务。若您还不了解ESG,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/sjwvytvh0 )和[操作指南](https://cloud.baidu.com/doc/VPC/s/Vjwvyu1sh) 。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[ENDPOINT服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw )的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/ )。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云ESG,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问ESG做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建ESG Client + +ESG Client是ESG服务的客户端,为开发者与ESG服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建ESG Client + +通过AK/SK方式访问ESG,用户可以参考如下代码新建一个ESG Client: + +```go +import ( +"github.com/baidubce/bce-sdk-go/services/esg" +) + +func main() { +// 用户的Access Key ID和Secret Access Key +ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + +// 用户指定的Endpoint +ENDPOINT := + +// 初始化一个ESGClient +esgClient, err := esg.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为ESG的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建ESG Client + +**申请STS token** + +ESG可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问ESG,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建ESG Client** + +申请好STS后,可将STS Token配置到ESG Client中,从而实现通过STS Token创建ESG Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建ESG Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/esg" //导入ESG服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建ESG服务的Client对象,Endpoint使用默认值 + esgClient, err := esg.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create esg client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + esgClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置ESG Client时,无论对应ESG服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问ESG + +ESG支持HTTPS传输协议,您可以通过在创建ESG Client对象时指定的Endpoint中指明HTTPS的方式,在ESG GO SDK中使用HTTPS访问ESG服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +esgClient, _ := esg.NewClient(AK, SK, ENDPOINT) +``` + +## 配置ESG Client + +如果用户需要配置ESG Client的一些细节的参数,可以在创建ESG Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问ESG服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +//创建ESG Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := esg.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := esg.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := esg.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问ESG时,创建的ESG Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建ESG Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 获取公共服务 + +使用以下代码获取公共服务 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +result, err := client.GetServices() + if err != nil { + fmt.Printf("list public services error: %+v\n", err) + return + } +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 创建ESG + +使用以下代码可以申请一个ESG。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.CreateEsgArgs{ + Name: "esgGoSdkTest", + Rules: []EnterpriseSecurityGroupRule{ + { + Action: "deny", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "udp", + Remark: "go sdk test", + SourceIp: "all", + }, + { + Action: "allow", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "icmp", + Remark: "go sdk test", + SourceIp: "all", + }, + }, + Desc: "go sdk test", + Tags: []model.TagModel{ + { + TagKey: "test", + TagValue: "", + }, + }, + } +result, err := client.CreateEsg(args) +if err != nil { + fmt.Printf("create esg error: %+v\n", err) + return +} + +fmt.Println("create esg success, esg: ", result.EnterpriseSecurityGroupId) +``` + + +## 查询ESG 列表 + +使用以下代码可以查询ESG列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.ListEsgArgs{ + InstanceId: "instanceId", + MaxKeys: 1000, + } +res, err := client.ListEsg(args) + if err != nil { + fmt.Printf("list esg error: %+v\n", err) + return + } + // 返回标记查询的起始位置 + fmt.Println("esg list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("esg list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("esg list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("esg list maxKeys: ", result.MaxKeys) + // 获取esg的列表信息 + for _, e := range res.EnterpriseSecurityGroups { + fmt.Println("esg id: ", e.Id) + fmt.Println("esg name: ", e.Name) + fmt.Println("esg description: ", e.Desc) + fmt.Println("esg createTime: ", e.CreateTime) + } +``` + +## 删除ESG + +使用以下代码可以释放指定的ESG。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.DeleteEsgArgs{ + EnterpriseSecurityGroupId: "esgId", +} +err := client.DeleteEsg(args) +if err != nil { + fmt.Printf("delete esg error: %+v\n", err) + return +} + +fmt.Printf("delete esg success\n") +``` + +> 注意: +> - 删除指定ESG,被释放的ESG无法找回 + +## 创建ESG规则 + +使用以下代码可以为ESG创建规则。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.CreateEsgRuleArgs{ + Rules: []EnterpriseSecurityGroupRule{ + { + Action: "deny", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "udp", + Remark: "go sdk test", + SourceIp: "all", + }, + }, + EnterpriseSecurityGroupId: "esg-id", + } +err := client.CreateEsgRules(args) +if err != nil { + fmt.Printf("create esg rules error: %+v\n", err) + return +} +fmt.Printf("create esg rules success\n") + +``` + +## 删除ESG规则 + +使用以下代码可以释放指定的ESG规则。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.DeleteEsgRuleArgs{ + EnterpriseSecurityGroupRuleId: "esgRuleId", +} +err := client.DeleteEsgRule(args) +if err != nil { + fmt.Printf("delete esg rule error: %+v\n", err) + return +} + +fmt.Printf("delete esg rule success\n") +``` + +> 注意: +> - 删除指定ESG规则,被释放的ESG规则无法找回 + +## 更新ESG规则 + +使用以下代码可以为ESG更新规则。 +```go +// import "github.com/baidubce/bce-sdk-go/services/esg" + +args := &esg.UpdateEsgRuleArgs{ + Priority: 900, + Remark: "go sdk test update", + EnterpriseSecurityGroupRuleId: "esgRuleId", +} +err := client.UpdateEsgRule(args) +if err != nil { + fmt.Printf("upda esg rule error: %+v\n", err) + return +} +fmt.Printf("update esg rule success\n") + +``` + + +# 错误处理 + +GO语言以error类型标识错误,ESG支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | ESG服务返回的错误 + +用户使用SDK调用ESG相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// esgClient 为已创建的ESG Client对象 +args := &esg.ListEsgArgs{} +result, err := client.ListEsg(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向ESG发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当ESG服务端出现异常时,ESG服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[ESG错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +ESG GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +ESG GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the ESG go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the ESG go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/ETGateway.md b/bce-sdk-go/doc/ETGateway.md new file mode 100644 index 0000000..1b945aa --- /dev/null +++ b/bce-sdk-go/doc/ETGateway.md @@ -0,0 +1,473 @@ +# 专线网关服务 + +# 概述 + +本文档主要介绍EtGateway GO SDK的使用。在使用本文档前,您需要先了解专线网关的一些基本知识,并已开通了专线网关服务。若您还不了解专线网关,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/Jjwvytz9j)和[操作指南](https://cloud.baidu.com/doc/VPC/s/Jjwvytz9j)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[专线网关服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云专线网关,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问专线网关做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建EtGateway Client + +EtGateway Client是专线网关服务的客户端,为开发者与专线网关服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建EtGateway Client + +通过AK/SK方式访问专线网关,用户可以参考如下代码新建一个EtGateway Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/etGateway" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个etGatewayClient + etGatewayClient, err := etGateway.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建EtGateway Client + +**申请STS token** + +EtGateway可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问专线网关,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建专线网关 Client** + +申请好STS后,可将STS Token配置到EtGateway Client中,从而实现通过STS Token创建EtGateway Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建专线网关 Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/etGateway" //导入专线网关服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建专线网关服务的Client对象,Endpoint使用默认值 + etGatewayClient, err := etGateway.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create etGateway client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + etGatewayClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置EtGatewayClient Client时,无论对应专线网关服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问专线网关 + +专线网关支持HTTPS传输协议,您可以通过在创建EtGateway Client对象时指定的Endpoint中指明HTTPS的方式,在EtGateway GO SDK中使用HTTPS访问专线网关服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +etGatewayClient, _ := etGateway.NewClient(AK, SK, ENDPOINT) +``` + +## 配置EtGateway Client + +如果用户需要配置EtGateway Client的一些细节的参数,可以在创建EtGateway Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问专线网关服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +//创建EtGateway Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := etGateway.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := bcc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := etGateway.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问专线网关时,创建的EtGateway Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建EtGateway Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + + +## 创建专线网关 + +使用以下代码可以申请一个专线网关。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" +args := &etGateway.CreateEtGatewayArgs{ + Name: "TestSDK", + Description: " test", + VpcId: "vpc-2pa2x0bjt26i", + Speed: 100, + ClientToken: getClientToken(), + } + result, err := client.CreateEtGateway(args) +if err != nil { + fmt.Printf("create etGateway error: %+v\n", err) + return +} + +fmt.Println("create etGateway success, etGateway: ", result.EtGatewayId) +``` + + +## 查询专线网关列表 + +使用以下代码可以查询专线网关列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +args := &etGateway.ListEtGatewayArgs{ + VpcId: "vpc-xsd65rcsp5ue", + } + result := &ListEtGatewayResult{} + result, err := client.ListEtGateway(args) + if err != nil { + fmt.Printf("list etGateway error: %+v\n", err) + return + } + // 返回标记查询的起始位置 + fmt.Println("etGateway list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("etGateway list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("etGateway list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("etGateway list maxKeys: ", result.MaxKeys) +``` + +## 查询专线网关详情 + +使用以下代码可以实现查询专线网关的详情信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + res := &etGateway.EtGatewayDetail{} + res, err := client.GetEtGatewayDetail("dcgw-vs1rvp9qy79f") + if err != nil { + fmt.Printf("get dcGateway detail error: %+v\n", err) + return + } + fmt.Println("etGatewayId id: ", result.EtGatewayId) +} +``` + +## 更新专线网关 + +使用以下代码可以实现专线网关的更新。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" +args := &etGateway.UpdateEtGatewayArgs{ + ClientToken: getClientToken(), + EtGatewayId: "dcgw-mx3annmentbu", + Name: "aaa", + Description: "test", + } + err := client.UpdateEtGateway(args) +if err != nil { + fmt.Printf("update etgateway error: %+v\n", err) + return +} +fmt.Printf("update etgateway success\n") +``` + +## 绑定专线 + +使用以下代码可以将专线网关绑定到专线上。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + +args := &etGateway.BindEtArgs{ + ClientToken: clientToken, + EtGatewayId: "dcgw-iiyc0ers2qx4", + EtId: "et-aaccd", + ChannelId: "sdxs", + LocalCidrs: []string{"192.168.0.1"}, + } + err := client.BindEt(args) +if err != nil { + fmt.Printf("bind etGateway error: %+v\n", err) + return +} +``` + + +## 解绑专线 + +使用以下代码可以将专线与专线网关进行解绑。 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" +if err := client.UnBindEt("dcgw-iiyc0ers2qx4", clientToken); err != nil { + fmt.Printf("unbind dc error: %+v\n", err) + return +} + +fmt.Printf("unbind dc success.") +``` + + +## 删除专线网关 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" +err := client.DeleteEtGateway("dcgw-iiyc0ers2qx4", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +if err != nil { + fmt.Printf("delete etGateway error: %+v\n", err) + return +} + +fmt.Printf("delete etGateway success\n") +``` +## 创建专线网关健康检查 +```go +// import "github.com/baidubce/bce-sdk-go/services/etGateway" + auto := true + args := &CreateHealthCheckArgs{ + ClientToken: getClientToken(), + EtGatewayId: "dcgw-iiyc0ers2qx4", + HealthCheckSourceIp: "1.2.3.4", + HealthCheckType: HEALTH_CHECK_ICMP, + HealthCheckPort: 80, + HealthCheckInterval: 60, + HealthThreshold: 3, + UnhealthThreshold: 4, + AutoGenerateRouteRule: &auto, + } + err := client.CreateHealthCheck(args) + if err != nil { + fmt.Printf("create etGateway healthcheck error: %+v\n", err) + return + } + fmt.Printf("create etGateway healthcheck success\n") +``` + + +# 错误处理 + +GO语言以error类型标识错误,专线网关支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | 专线网关服务返回的错误 + +用户使用SDK调用专线网关相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// etGatewayClient 为已创建的专线网关 Client对象 +args := &etGateway.ListEtGatewayArgs{} +result, err := client.ListEtGatewayArgs(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向专线网关发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当专线网关服务端出现异常时,专线网关服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[专线网关错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +EtGateway GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +EtGateway GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the etGateway go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the etGateway go sdk") +``` + + +# 版本变更记录 + +## v0.9.5 [2020-05-29] + +首次发布: + + - 支持创建专线网关、查询专线网关列表、查询专线网关详情、更新专线网关、释放专线网关、绑定/解绑专线、创建专线网关健康检查。 \ No newline at end of file diff --git a/bce-sdk-go/doc/GAIADB.md b/bce-sdk-go/doc/GAIADB.md new file mode 100644 index 0000000..a12e4f0 --- /dev/null +++ b/bce-sdk-go/doc/GAIADB.md @@ -0,0 +1,1225 @@ +# GAIADB服务 + +# 概述 + +本文档主要介绍GAIADB GO SDK的使用。在使用本文档前,您需要先了解GAIADB的一些基本知识。若您还不了解GAIADB,可以参考[产品描述](https://cloud.baidu.com/doc/GaiaDB/s/mkd45c3ap)和[入门指南](https://cloud.baidu.com/doc/GaiaDB/s/vkgkfur5q)。 + +相关参数说明可参考官方API文档[API参考](https://cloud.baidu.com/doc/GaiaDB/s/Zl82ton8n) + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[GAIADB访问域名](https://cloud.baidu.com/doc/GaiaDB/s/al84889rz)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/vkgkfur5q/)。 + +## 获取密钥 + +要使用百度云SCS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问SCS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建GAIADB Client + +GAIADB Client是GAIADB服务的客户端,为开发者与GAIADB服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建GAIADB Client + +通过AK/SK方式访问GAIADB,用户可以参考如下代码新建一个GAIADB Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/gaiadb" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个GAIADBClient + gaiadbClient, err := scs.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”。第三个参数`ENDPOINT`支持用户自己指定域名. + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`gaiadb.bj.baidubce.com`。 + +### 使用STS创建GAIADB Client + +**申请STS token** + +GAIADB可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问GAIADB,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建GAIADB Client** + +申请好STS后,可将STS Token配置到GAIADB Client中,从而实现通过STS Token创建GAIADB Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建GAIADB Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/gaiadb" //导入GAIADB服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建GAIADB服务的Client对象,Endpoint使用默认值 + gaiadbClient, err := gaiadb.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create GAIADB client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + gaiadbClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置GAIADB Client时,无论对应GAIADB服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问GAIADB + +GAIADB支持HTTPS传输协议,您可以通过在创建GAIADB Client对象时指定的Endpoint中指明HTTPS的方式,在GAIADB GO SDK中使用HTTPS访问SCS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/gaiadb" + +ENDPOINT := "https://gaiadb.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +gaiadbClient, _ := gaiadb.NewClient(AK, SK, ENDPOINT) +``` + +## 配置GAIADB Client + +如果用户需要配置GAIADB Client的一些细节的参数,可以在创建GAIADB Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问SCS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/gaiadb" + +//创建GAIADB Client对象 +AK, SK := , +ENDPOINT := "gaiadb.bj.baidubce.com" +client, _ := gaiadb.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/gaiadb" + +AK, SK := , +ENDPOINT := "gaiadb.bj.baidubce.com" +client, _ := gaiadb.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/gaiadb" + +AK, SK := , +ENDPOINT := "gaiadb.bj.baidubce.com" +client, _ := gaiadb.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问GAIADB时,创建的GAIADB Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建GAIADB Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + + +## 集群管理 + +### 创建集群 +使用以下代码可以创建Gaiadb集群 +```go +args := &gaiadb.CreateClusterArgs{ + ClientToken: getClientToken(), + Number: 1, + ProductType: "postpay", + InstanceParam: InstanceParam{ + EngineVersion: "8.0", + SubnetId: "sbn-na4tmg4v11hs", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, + AllocatedStorageInGB: 5120, + Engine: "MySQL", + VpcId: "vpc-it3v6qt3jhvj", + InstanceAmount: 2, + ProxyAmount: 2, + }, +} +result, err := GAIADB_CLIENT.CreateCluster(args) + +if err != nil { + fmt.Println("create cluster failed:", err) +} else { + fmt.Println("create cluster success: ", result) +} +``` +### 删除集群 +使用以下代码可以删除Gaiadb集群 +```go +err := GAIADB_CLIENT.DeleteCluster(clusterId) + +if err != nil { + fmt.Println("delete cluster failed:", err) +} else { + fmt.Println("delete cluster success") +} +``` +### 修改集群名称 +使用以下代码可以修改集群名称 +```go +args := &gaiadb.ClusterName{ + ClusterName: "cluster_test", +} +err := GAIADB_CLIENT.RenameCluster(clusterId, args) + +if err != nil { + fmt.Println("rename cluster failed:", err) +} else { + fmt.Println("rename cluster success") +} +``` + +### 变配集群 +使用以下代码可以变配Gaiadb集群 +```go +args := &gaiadb.ResizeClusterArgs{ + ResizeType: "resizeSlave", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, +} +result, err := GAIADB_CLIENT.ResizeCluster(clusterId, args) + +if err != nil { + fmt.Println("resize cluster failed:", err) +} else { + fmt.Println("resize cluster success: ", result) +} +``` + +### 获取集群列表 +使用以下代码可以获取集群列表 +```go +args := &gaiadb.Marker{ + Marker: "-1", + MaxKeys: 1000, +} +result, err := GAIADB_CLIENT.GetClusterList(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) + +if err != nil { + fmt.Println("get cluster list failed:", err) +} else { + fmt.Println("get cluster list success: ", result) +} +``` +### 获取集群详情 +使用以下代码可以获取集群详情 +```go +result, err := GAIADB_CLIENT.GetClusterDetail(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) + +if err != nil { + fmt.Println("get cluster detail failed:", err) +} else { + fmt.Println("get cluster detail success: ", result) +} +``` +### 查询集群存储容量 +使用以下代码可以查询集群存储容量 +```go +result, err := GAIADB_CLIENT.GetClusterCapacity(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) + +if err != nil { + fmt.Println("get cluster capacity failed:", err) +} else { + fmt.Println("get cluster capacity success: ", result) +} +``` + +### 查询新购集群价格 +使用以下代码可以查询新购集群价格 +```go +args := &gaiadb.QueryPriceArgs{ + Number: 1, + InstanceParam: InstanceInfo{ + ReleaseVersion: "5.7", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, + AllocatedStorageInGB: 5120, + InstanceAmount: 2, + ProxyAmount: 2, + }, + ProductType: "postpay", +} +result, err := GAIADB_CLIENT.QueryClusterPrice(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get cluster price failed:", err) +} else { + fmt.Println("get cluster price success: ", result) +} +``` + +### 查询变配集群价格 +使用以下代码可以查询变配集群价格 +```go +args := &gaiadb.QueryResizePriceArgs{ + ClusterId: clusterId, + ResizeType: "resizeSlave", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, +} +result, err := GAIADB_CLIENT.QueryResizeClusterPrice(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get resize cluster price failed:", err) +} else { + fmt.Println("get resize cluster price success: ", result) +} +``` + +### 重启计算节点 +使用以下代码可以重启计算节点 +```go +args := &gaiadb.RebootInstanceArgs{ + ExecuteAction: "executeNow", +} +err := GAIADB_CLIENT.RebootInstance(clusterId, instanceId, args) +if err != nil { + fmt.Println("reboot instance failed:", err) +} else { + fmt.Println("reboot instance success ") +} +``` +### 绑定标签 +使用以下代码可以绑定标签 +```go +args := &gaiadb.BindTagsArgs{ + Resources: []Resource{ + { + ResourceId: clusterId, + Tags: []Tag{ + { + TagKey: "testTagKey", + TagValue: "testTagValue", + }, + }, + }, + }, +} +err := GAIADB_CLIENT.BindTags(args) +if err != nil { + fmt.Println("bind tag failed:", err) +} else { + fmt.Println("bind tag success ") +} +``` + +### 主从切换 +使用以下代码可以主从切换 +```go +args := &gaiadb.ClusterSwitchArgs{ + ExecuteAction: "executeNow", + SecondaryInstanceId: instanceId, +} +result, err := GAIADB_CLIENT.ClusterSwitch(clusterId, args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("cluster switch failed:", err) +} else { + fmt.Println("cluster switch success: ", result) +} +``` +## 入口管理 +### 查询入口列表 +使用以下代码查询入口列表 +```go +result, err := GAIADB_CLIENT.GetInterfaceList(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get interface list failed:", err) +} else { + fmt.Println("get interface list success: ", result) +} +``` +### 更新入口域名 +使用以下代码更新入口域名 +```go +args := &gaiadb.UpdateDnsNameArgs{ + InterfaceId: "gaiadbm5h6ys_interface0000", + DnsName: "my.gaiadb.bj.baidubce.com", +} +err := GAIADB_CLIENT.UpdateDnsName(clusterId, args) +if err != nil { + fmt.Println("update dns name failed:", err) +} else { + fmt.Println("update dns name success") +} +``` +### 更新入口配置 +使用以下代码更新入口配置 +```go +args := &gaiadb.UpdateInterfaceArgs{ + InterfaceId: "gaiadbm5h6ys_interface0000", + Interface: InterfaceInfo{ + MasterReadable: 1, + AddressName: "addressname", + InstanceBinding: []string{ + "gaiadbymbrc8-primary-6f1cc3a2", + "gaiadbymbrc8-secondary-ec909467", + }, + }, +} +err := GAIADB_CLIENT.UpdateInterface(clusterId, args) +if err != nil { + fmt.Println("update interface failed:", err) +} else { + fmt.Println("update interface success") +} +``` +### 更新入口新节点自动加入配置 +使用以下代码更新入口新节点自动加入配置 +```go +args := &gaiadb.NewInstanceAutoJoinArgs{ + AutoJoinRequestItems: []AutoJoinRequestItem{ + { + NewInstanceAutoJoin: "off", + InterfaceId: "gaiadbymbrc8-primary-6f1cc3a2", + }, + }, +} +err := GAIADB_CLIENT.NewInstanceAutoJoin(clusterId, args) +if err != nil { + fmt.Println("new instance audo join failed:", err) +} else { + fmt.Println("new instance audo join success") +} +``` +## 账号管理 +### 创建账号 +使用以下代码创建账号 +```go +args := &gaiadb.CreateAccountArgs{ + AccountName: "testaccount", + Password: "testpassword", + AccountType: "common", + Remark: "testRemark", +} +err := GAIADB_CLIENT.CreateAccount(clusterId, args) +if err != nil { + fmt.Println("create account failed:", err) +} else { + fmt.Println("create account success") +} +``` +### 删除账号 +使用以下代码删除账号 +```go +err := GAIADB_CLIENT.DeleteAccount(clusterId, "testaccount") +if err != nil { + fmt.Println("delete account failed:", err) +} else { + fmt.Println("delete account success") +} +``` +### 查询账号详情 +使用以下代码查询账号详情 +```go +result, err := GAIADB_CLIENT.GetAccountDetail(clusterId, "testaccount") +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get account detail failed:", err) +} else { + fmt.Println("get account detail success") +} +``` +### 查询账号列表 +使用以下代码查询账号列表 +```go +result, err := GAIADB_CLIENT.GetAccountList(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get account list failed:", err) +} else { + fmt.Println("get account list success") +} +``` + + +### 更新账号备注 +使用以下代码更新账号备注 +```go +args := &gaiadb.RemarkArgs{ + Remark: "remark", + Etag: "v0", +} +result, err := GAIADB_CLIENT.UpdateAccountRemark(clusterId, accountName, args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("update account remark failed:", err) +} else { + fmt.Println("update account remark success") +} +``` + +### 更新账号白名单 +使用以下代码更新账号白名单 +```go +args := &gaiadb.AuthIpArgs{ + Action: "ipAdd", + Value: AuthIp{ + Authip: []string{"10.10.10.10"}, + Authbns: []string{}, + }, +} +err := GAIADB_CLIENT.UpdateAccountAuthIp(clusterId, "testaccount", args) +if err != nil { + fmt.Println("update account auth ip failed:", err) +} else { + fmt.Println("update account auth ip success") +} +``` + +### 更新账号权限 +使用以下代码更新账号权限 +```go +args := &gaiadb.PrivilegesArgs{ + DatabasePrivileges: []DatabasePrivilege{ + { + DbName: "testdb", + AuthType: "definePrivilege", + Privileges: []string{"UPDATE"}, + }, + }, + Etag: "v0", +} +err := GAIADB_CLIENT.UpdateAccountPrivileges(clusterId, "testaccount", args) +if err != nil { + fmt.Println("update account privileges failed:", err) +} else { + fmt.Println("update account privileges success") +} +``` +### 更新账号密码 +使用以下代码更新账号密码 +```go +args := &gaiadb.PasswordArgs{ + Password: "testpassword", + Etag: "v0", +} +err := GAIADB_CLIENT.UpdateAccountPassword(clusterId, "testaccount", args) +if err != nil { + fmt.Println("update account password failed:", err) +} else { + fmt.Println("update account password success") +} +``` +## 数据库管理 +### 创建数据库 +使用以下代码创建数据库 +```go +args := &gaiadb.CreateDatabaseArgs{ + DbName: "test_db", + CharacterSetName: "utf8", + Remark: "sdk test", +} +err := GAIADB_CLIENT.CreateDatabase(clusterId, args) +if err != nil { + fmt.Println("create database failed:", err) +} else { + fmt.Println("create database success") +} +``` +### 删除数据库 +使用以下代码删除数据库 +```go +err := GAIADB_CLIENT.DeleteDatabase(clusterId, "test_db") +if err != nil { + fmt.Println("delete database failed:", err) +} else { + fmt.Println("delete database success") +} +``` +### 查看数据库列表 +使用以下代码查看数据库列表 +```go +result, err := GAIADB_CLIENT.ListDatabase(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("list database failed:", err) +} else { + fmt.Println("list database success") +} +``` +## 备份管理 +### 创建备份 +使用以下代码创建备份 +```go +err := GAIADB_CLIENT.CreateSnapshot(clusterId) +if err != nil { + fmt.Println("create snapshot failed:", err) +} else { + fmt.Println("create snapshot success") +} +``` + +### 查询备份列表 +使用以下代码查询备份列表 +```go +result, err := GAIADB_CLIENT.ListSnapshot(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get snapshot list failed:", err) +} else { + fmt.Println("get snapshot list success") +} +``` +### 更新备份策略 +使用以下代码更新备份策略 +```go +args := &gaiadb.UpdateSnapshotPolicyArgs{ + DataBackupWeekDay: []string{"Monday"}, + DataBackupRetainStrategys: []DataBackupRetainStrategy{{ + StartSeconds: 0, + RetainCount: "8", + Precision: 86400, + EndSeconds: -691200, + }}, + DataBackupTime: "02:00:00Z", +} +err := GAIADB_CLIENT.UpdateSnapshotPolicy(clusterId, args) +if err != nil { + fmt.Println("update snapshot policy failed:", err) +} else { + fmt.Println("update snapshot policy success") +} +``` +## 安全管理 +### 更新 IP 白名单 +使用以下代码更新 IP 白名单 +```go +args := &gaiadb.UpdateWhiteListArgs{ + AuthIps: []string{"192.168.1.2"}, + Etag: "v0", +} +err := GAIADB_CLIENT.UpdateWhiteList(clusterId, args) +if err != nil { + fmt.Println("update white list failed:", err) +} else { + fmt.Println("update white list success") +} +``` +### 查询 IP 白名单 +使用以下代码查询 IP 白名单 +```go +result, err := GAIADB_CLIENT.GetWhiteList(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get white list failed:", err) +} else { + fmt.Println("get white list success: ", result) +} +``` +## 热活集群组管理 +### 创建热活集群组 +使用以下代码创建热活集群组 +```go +args := &gaiadb.CreateMultiactiveGroupArgs{ + LeaderClusterId: clusterId, + MultiActiveGroupName: "test_multiactive_group", +} +result, err := GAIADB_CLIENT.CreateMultiactiveGroup(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("create multiactive group failed:", err) +} else { + fmt.Println("create multiactive group success: ", result) +} +``` +### 删除热活集群组 +使用以下代码删除热活集群组 +```go +err := GAIADB_CLIENT.DeleteMultiactiveGroup(groupId) +if err != nil { + fmt.Println("delete multiactive group failed:", err) +} else { + fmt.Println("delete multiactive group success: ", result) +} +``` +### 更新热活集群组名称 +使用以下代码更新热活集群组名称 +```go +args := &gaiadb.RenameMultiactiveGroupArgs{ + MultiActiveGroupName: "test_multiactive_group", +} +err := GAIADB_CLIENT.RenameMultiactiveGroup(groupId, args) +if err != nil { + fmt.Println("rename multiactive group failed:", err) +} else { + fmt.Println("rename multiactive group success: ", result) +} +``` +### 查询热活集群组列表 +使用以下代码查询热活集群组列表 +```go +result, err := GAIADB_CLIENT.MultiactiveGroupList() +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("list multiactive group failed:", err) +} else { + fmt.Println("list multiactive group success: ", result) +} +``` +### 查询热活集群组详情 +使用以下代码查询热活集群组详情 +```go +result, err := GAIADB_CLIENT.MultiactiveGroupDetail("gaiagroup-0luzwo") +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get multiactive group detail failed:", err) +} else { + fmt.Println("get multiactive group detail success: ", result) +} +``` +### 查询从集群延迟信息 +使用以下代码查询从集群延迟信息 +```go +result, err := GAIADB_CLIENT.GetSyncStatus("gaiagroup-0luzwo", clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get sync status failed:", err) +} else { + fmt.Println("get sync status success: ", result) +} +``` +### 主从切换 +使用以下代码主从切换 +```go +args := &gaiadb.ExchangeArgs{ + ExecuteAction: "executeNow", + NewLeaderClusterId: clusterId, +} +err := GAIADB_CLIENT.GroupExchange("gaiagroup-0luzwo", args) +if err != nil { + fmt.Println("exchange failed:", err) +} else { + fmt.Println("exchange success: ", result) +} +``` +## 参数模板管理 +### 查询参数列表 +使用以下代码查询参数列表 +```go +result, err := GAIADB_CLIENT.GetParamsList(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get params list failed:", err) +} else { + fmt.Println("get params list success: ", result) +} +``` +### 查询参数更新历史 +使用以下代码查询参数更新历史 +```go +result, err := GAIADB_CLIENT.GetParamsHistory(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get params history failed:", err) +} else { + fmt.Println("get params history success: ", result) +} +``` +### 更新参数 +使用以下代码更新参数 +```go +args := &gaiadb.UpdateParamsArgs{ + Params: map[string]interface{}{ + "auto_increment_increment": "5", + }, + Timing: "now", +} +err := GAIADB_CLIENT.UpdateParams(clusterId, args) +if err != nil { + fmt.Println("update params failed:", err) +} else { + fmt.Println("update params success: ", result) +} +``` +## 参数模板管理 +### 查询参数模板列表 +使用以下代码查询参数模板列表 +```go +args := &gaiadb.ListParamTempArgs{ + Detail: 0, + Type: "mysql", + PageNo: 1, + PageSize: 10, +} +result, err := GAIADB_CLIENT.ListParamTemplate(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("list param template failed:", err) +} else { + fmt.Println("list param template success: ", result) +} +``` +### 保存为参数模板 +使用以下代码保存为参数模板 +```go +args := &gaiadb.ParamTempArgs{ + Type: "mysql", + Version: "8.0", + Description: "create by sdk", + Name: "sdk_test", + Source: clusterId, +} +err := GAIADB_CLIENT.SaveAsParamTemplate(args) +if err != nil { + fmt.Println("save as template failed:", err) +} else { + fmt.Println("save as template success: ", result) +} +``` +### 查询参数模板应用记录 +使用以下代码查询参数模板应用记录 +```go +result, err := GAIADB_CLIENT.GetTemplateApplyRecords(templateId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get apply record failed:", err) +} else { + fmt.Println("get apply record success: ", result) +} +``` +### 删除参数模板中的参数 +使用以下代码删除参数模板中的参数 +```go +args := &gaiadb.Params{ + Params: []string{"long_query_time"}, +} +err := GAIADB_CLIENT.DeleteParamsFromTemp(templateId, args) +if err != nil { + fmt.Println("delete params from template failed:", err) +} else { + fmt.Println("delete params from template success: ", result) +} +``` +### 更新参数模板 +使用以下代码更新参数模板 +```go +args := &gaiadb.UpdateParamTplArgs{ + Name: "test_template", + Description: "test_template_description", +} +err := GAIADB_CLIENT.UpdateParamTemplate(templateId, args) + +if err != nil { + fmt.Println("update params template failed:", err) +} else { + fmt.Println("update params template success: ", result) +} +``` +### 修改参数模板中的参数 +使用以下代码修改参数模板中的参数 +```go +args := &ModifyParamsArgs{ + Params: map[string]interface{}{ + "auto_increment_increment": "5", + "long_query_time": "6.6", + }, +} +err := GAIADB_CLIENT.ModifyParams(templateId, args) + +if err != nil { + fmt.Println("modify params failed:", err) +} else { + fmt.Println("modify params success: ", result) +} +``` +### 删除参数模板 +使用以下代码删除参数模板 +```go +err := GAIADB_CLIENT.DeleteParamTemplate(templateId) +if err != nil { + fmt.Println("delete param template failed:", err) +} else { + fmt.Println("delete param template success: ", result) +} +``` +### 创建参数模板 +使用以下代码创建参数模板 +```go +args := &gaiadb.CreateParamTemplateArgs{ + Name: "test_template", + Type: "mysql", + Version: "8.0", + Description: "test_template_description", +} +err := GAIADB_CLIENT.CreateParamTemplate(args) +if err != nil { + fmt.Println("create param template failed:", err) +} else { + fmt.Println("create param template success: ", result) +} +``` +### 查询参数模板详情 +使用以下代码查询参数模板详情 +```go +result, err := GAIADB_CLIENT.GetParamTemplateDetail(templateId, "0") +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get param template detail failed:", err) +} else { + fmt.Println("get param template detail success: ", result) +} +``` +### 查询参数模板更新历史 +使用以下代码查询参数模板更新历史 +```go +result, err := GAIADB_CLIENT.GetParamTemplateHistory(templateId, "addParam") +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get param template records failed:", err) +} else { + fmt.Println("get param template records success: ", result) +} +``` +### 应用参数模板 +使用以下代码应用参数模板 +```go +args := &gaiadb.ApplyParamTemplateArgs{ + Timing: "now", + Clusters: map[string]interface{}{ + "gaiadbk3pyxv": []interface{}{}, + }, +} +err := GAIADB_CLIENT.ApplyParamTemplate(templateId, args) +if err != nil { + fmt.Println("apply param template failed:", err) +} else { + fmt.Println("apply param template success: ", result) +} +``` +## 维护时间窗口管理 +### 更新时间窗口 +使用以下代码更新时间窗口 +```go +args := &gaiadb.UpdateMaintenTimeArgs{ + Period: "1,2,3", + StartTime: "03:00", + Duration: 1, +} +err := GAIADB_CLIENT.UpdateMaintenTime(clusterId, args) +if err != nil { + fmt.Println("update mainten time failed:", err) +} else { + fmt.Println("update mainten time success: ", result) +} +``` +### 查询时间窗口详情 +使用以下代码查询时间窗口详情 +```go +result, err := GAIADB_CLIENT.GetMaintenTime(clusterId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get mainten time detail failed:", err) +} else { + fmt.Println("get mainten time detail success: ", result) +} +``` +## 慢日志管理 +### 查询慢sql详情 +使用以下代码查询慢sql详情 +```go +args := &gaiadb.GetSlowSqlArgs{ + Page: "1", + PageSize: "10", +} +result, err := GAIADB_CLIENT.GetSlowSqlDetail(clusterId, args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get slow sql detail failed:", err) +} else { + fmt.Println("get slow sql detail success: ", result) +} +``` +### 查询慢sql优化建议 +使用以下代码查询慢sql优化建议 +```go +result, err := GAIADB_CLIENT.SlowSqlAdvice(clusterId, sqlId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get slow sql advice failed:", err) +} else { + fmt.Println("get slow sql advice success: ", result) +} +``` +## Binlog管理 +### 查询 binlog 备份详情 +使用以下代码查询 binlog 备份详情 +```go +args := &gaiadb.GetBinlogArgs{ + AppId: clusterId, + LogBackupType: "logical", +} +result, err := GAIADB_CLIENT.GetBinlogDetail(logId, args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get binlog detail failed:", err) +} else { + fmt.Println("get binlog detail success: ", result) +} +``` +### 查询 binlog 备份列表 +使用以下代码查询 binlog 备份列表 +```go +args := &gaiadb.GetBinlogListArgs{ + AppID: clusterId, + LogBackupType: "logical", + PageNo: 1, + PageSize: 10, +} +result, err := GAIADB_CLIENT.GetBinlogList(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get binlog list failed:", err) +} else { + fmt.Println("get binlog list success: ", result) +} +``` +## 任务管理 +### 立即执行任务 +使用以下代码立即执行任务 +```go +err := GAIADB_CLIENT.ExecuteTaskNow(taskId) +if err != nil { + fmt.Println("execute task now failed:", err) +} else { + fmt.Println("execute task now success: ", result) +} +``` +### 取消任务 +使用以下代码取消任务 +```go +err := GAIADB_CLIENT.CancelTask(taskId) +if err != nil { + fmt.Println("cancel task failed:", err) +} else { + fmt.Println("cancel task success: ", result) +} +``` +### 查询任务列表 +使用以下代码查询任务列表 +```go +args := &gaiadb.TaskListArgs{ + Region: "bj", + StartTime: "2023-09-11 16:00:00", +} +result, err := GAIADB_CLIENT.GetTaskList(args) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get task list failed:", err) +} else { + fmt.Println("get task list success: ", result) +} +``` +## 安全组管理 +### 通过VPC ID查询GaiaDB集群 +使用以下代码通过VPC ID查询GaiaDB集群 +```go +result, err := GAIADB_CLIENT.GetClusterByVpcId(vpcId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get cluster by vpcid failed:", err) +} else { + fmt.Println("get cluster by vpcid success: ", result) +} +``` +### 通过Lb ID查询GaiaDB集群 +使用以下代码通过Lb ID查询GaiaDB集群 +```go +result, err := GAIADB_CLIENT.GetClusterByLbId(lbIds) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get cluster by lbids failed:", err) +} else { + fmt.Println("get cluster by lbids success: ", result) +} +``` +## 其他 +### 查询订单信息 +使用以下代码查询订单信息 +```go +result, err := GAIADB_CLIENT.GetOrderInfo(orderId) +re, _ := json.Marshal(result) +fmt.Println(string(re)) +if err != nil { + fmt.Println("get order info failed:", err) +} else { + fmt.Println("get order info success: ", result) +} +``` +# 错误处理 + +GO语言以error类型标识错误,SCS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | GAIADB服务返回的错误 + +用户使用SDK调用GAIADB相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// gaiadbClient 为已创建的GAIADB Client对象 +clusterDetail, err := gaiadbClient.GetClusterDetail(clusterId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get instance detail success: ", clusterDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向GAIADB发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当GAIADB服务端出现异常时,GAIADB服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[GAIADB错误返回](https://cloud.baidu.com/doc/GaiaDB/s/al84889rz) + diff --git a/bce-sdk-go/doc/HAVIP.md b/bce-sdk-go/doc/HAVIP.md new file mode 100644 index 0000000..085aae9 --- /dev/null +++ b/bce-sdk-go/doc/HAVIP.md @@ -0,0 +1,511 @@ +# HAVIP服务 + +# 概述 + +本文档主要介绍HAVIP GO SDK的使用。在使用本文档前,您需要先了解HAVIP的一些基本知识,并已开通了HAVIP服务。若您还不了解HAVIP,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/sjwvytvh0 )和[操作指南](https://cloud.baidu.com/doc/VPC/s/dlcj263th) 。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[ENDPOINT服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw )的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/ )。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云HAVIP,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问HAVIP做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建HAVIP Client + +HAVIP Client是HAVIP服务的客户端,为开发者与HAVIP服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建HAVIP Client + +通过AK/SK方式访问HAVIP,用户可以参考如下代码新建一个HAVIP Client: + +```go +import ( +"github.com/baidubce/bce-sdk-go/services/havip" +) + +func main() { +// 用户的Access Key ID和Secret Access Key +ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + +// 用户指定的Endpoint +ENDPOINT := + +// 初始化一个HAVIPClient +havipClient, err := havip.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为HAVIP的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建HAVIP Client + +**申请STS token** + +HAVIP可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问HAVIP,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建HAVIP Client** + +申请好STS后,可将STS Token配置到HAVIP Client中,从而实现通过STS Token创建HAVIP Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建HAVIP Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/havip" //导入HAVIP服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建HAVIP服务的Client对象,Endpoint使用默认值 + havipClient, err := havip.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create havip client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + havipClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置HAVIP Client时,无论对应HAVIP服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问HAVIP + +HAVIP支持HTTPS传输协议,您可以通过在创建HAVIP Client对象时指定的Endpoint中指明HTTPS的方式,在HAVIP GO SDK中使用HTTPS访问HAVIP服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +havipClient, _ := havip.NewClient(AK, SK, ENDPOINT) +``` + +## 配置HAVIP Client + +如果用户需要配置HAVIP Client的一些细节的参数,可以在创建HAVIP Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问HAVIP服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +//创建HAVIP Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := havip.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := havip.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := havip.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问HAVIP时,创建的HAVIP Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建HAVIP Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 获取公共服务 + +使用以下代码获取公共服务 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +result, err := client.GetServices() + if err != nil { + fmt.Printf("list public services error: %+v\n", err) + return + } +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 创建HAVIP + +使用以下代码可以申请一个HAVIP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.CreateHaVipArgs{ + Name: "havipGoSdkTest", + Description: "go sdk test", + SubnetId: "sbn-mnnhbyd2tbqr", + PrivateIpAddress: "192.168.1.3", + } +result, err := client.CreateHaVip(args) +if err != nil { + fmt.Printf("create havip error: %+v\n", err) + return +} + +fmt.Println("create havip success, havip: ", result.HaVipId) +``` + + +## 查询HAVIP 列表 + +使用以下代码可以查询HAVIP列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.ListHaVipArgs{ + vpcId: "vpc-rndav7yrmbi6", + MaxKeys: 1000, +} +res, err := client.ListHaVip(args) + if err != nil { + fmt.Printf("list havip error: %+v\n", err) + return + } + // 返回标记查询的起始位置 + fmt.Println("havip list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("havip list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("havip list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("havip list maxKeys: ", result.MaxKeys) + // 获取havip的列表信息 + for _, e := range res.HaVips { + fmt.Println("havip id: ", e.HaVipId) + fmt.Println("havip name: ", e.Name) + fmt.Println("havip description: ", e.Description) + fmt.Println("havip createTime: ", e.CreateTime) + } +``` + +## 查询HAVIP详情 + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" +result, err := havip.GetHaVipDetail("havip-ied0wq4fs8va") +if err != nil { + fmt.Printf("select havip error: %+v\n", err) + return +} +r, err := json.Marshal(result) +fmt.Println("select havip success", string(r)) +``` + +## 更新HAVIP + +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" +args := &havip.UpdateHaVipArgs{ + HaVipId: "havip-swqx77k5f2cn", + Name: "name", + Description: "description", + } +res, err := client.UpdateHaVip(args) +if err != nil { + fmt.Printf("update havip error: %+v\n", err) + return +} +fmt.Printf("update havip success\n") +``` + +## 删除HAVIP + +使用以下代码可以释放指定的HAVIP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.DeleteHaVipArgs{ + HaVipId: "havip-swqx77k5f2cn", +} +err := client.DeleteHaVip(args) +if err != nil { + fmt.Printf("delete havip error: %+v\n", err) + return +} + +fmt.Printf("delete havip success\n") +``` + +> 注意: +> - 删除指定HAVIP,被释放的HAVIP无法找回 + +## HAVIP绑定实例 + +使用以下代码可以为HAVIP绑定实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.HaVipInstanceArgs{ + HaVipId: "havip-swqx77k5f2cn", + InstanceIds: []string{ + "eni-xgg3pfhw384n", + }, + InstanceType: "ENI", +} +err := client.HaVipAttachInstance(args) +if err != nil { + fmt.Printf("havip attach instance error: %+v\n", err) + return +} + +fmt.Printf("havip attach instance success\n") +``` + +## HAVIP解绑实例 + +使用以下代码可以为HAVIP解绑实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.HaVipInstanceArgs{ + HaVipId: "havip-swqx77k5f2cn", + InstanceIds: []string{ + "eni-xgg3pfhw384n", + }, + InstanceType: "ENI", +} +err := client.HaVipDetachInstance(args) +if err != nil { + fmt.Printf("havip detach instance error: %+v\n", err) + return +} + +fmt.Printf("havip detach instance success\n") +``` + +## HAVIP绑定公网IP + +使用以下代码可以为HAVIP绑定公网IP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.HaVipBindPublicIpArgs{ + HaVipId: "havip-swqx77k5f2cn", + PublicIpAddress: "110.242.73.217", +} +err := client.HaVipBindPublicIp(args) +if err != nil { + fmt.Printf("havip bind public ip error: %+v\n", err) + return +} + +fmt.Printf("havip bind public ip success\n") +``` + +## HAVIP解绑公网IP + +使用以下代码可以为HAVIP解绑公网IP。 +```go +// import "github.com/baidubce/bce-sdk-go/services/havip" + +args := &havip.HaVipUnbindPublicIpArgs{ + HaVipId: "havip-swqx77k5f2cn", +} +err := client.HaVipUnbindPublicIp(args) +if err != nil { + fmt.Printf("havip unbind public ip error: %+v\n", err) + return +} + +fmt.Printf("havip unbind public ip success\n") +``` + +# 错误处理 + +GO语言以error类型标识错误,HAVIP支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError |HAVIP服务返回的错误 + +用户使用SDK调用HAVIP相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// havipClient 为已创建的HAVIP Client对象 +args := &havip.ListHaVipArgs{} +result, err := client.ListHaVip(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向HAVIP发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当HAVIP服务端出现异常时,HAVIP服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[HAVIP错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +HAVIP GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +HAVIP GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the HAVIP go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the HAVIP go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/IAM.md b/bce-sdk-go/doc/IAM.md new file mode 100644 index 0000000..04e81ce --- /dev/null +++ b/bce-sdk-go/doc/IAM.md @@ -0,0 +1,941 @@ +# IAM服务 + +# 概述 + +本文档主要介绍普通型IAM GO SDK的使用。在使用本文档前,您需要先了解IAM的一些基本知识。若您还不了解IAM,可以参考[产品描述](https://cloud.baidu.com/doc/IAM/s/xjwvybxhv)和[应用场景](https://cloud.baidu.com/doc/IAM/s/Djwvybxus)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[IAM访问域名](https://cloud.baidu.com/doc/IAM/s/cjwvxnzix)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云IAM,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问IAM做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/#/iam/accesslist) + +## 新建IAM Client + +普通型IAM Client是IAM服务的客户端,为开发者与IAM服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建IAM Client + +通过AK/SK方式访问IAM,用户可以参考如下代码新建一个IAM Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/iam" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 初始化一个IAMClient + iamClient, err := iam.NewClient(AK, SK) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/IAM/s/ojwvynrqn)》。 + +### 使用STS创建IAM Client + +**申请STS token** + +IAM可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问IAM,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建IAM Client** + +申请好STS后,可将STS Token配置到IAM Client中,从而实现通过STS Token创建IAM Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建IAM Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/iam" //导入IAM服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建IAM服务的Client对象,Endpoint使用默认值 + iamClient, err := iam.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create iam client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + iamClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置IAM Client时,无论对应IAM服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问IAM + +IAM支持HTTPS传输协议,您可以通过在创建IAM Client对象时指定的Endpoint中指明HTTPS的方式,在IAM GO SDK中使用HTTPS访问IAM服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/iam" + +ENDPOINT := "https://iam.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +iamClient, _ := iam.NewClientWithEndpoint(AK, SK, ENDPOINT) +``` + +## 配置IAM Client + +如果用户需要配置IAM Client的一些细节的参数,可以在创建IAM Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问IAM服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/iam" + +//创建IAM Client对象 +AK, SK := , +ENDPOINT := "iam.bj.baidubce.com +client, _ := iam.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/iam" + +AK, SK := , +client, _ := iam.NewClient(AK, SK) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/iam" + +AK, SK := , +client, _ := iam.NewClient(AK, SK) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问IAM时,创建的IAM Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建IAM Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +# 主要接口 + +## 用户管理 + +### 创建用户 +通过以下代码可以创建子用户 + +```go + + name := "test-user-sdk-go" + args := &api.CreateUserArgs{ + Name: name, + Description: "description", + } + + result, err := client.CreateUser(args) + if err != nil { + fmt.Println("Create iam user failed", err) + } else { + fmt.Println("Create iam user success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[CreateUser创建用户](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7) + +### 查询用户 +通过以下代码可以查询单个子用户 +```go + name := "test-user-sdk-go" + result, err := client.GetUser(name) + if err != nil { + fmt.Println("Get iam user failed", err) + } else { + fmt.Println("Get iam user success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[GetUser查询用户](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E6%9F%A5%E8%AF%A2%E7%94%A8%E6%88%B7) + +### 更新用户 +通过以下代码可以更新子用户 + +```go + name := "test-user-sdk-go" + args := &api.UpdateUserArgs{ + Description: "newDescription", + } + + result, err := client.UpdateUser(name, args) + if err != nil { + fmt.Println("Update iam user failed", err) + } else { + fmt.Println("Update iam user success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[UpdateUser更新用户](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7) + +### 列举用户 +通过以下代码可以列举子用户 +```go + result, err := client.ListUser() + if err != nil { + fmt.Println("List iam user failed", err) + } else { + fmt.Println("List iam user success", result) + } +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[ListUser创建用户](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%97%E4%B8%BE%E7%94%A8%E6%88%B7) + +### 删除用户 +通过以下代码可以更新子用户 + +```go + name := "test-user-sdk-go" + err = client.DeleteUser(name) + if err != nil { + fmt.Println("Delete iam user failed", err) + } else { + fmt.Println("Delete iam user success", name) + } +``` + +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[DeleteUser删除用户](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%A0%E9%99%A4%E7%94%A8%E6%88%B7) + + +### 配置用户控制台登录 +通过以下代码可以配置用户的控制台登录,为其配置登录密码、开启登录MFA、配置第三方账号绑定等 + +```go + name := "test-user-sdk-go-login-profile" + args := &api.UpdateUserLoginProfileArgs{ + Password: "testpassword", + EnabledLoginMfa: false, + LoginMfaType: "PHONE", + } + + result, err := client.UpdateUserLoginProfile(name, args) + if err != nil { + fmt.Println("Update iam user login profile failed", err) + } else { + fmt.Println("Update iam user login profile success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[UpdateUserLoginProfile配置用户控制台登录](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E9%85%8D%E7%BD%AE%E7%94%A8%E6%88%B7%E7%9A%84%E6%8E%A7%E5%88%B6%E5%8F%B0%E7%99%BB%E5%BD%95) + +### 查询控制台登录配置 +通过以下代码可以查询用户的控制台登录配置 +```go + name := "test-user-sdk-go-login-profile" + result, err := client.GetUserLoginProfile(name) + if err != nil { + fmt.Println("Get iam user login profile failed", err) + } else { + fmt.Println("Get iam user login profile success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[GetUserLoginProfile查询用户控制台登录](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E6%9F%A5%E8%AF%A2%E6%8E%A7%E5%88%B6%E5%8F%B0%E7%99%BB%E5%BD%95%E9%85%8D%E7%BD%AE) + +### 关闭控制台登录配置 +关闭用户的控制台登录配置,即关闭用户的控制台登录 +```go + name := "test-user-sdk-go-login-profile" + err = client.DeleteUserLoginProfile(name) + if err != nil { + fmt.Println("Delete iam user login profile failed", err) + } else { + fmt.Println("Delete iam user login profile success", name) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[DeleteUserLoginProfile关闭用户控制台登录](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%85%B3%E9%97%AD%E6%8E%A7%E5%88%B6%E5%8F%B0%E7%99%BB%E5%BD%95%E9%85%8D%E7%BD%AE) + +### 创建用户的AccessKey +通过以下代码为用户创建一组AccessKey访问密钥 + +```go + name := "test-user-sdk-go-accessKey" + result, err := client.CreateAccessKey(name) + if err != nil { + fmt.Println("Create accessKey failed", err) + } else { + fmt.Println("Create accessKey success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[创建用户的AccessKey](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7%E7%9A%84accesskey) + +### 禁用用户的AccessKey +通过以下代码为禁用用户的AccessKey +```go + name := "test-user-sdk-go-accessKey" + accessKeyId := "" + result, err := client.DisableAccessKey(name, accessKeyId) + if err != nil { + fmt.Println("Disable accessKey failed", err) + } else { + fmt.Println("Disable accessKey success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[禁用用户的AccessKey](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7%E7%9A%84accesskey) + +### 启用用户的AccessKey +通过以下代码为启用用户的AccessKey +```go + name := "test-user-sdk-go-accessKey" + accessKeyId := "" + result, err := client.EnableAccessKey(name, accessKeyId) + if err != nil { + fmt.Println("Enable accessKey failed", err) + } else { + fmt.Println("Enable accessKey success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[启用用户的AccessKey](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%90%AF%E7%94%A8%E7%94%A8%E6%88%B7%E7%9A%84accesskey) + +### 删除用户的AccessKey +删除用户的指定一组AccessKey访问密钥 +```go + name := "test-user-sdk-go-accessKey" + accessKeyId := "" + err = client.DeleteAccessKey(name, accessKeyId) + if err != nil { + fmt.Println("Delete accessKey failed", err) + } else { + fmt.Println("Delete accessKey success", accessKeyId) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[删除用户的AccessKey](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%A0%E9%99%A4%E7%94%A8%E6%88%B7%E7%9A%84accesskey) + +### 列举用户的AccessKey +列举用户的全部AccessKey访问密钥 +```go + name := "test-user-sdk-go-accessKey" + result, err := client.ListAccessKey(name) + if err != nil { + fmt.Println("List accessKey failed", err) + } else { + fmt.Println("List accessKey success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举用户的AccessKey](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E5%88%97%E4%B8%BE%E7%94%A8%E6%88%B7%E7%9A%84accesskey) + + +### 创建组 +通过以下代码创建组 +```go + name := "test_group_sdk_go" + args := &api.CreateGroupArgs{ + Name: name, + Description: name, + } + + result, err := client.CreateGroup(args) + if err != nil { + fmt.Println("Create group failed", err) + } else { + fmt.Println("Create group success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[创建组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E5%88%9B%E5%BB%BA%E7%BB%84) + + +### 查询组 +通过以下代码查询组 +```go + name := "test_group_sdk_go" + result, err := client.GetGroup(name) + if err != nil { + fmt.Println("Get group failed", err) + } else { + fmt.Println("Get group success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[查询组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E6%9F%A5%E8%AF%A2%E7%BB%84) + + +### 更新组 +通过以下代码更新组 +```go + name := "test_group_sdk_go" + args := &api.UpdateGroupArgs{ + Description: "newDes", + } + result, err := client.UpdateGroup(name, args) + if err != nil { + fmt.Println("Update group failed", err) + } else { + fmt.Println("Update group success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[更新组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E6%9B%B4%E6%96%B0%E7%BB%84) + +### 删除组 +通过以下代码删除组 +```go + name := "test_group_sdk_go" + err = client.DeleteGroup(name) + if err != nil { + fmt.Println("Delete group failed", err) + } else { + fmt.Println("Delete group success", name) + } + +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[删除组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E5%88%A0%E9%99%A4%E7%BB%84) + +### 列举组 +通过以下代码删除组 +```go + result, err := client.ListGroup() + if err != nil { + fmt.Println("Delete group failed", err) + } else { + fmt.Println("Delete group success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E5%88%97%E4%B8%BE%E7%BB%84) + +### 添加用户到组 +通过以下代码添加用户到组 +```go + userName := "test_user_sdk_go" + groupName := "test_user_sdk_go" + err = client.AddUserToGroup(userName, groupName) + if err != nil { + fmt.Println("Add user to group failed", err) + } else { + fmt.Println("Add user to group success", userName) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[添加用户到组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E5%88%B0%E7%BB%84) + + +### 从组内移除用户 +通过以下代码从组内移除用户 +```go + userName := "test_user_sdk_go" + groupName := "test_user_sdk_go" + err = client.DeleteUserFromGroup(userName, groupName) + if err != nil { + fmt.Println("Add user to group failed", err) + } else { + fmt.Println("Add user to group success", userName) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[从组内移除用户](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E4%BB%8E%E7%BB%84%E5%86%85%E7%A7%BB%E9%99%A4%E7%94%A8%E6%88%B7) + +### 列举用户的组 +通过以下代码列举用户的组 +```go + userName := "test_user_sdk_go" + result, err := client.ListGroupsForUser(userName) + if err != nil { + fmt.Println("List groups for user failed", err) + } else { + fmt.Println("List groups for user success", result) + } + +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举用户的组](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E5%88%97%E4%B8%BE%E7%94%A8%E6%88%B7%E7%9A%84%E7%BB%84) + +### 列举组内用户 +通过以下代码列举组内用户 +```go + groupName := "test_user_sdk_go" + result, err := client.ListUsersInGroup(groupName) + if err != nil { + fmt.Println("List user in group failed", err) + } else { + fmt.Println("List user in group success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举组内用户](https://cloud.baidu.com/doc/IAM/s/ljx35h8lx#%E5%88%97%E4%B8%BE%E7%BB%84%E5%86%85%E7%94%A8%E6%88%B7) + +### 创建角色 +通过以下代码创建角色 +```go + roleName := "test_role_sdk_go" + args := &api.CreateRoleArgs{ + Name: roleName, + Description: "description", + AssumeRolePolicyDocument: "{\"version\":\"v1\",\"accessControlList\":[{\"service\":\"bce:iam\",\"permission\"" + + ":[\"AssumeRole\"],\"region\":\"*\",\"grantee\":[{\"id\":\"grantee-id\"}],\"effect\":\"Allow\"}]}", + } + + result, err := client.CreateRole(args) + if err != nil { + fmt.Println("Create role failed", err) + } else { + fmt.Println("Create role success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[创建角色](https://cloud.baidu.com/doc/IAM/s/ek5eq1zp1) + +### 查询角色 +通过以下代码查询角色 +```go + roleName := "test_role_sdk_go" + result, err := client.GetRole(roleName) + if err != nil { + fmt.Println("Get role failed", err) + } else { + fmt.Println("Get role success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[查询角色](https://cloud.baidu.com/doc/IAM/s/ek5eq1zp1#%E6%9F%A5%E8%AF%A2%E8%A7%92%E8%89%B2) + +### 更新角色 +通过以下代码查询角色 +```go + args := &api.UpdateRoleArgs{ + Description: "newDescription", + AssumeRolePolicyDocument: "{\"version\":\"v1\",\"accessControlList\":[{\"service\":\"bce:iam\",\"permission\"" + + ":[\"AssumeRole\"],\"region\":\"*\",\"grantee\":[{\"id\":\"grantee-id\"}],\"effect\":\"Allow\"}]}", + } + + roleName := "test_role_sdk_go" + result, err := client.UpdateRole(roleName, args) + if err != nil { + fmt.Println("Update role failed", err) + } else { + fmt.Println("Update role success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[更新角色](https://cloud.baidu.com/doc/IAM/s/ek5eq1zp1#%E6%9B%B4%E6%96%B0%E8%A7%92%E8%89%B2) + +### 删除角色 +通过以下代码查询角色 +```go + roleName := "test_role_sdk_go" + err = client.DeleteRole(roleName) + if err != nil { + fmt.Println("Delete role failed", err) + } else { + fmt.Println("Delete role success", roleName) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[删除角色](https://cloud.baidu.com/doc/IAM/s/ek5eq1zp1#%E5%88%A0%E9%99%A4%E8%A7%92%E8%89%B2) + +### 列举角色 +通过以下代码列举角色 +```go + result, err := client.ListRole() + if err != nil { + fmt.Println("List role failed", err) + } else { + fmt.Println("List role success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举角色](https://cloud.baidu.com/doc/IAM/s/ek5eq1zp1#%E5%88%97%E4%B8%BE%E8%A7%92%E8%89%B2) + +### 创建策略 +通过以下代码创建策略 +```go + +name := "test_sdk_go_policy" +args := &api.CreatePolicyArgs{ + Name: name, + Description: "description", + Document: "{\"accessControlList\": [{\"region\":\"bj\",\"service\":\"bcc\"," + +"\"resource\":[\"*\"],\"permission\":[\"*\"],\"effect\":\"Allow\"}]}", +} + +result, err := client.CreatePolicy(args) +if err != nil { + fmt.Println("Update policy failed", err) +} else { + fmt.Println("Update policy success", result) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[创建策略](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%9B%E5%BB%BA%E7%AD%96%E7%95%A5) + +### 查询策略 +通过以下代码查询策略 +```go + name := "test_sdk_go_policy" + policyType := "Custom" + result, err := client.GetPolicy(name, policyType) + if err != nil { + fmt.Println("Update policy failed", err) + } else { + fmt.Println("Update policy success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[创建策略](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%9B%E5%BB%BA%E7%AD%96%E7%95%A5) + +### 删除策略 +通过以下代码删除策略 +```go + name := "test_sdk_go_policy" + err = client.DeletePolicy(name) + if err != nil { + fmt.Println("List policy failed", err) + } else { + fmt.Println("List policy success", name) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[删除策略](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%A0%E9%99%A4%E7%AD%96%E7%95%A5) + + +### 列举策略 +通过以下代码列举策略 +```go + name := "test_sdk_go_policy" + policyType := "Custom" + result, err := client.ListPolicy(name, policyType) + if err != nil { + fmt.Println("List policy failed", err) + } else { + fmt.Println("List policy success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举策略](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%97%E4%B8%BE%E7%AD%96%E7%95%A5) + +### 关联用户权限 +通过以下代码关联用户权限 +```go + userName := "test_sdk_go_user" + policyName := "test_sdk_go_policy" + args := &api.AttachPolicyToUserArgs{ + UserName: userName, + PolicyName: policyName, + } + err = client.AttachPolicyToUser(args) + if err != nil { + fmt.Println("Attach policy to user failed", err) + } else { + fmt.Println("Attach policy to user success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[关联用户权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%85%B3%E8%81%94%E7%94%A8%E6%88%B7%E6%9D%83%E9%99%90) + +### 解除用户权限 +通过以下代码解除用户权限 +```go + userName := "test_sdk_go_user" + policyName := "test_sdk_go_policy" + args := &api.DetachPolicyFromUserArgs{ + UserName: userName, + PolicyName: policyName, + } + err = client.DetachPolicyFromUser(args) + if err != nil { + fmt.Println("Detach policy to user failed", err) + } else { + fmt.Println("Detach policy to user success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[解除用户权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E8%A7%A3%E9%99%A4%E7%94%A8%E6%88%B7%E6%9D%83%E9%99%90) + +### 列举用户的权限 +通过以下代码列举用户的权限 +```go + userName := "test_sdk_go_user" + result, err := client.ListUserAttachedPolicies(userName) + if err != nil { + fmt.Println("List user attached policy failed", err) + } else { + fmt.Println("List user attached policy success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举用户的权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%97%E4%B8%BE%E7%94%A8%E6%88%B7%E7%9A%84%E6%9D%83%E9%99%90) + +### 关联组权限 +通过以下代码关联组权限 +```go + groupName := "test_sdk_go_group" + policyName := "test_sdk_go_policy" + args := &api.AttachPolicyToGroupArgs{ + GroupName: groupName, + PolicyName: policyName, + } + err = client.AttachPolicyToGroup(args) + if err != nil { + fmt.Println("Attach policy to group failed", err) + } else { + fmt.Println("Attach policy to group success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[关联组权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%85%B3%E8%81%94%E7%BB%84%E6%9D%83%E9%99%90) + +### 解除组权限 +通过以下代码解除组权限 +```go + groupName := "test_sdk_go_group" + policyName := "test_sdk_go_policy" + args := &api.DetachPolicyFromGroupArgs{ + GroupName: groupName, + PolicyName: policyName, + } + err = client.DetachPolicyFromGroup(args) + if err != nil { + fmt.Println("Detach policy to group failed", err) + } else { + fmt.Println("Detach policy to group success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[解除组权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E8%A7%A3%E9%99%A4%E7%BB%84%E6%9D%83%E9%99%90) + +### 列举组权限 +通过以下代码列举组权限 +```go + groupName := "test_sdk_go_group" + result, err := client.ListGroupAttachedPolicies(groupName) + if err != nil { + fmt.Println("List group attached policy failed", err) + } else { + fmt.Println("List group attached policy success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举组权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%97%E4%B8%BE%E7%BB%84%E7%9A%84%E6%9D%83%E9%99%90) + +### 关联角色权限 +通过以下代码关联角色权限 +```go + roleName := "test_sdk_go_group" + policyName := "test_sdk_go_policy" + args := &api.AttachPolicyToRoleArgs{ + RoleName: roleName, + PolicyName: policyName, + } + err = client.AttachPolicyToRole(args) + if err != nil { + fmt.Println("Attach policy to role failed", err) + } else { + fmt.Println("Attach policy to role success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[关联角色权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%85%B3%E8%81%94%E8%A7%92%E8%89%B2%E6%9D%83%E9%99%90) + +### 解除角色权限 +通过以下代码关联角色权限 +```go + roleName := "test_sdk_go_group" + policyName := "test_sdk_go_policy" + args := &api.DetachPolicyToRoleArgs{ + RoleName: roleName, + PolicyName: policyName, + } + err = client.DetachPolicyFromRole(args) + if err != nil { + fmt.Println("Detach policy to role failed", err) + } else { + fmt.Println("Detach policy to role success", args) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[解除角色权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E8%A7%A3%E9%99%A4%E8%A7%92%E8%89%B2%E6%9D%83%E9%99%90) + +### 列举角色的权限 +通过以下代码列举角色权限 +```go + roleName := "test_sdk_go_group" + result, err := client.ListRoleAttachedPolicies(roleName) + if err != nil { + fmt.Println("List role attached policy failed", err) + } else { + fmt.Println("List role attached policy success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[列举角色的权限](https://cloud.baidu.com/doc/IAM/s/Wjx35jxes#%E5%88%97%E4%B8%BE%E8%A7%92%E8%89%B2%E7%9A%84%E6%9D%83%E9%99%90) + +### 修改子用户操作保护 +通过以下代码修改子用户操作保护 +```go + userName := "test-user-sdk-go-switch-operation-mfa" + enableMfa := true + mfaType := "PHONE,TOTP" + args := &api.UserSwitchMfaArgs{ + UserName: userName, + EnabledMfa: enableMfa, + MfaType: mfaType, + } + err := IAM_CLIENT.UserOperationMfaSwitch(args) + if err != nil { + fmt.Println("switch user mfa failed", err) + } else { + fmt.Println("switch user mfa success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[UserOperationMfaSwitch修改子用户操作保护](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E4%BF%AE%E6%94%B9%E5%AD%90%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C%E4%BF%9D%E6%8A%A4) + + +### 修改子用户密码 +通过以下代码修改子用户密码 +```go + userName := "test-user-name-sdk-go-sub-update" + Password := "testpassword" + args := &api.UpdateSubUserArgs{ + Password: Password, + } + res, err := IAM_CLIENT.SubUserUpdate(userName, args) + if err != nil { + fmt.Println("update sub user failed", err) + } else { + fmt.Println("update sub user success", result) + } +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考IAM API 文档[SubUserUpdate修改子用户密码](https://cloud.baidu.com/doc/IAM/s/mjx35fixq#%E4%BF%AE%E6%94%B9%E5%AD%90%E7%94%A8%E6%88%B7%E5%AF%86%E7%A0%81) + +# 错误处理 + +GO语言以error类型标识错误,IAM支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | IAM服务返回的错误 + +## 客户端异常 + +客户端异常表示客户端尝试向IAM发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当IAM服务端出现异常时,IAM服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[IAM错误返回](https://cloud.baidu.com/doc/IAM/s/Rjx4d0rxo) + +# 版本变更记录 + +## v0.9.11 [2022-10-13] + +首次发布: + +- 创建、查看、列表、更新、删除IAM用户 +- 配置、查询、关闭用户控制台配置 +- 创建、查看、列表、删除、启用、禁用AccessKey +- 创建、查看、列表、更新、删除IAM用户组 +- 创建、列表、列表、更新、删除角色 +- 创建、查看、列表、更新、删除、关联用户权限、解除用户权限、列举用户权限、关联组权限、解除组权限、列举组权限、关联角色权限、解除角色权限、列举角色的权限 + +## v0.9.12 [2023-02-13] + +- 修改子用户的操作保护 +- 修改子用户密码 diff --git a/bce-sdk-go/doc/LBDC.md b/bce-sdk-go/doc/LBDC.md new file mode 100644 index 0000000..b514d4d --- /dev/null +++ b/bce-sdk-go/doc/LBDC.md @@ -0,0 +1,514 @@ +# LBDC服务 + +# 概述 + +本文档主要介绍负载均衡专属集群LBDC GO SDK的使用。在使用本文档前,您需要先了解BLB的一些基本知识。若您还不了解BLB,可以参考[产品描述](https://cloud.baidu.com/doc/BLB/s/Ajwvxno34)和[入门指南](https://cloud.baidu.com/doc/BLB/s/cjwvxnr91)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[BLB访问域名](https://cloud.baidu.com/doc/BLB/s/cjwvxnzix)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云LBDC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问LBDC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建负载均衡专属集群LBDC Client + +LBDC Client是负载均衡专属集群LBDC服务的客户端,为开发者与LBDC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建负载均衡专属集群LBDC Client + +通过AK/SK方式访问LBDC,用户可以参考如下代码新建一个LBDC Client: +```go +import ( + "github.com/baidubce/bce-sdk-go/services/lbdc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个LBDC Client + lbdcClient, err := lbdc.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/BLB/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为LBDC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`blb.bj.baidubce.com`。 + +### 使用STS创建LBDC Client + +**申请STS token** + +LBDC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问LBDC,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建LBDC Client** + +申请好STS后,可将STS Token配置到LBDC Client中,从而实现通过STS Token创建LBDC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建LBDC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/lbdc" //导入LBDC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建LBDC服务的Client对象,Endpoint使用默认值 + lbdcClient, err := lbdc.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create lbdc client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + lbdcClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置LBDC Client时,无论对应LBDC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问LBDC + +LBDC支持HTTPS传输协议,您可以通过在创建LBDC Client对象时指定的Endpoint中指明HTTPS的方式,在LBDC GO SDK中使用HTTPS访问LBDC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +ENDPOINT := "https://lbdc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +lbdcClient, _ := lbdc.NewClient(AK, SK, ENDPOINT) +``` + +## 配置LBDC Client + +如果用户需要配置LBDC Client的一些细节的参数,可以在创建LBDC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问LBDC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +//创建LBDC Client对象 +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := lbdc.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := lbdc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +AK, SK := , +ENDPOINT := "blb.bj.baidubce.com" +client, _ := lbdc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问LBDC时,创建的LBDC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建LBDC Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +负载均衡专属集群提供性能可控、资源独享、物理资源隔离的专属负载均衡服务,满足超高性能和独占资源需求。 + +## 实例管理 + +### 创建LBDC + +通过以下代码,可以创建LBDC +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +args := &CreateLbdcArgs{ + ClientToken: ClientToken(), + Name: Name, + Type: Type, + CcuCount: CcuCount, + Description: &Description, + Billing: &Billing{ + PaymentTiming: PaymentTiming, + Reservation: &Reservation{ + ReservationLength: ReservationLength, + }, + }, + RenewReservation: &Reservation{ + ReservationLength: ReservationLength, + }, +} +res, err := client.CreateLbdc(args) +ExpectEqual(t.Errorf, nil, err) +e, err := json.Marshal(res) +if err != nil { + fmt.Printf("create lbdc error: %+v\n", err) + return +} +fmt.Printf("create lbdc success,lbdcId is: %+v",res.Id) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[创建LBDC](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#%E5%88%9B%E5%BB%BAlbdc) + +### 升级LBDC + +通过以下代码,可以升级LBDC +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +args := &UpgradeLbdcArgs{ + ClientToken: ClientToken(), + Id: Id, + CcuCount: CcuCount, +} +err := client.UpgradeLbdc(args) +if err != nil { + fmt.Printf("upgrade lbdc error: %+v\n", err) + return +} +fmt.Printf("upgrade lbdc success") +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[升级LBDC](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#%E5%8D%87%E7%BA%A7lbdc) + +### 续费LBDC + +通过以下代码,可以续费LBDC +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +args := &RenewLbdcArgs{ + ClientToken: ClientToken(), + Id: Id, + Billing: &BillingForRenew{ + Reservation: &Reservation{ + ReservationLength: ReservationLength, + }, + }, +} +err := client.RenewLbdc(args) +if err != nil { + fmt.Printf("renew lbdc error: %+v\n", err) + return +} +fmt.Printf("renew lbdc success") +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[续费LBDC](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#%E7%BB%AD%E8%B4%B9lbdc) + +### 查询LBDC列表 + +通过以下代码,可以查询LBDC列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +args := &ListLbdcArgs{ + Id: Id, + Name: Name, +} +res, err := client.ListLbdc(args) +if err != nil { + fmt.Printf("get lbdc list error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("lbdc list marker: ", res.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("lbdc list isTruncated: ", res.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("lbdc list nextMarker: ", res.NextMarker) +// 每页包含的最大数量 +fmt.Println("lbdc list maxKeys: ", res.MaxKeys) +// 获取lbdc的具体信息 +for _, v := range result.ClusterList { + // 集群id + fmt.Println("Cluster id: ", v.Id) + // 集群名称 + fmt.Println("Cluster name: ", v.Name) + // 集群类型 + fmt.Println("Cluster type: ", v.Type) + // 集群状态 + fmt.Println("Cluster status: ", v.Status) + // 集群性能容量 + fmt.Println("Cluster ccuCount: ", v.CcuCount) + // 集群创建时间 + fmt.Println("Cluster createTime: ", v.CreateTime) + // 集群失效时间 + fmt.Println("Cluster expireTime: ", v.ExpireTime) + // 描述 + fmt.Println("Cluster description: ", v.Description) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[查询LBDC列表](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#lbdc%E5%88%97%E8%A1%A8) + +### 查询LBDC详情 + +通过以下代码,可以查询LBDC详情 +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +res, err := client.GetLbdcDetail(lbdcId) +if err != nil { + fmt.Printf("get lbdc detail error: %+v\n", err) + return +} + +// 集群id +fmt.Println("lbdc id: ", res.VpnId) +// 集群名称 +fmt.Println("lbdc name: ", res.Name) +// 集群类型 +fmt.Println("lbdc type: ", res.Type) +// 集群状态 +fmt.Println("lbdc status: ", res.Status) +// 集群性能容量 +fmt.Println("lbdc ccuCount: ", res.CcuCount) +// 集群创建时间 +fmt.Println("lbdc createTime: ", res.CreateTime) +// 集群失效时间 +fmt.Println("lbdc expireTime: ", res.ExpireTime) +// 集群并发连接数 +fmt.Println("lbdc totalConnectCount: ", res.TotalConnectCount) +// 集群网络输入带宽 +fmt.Println("lbdc networkInBps: ", res.NetworkInBps) +// 集群网络输出带宽 +fmt.Println("lbdc networkOutBps: ", res.NetworkOutBps) + +// if 4 layers +// 集群新建连接速度 +fmt.Println("lbdc newConnectCps: ", res.NewConnectCps) + +// if 7 layers +// 集群https的qps +fmt.Println("lbdc httpsQps: ", res.HttpsQps) +// 集群http的qps +fmt.Println("lbdc httpQps: ", res.HttpQps) +// 集群http新建速度 +fmt.Println("lbdc httpNewConnectCps: ", res.HttpNewConnectCps) +// 集群https新建速度 +fmt.Println("lbdc httpsNewConnectCps: ", res.HttpsNewConnectCps) +// 集群ssl新建速度 +fmt.Println("lbdc sslNewConnectCps: ", res.SslNewConnectCps) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[LBDC详情](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#lbdc%E8%AF%A6%E6%83%85) + +### 更新LBDC + +通过以下代码,可以更新LBDC名称或者描述 +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +args := &UpdateLbdcArgs{ + ClientToken: ClientToken(), + Id: Id, + UpdateLbdcBody: &UpdateLbdcBody{ + Name: &Name, + Description: &Description, + }, +} +err := client.UpdateLbdc(args) +if err != nil { + fmt.Printf("update lbdc error: %+v\n", err) + return +} +fmt.Printf("update lbdc success") +``` +> **提示:** +> 1. 名称和描述都可以为空 +> 2. 注意名称和描述是指针类型 +> 3. 详细的参数配置及限制条件,可以参考LBDC API 文档[更新LBDC](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#%E6%9B%B4%E6%96%B0lbdc) + +### 查询LBDC关联的BLB列表 + +通过以下代码,可以查询LBDC关联的BLB列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/lbdc" + +res, err := client.GetBoundBlBListOfLbdc(lbdcId) +if err != nil { + fmt.Printf("get bound blb list of lbdc error: %+v\n", err) + return +} + +// 获取lbdc关联的BLB的具体信息 +for _, v := range result.BlbList { + // 负载均衡id + fmt.Println("BLB id: ", v.BlbId) + // blb名称 + fmt.Println("BLB name: ", v.Name) + // blb状态 + fmt.Println("BLB status: ", v.Status) + // blb类型 + fmt.Println("BLB type: ", v.BlbType) + // 公网ip + fmt.Println("BLB publicIp: ", v.PublicIp) + // eip线路类型 + fmt.Println("BLB eipRouteType: ", v.EipRouteType) + // 带宽 + fmt.Println("BLB bandwidth: ", v.Bandwidth) + // 内网ip地址 + fmt.Println("BLB address: ", v.Address) + // ipv6地址 + fmt.Println("BLB ipv6: ", v.Ipv6) + // vpcId + fmt.Println("BLB vpcId: ", v.VpcId) + // 子网id + fmt.Println("BLB subnetId: ", v.SubnetId) +} +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考LBDC API 文档[查询LBDC关联的BLB列表](https://cloud.baidu.com/doc/BLB/s/6kszzygx4#lbdc%E5%85%B3%E8%81%94%E7%9A%84blb%E5%88%97%E8%A1%A8) + + +# 错误处理 + +GO语言以error类型标识错误,LBDC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | LBDC服务返回的错误 + +用户使用SDK调用LBDC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// client 为已创建的LBDC Client对象 +lbdcDetail, err := client.GetLbdcDetail(lbdcId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get lbdc detail success: ", lbdcDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向LBDC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当LBDC服务端出现异常时,LBDC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[BLB错误返回](https://cloud.baidu.com/doc/BLB/s/Djwvxnzw6) + +# 版本变更记录 + +## v1.0.0 [2022-09-21] + +首次发布: + + - 创建、升级、续费、查看、列表、更新LBDC实例,查LBDC关联的BLB列表 + + diff --git a/bce-sdk-go/doc/LOCALDNS.md b/bce-sdk-go/doc/LOCALDNS.md new file mode 100644 index 0000000..753cda7 --- /dev/null +++ b/bce-sdk-go/doc/LOCALDNS.md @@ -0,0 +1,420 @@ +# LD服务 + +# 概述 + +本文档主要介绍LD(Local DNS) GO SDK的使用。在使用本文档前,您需要先了解LD的一些基本知识,并已开通了LD服务。若您还不了解LD,可以参考[产品描述](https://cloud.baidu.com/doc/DNS/s/Ajwvywvx3 )和[操作指南](https://cloud.baidu.com/doc/DNS/s/yjxkakdj4) 。 + +# 初始化 + +## 确认Endpoint + +内网DNS API 的服务域名为:privatezone.baidubce.com + +API支持HTTP和HTTPS两种调用方式。为了提升数据的安全性,建议通过HTTPS调用。 + + +## 获取密钥 + +要使用百度云LD,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问LD做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建LD Client + +LD Client是LD服务的客户端,为开发者与LD服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建LD Client + +通过AK/SK方式访问LD,用户可以参考如下代码新建一个LD Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/localDns" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个LDClient + ldClient, err := ld.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为LD的服务地址。 + +### 使用STS创建LD Client + +**申请STS token** + +LD可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问LD,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建LD Client** + +申请好STS后,可将STS Token配置到LD Client中,从而实现通过STS Token创建LD Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建LD Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/localDns" //导入LD服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建LD服务的Client对象,Endpoint使用默认值 + ldClient, err := ld.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "privatezone.baidubce.com") + if err != nil { + fmt.Println("create localDns client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + ldClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置LD Client时,STS的Endpoint需配置为http://sts.bj.baidubce.com。 + +# 配置HTTPS协议访问LD + +LD支持HTTPS传输协议,您可以通过在创建LD Client对象时指定的Endpoint中指明HTTPS的方式,在LD GO SDK中使用HTTPS访问LD服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/localDns" + +ENDPOINT := "https://privatezone.baidubce.com " //指明使用HTTPS协议 +AK, SK := , +ldClient, _ := ld.NewClient(AK, SK, ENDPOINT) +``` + +## 配置LD Client + +如果用户需要配置LD Client的一些细节的参数,可以在创建LD Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问LD服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/localDns" + +//创建LD Client对象 +AK, SK := , +ENDPOINT := "privatezone.baidubce.com" +client, _ := ld.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/localDns" + +AK, SK := , +ENDPOINT := "privatezone.baidubce.com" +client, _ := ld.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/localDns" + +AK, SK := , +ENDPOINT := "privatezone.baidubce.com" +client, _ := ld.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问LD时,创建的LD Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建LD Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 创建PrivateZone + +```go +args := &CreatePrivateZoneRequest{ + ZoneName: "sdkLd.com", + } +result, err := LdClient.CreatePrivateZone(args) +ExpectEqual(t.Errorf, nil, err) +ZoneId := result.ZoneId +log.Debug(ZoneId) +``` + +## 删除PrivateZone + +```go +err := LdClient.DeletePrivateZone("zone-mk2guy4qxd7c") +ExpectEqual(t.Errorf, nil, err) +``` + + +## 查询PrivateZone列表 + +```go +args := &ListPrivateZoneRequest{ + Marker: "zone-mk2guy4qxd7c", +} +result, err := LdClient.ListPrivateZone(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 查询PrivateZone详情 + +```go +result, err := LdClient.GetPrivateZone("zone-mk2guy4qxd7c") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 关联VPC + +```go +args := &BindVpcRequest{ + Region: "bj", + VpcIds: []string{"vpc-cxvqgxipk36r", "vpc-0n1hhh8759b0"}, +} +err := LdClient.BindVpc("zone-mk2guy4qxd7c", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 解关联VPC + +```go +args := &UnbindVpcRequest{ + Region: "bj", + VpcIds: []string{"vpc-cxvqgxipk36r", "vpc-0n1hhh8759b0"}, +} +err := LdClient.UnbindVpc("zone-mk2guy4qxd7c", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 添加解析记录 + +```go +args := &AddRecordRequest{ + Rr: "www", + Type: "A", + Value: "192.168.1.1", + } +result, err := LdClient.AddRecord("zone-mk2guy4qxd7c", args) +ExpectEqual(t.Errorf, nil, err) +RecordId := result.RecordId +log.Debug(RecordId) +``` + +## 修改解析记录 + +```go +args := &UpdateRecordRequest{ + Rr: "www", + Type: "A", + Value: "192.168.1.2", + } +err := LdClient.UpdateRecord("rc-jih8hd5s", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 删除解析记录 + +```go +err := LdClient.DeleteRecord("rc-jih8hd5s") +ExpectEqual(t.Errorf, nil, err) +``` + +## 查询解析记录列表 + +```go +result, err := LdClient.ListRecord("zone-mk2guy4qxd7c") +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +log.Debug(string(r)) +``` + +## 开启解析记录 + +```go +err := LdClient.EnableRecord("rc-jih8hd5s", args) +ExpectEqual(t.Errorf, nil, err) +``` + +## 暂停解析记录 + +```go +err := LdClient.DisableRecord("rc-jih8hd5s", args) +ExpectEqual(t.Errorf, nil, err) +``` + +# 错误处理 + +GO语言以error类型标识错误,LD支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | LD服务返回的错误 + +用户使用SDK调用LD相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +args := &ListPrivateZoneRequest{ + Marker: "zone-mk2guy4qxd7c", +} +result, err := LdClient.ListPrivateZone(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向LD发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当LD服务端出现异常时,LD服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[LD错误码](https://cloud.baidu.com/doc/DNS/s/lkk5elv58) + +## SDK日志 + +LD GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +LD GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the LD go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the LD go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/MCP.md b/bce-sdk-go/doc/MCP.md new file mode 100644 index 0000000..96e678c --- /dev/null +++ b/bce-sdk-go/doc/MCP.md @@ -0,0 +1,894 @@ +# MCP服务 + +# 概述 + +本文档主要介绍MCP GO SDK的使用。在使用本文档前,您需要先了解MCP的一些基本知识,并已开通了MCP服务。若您还不了解MCP,可以参考[产品描述](https://cloud.baidu.com/doc/MCT/s/9jwvz4hes)和[入门指南](https://cloud.baidu.com/doc/MCT/s/mkd8ii2ck)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[使用须知](https://cloud.baidu.com/doc/MCT/s/Sjwvz5hq5)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/Regions.html)。 + +目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:`http://media.bj.baidubce.com`,广州区域:`http://media.gz.baidubce.com`,苏州区域:`http://media.su.baidubce.com`。对应信息为: + +| 访问区域 | 对应Endpoint | +| -------- | --------------------- | +| BJ | media.bj.baidubce.com | +| GZ | media.gz.baidubce.com | +| SU | media.su.baidubce.com | + +## 获取密钥 + +要使用百度云MCP,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问MCP做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建MCP Client + +MCP Client是MCP服务的客户端,为开发者与MCP服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建MCP Client + +通过AK/SK方式访问MCP,用户可以参考如下代码新建一个MCP Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/media" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个MCPClient + MEDIA_CLIENT, err := media.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《相关参考 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为MCP的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://media.bj.baidubce.com`。 + +# pipeline队列 + +队列分为免费型与专享型: + +- 免费型队列中的转码任务分享百度智能云为音视频转码所提供的约400路720P转码计算资源。 +- 专享型队列需额外采购,以便更好的满足那些对于转码时效性和稳定性有更高要求的用户的业务需求。 + +用户可以利用队列实现任务优先级。用户通过创建多个队列达到区分任务优先级的目的,将大部分任务创建至普通优先级队列,将高优的任务放入高优先级的队列,以利用队列先到先服务的工作原理来实现任务的优先级调整。 + +## 新建Pipeline + +如下代码可以新建一个Pipeline。 + +```go +pipelineName := "test" +sourceBucket := "testBucket" +targetBucket := "targetBucket" +capacity := 10 +err := MEDIA_CLIENT.CreatePipeline(pipelineName, sourceBucket, targetBucket, capacity) +if err != nil { + fmt.Printf("create Pipeline error: %+v\n", err) + return +} +fmt.Println("create pipeline success") +``` + +## 列出全部pipline + +如下代码可以列出用户所有的pipeline + +```go +pipelines, err := MEDIA_CLIENT.ListPipelines() +if err != nil { + fmt.Printf("list Pipeline error: %+v\n", err) + return +} +fmt.Println("list pipeline success\n") +for _, pipeline := range pipelines.Pipelines { + fmt.Printf("pipeline: %+v\n", pipeline) +} +``` + +## 查询指定pipeline + +如下代码可以按照pipelineName查询pipeline。 + +```go +pipelineName := "test" +pipeline, err := MEDIA_CLIENT.GetPipeline(pipelineName) +if err != nil { + fmt.Printf("list Pipeline error: %+v\n", err) + return +} +fmt.Println("get pipeline success") +fmt.Printf("pipeline: %+v\n", pipeline) +``` + +## 删除pipeline + +如下代码可以按照pipelineName删除pipeline。 + +```go +pipelineName := "test" +err := MEDIA_CLIENT.DeletePipeline(pipelineName) +if err != nil { + fmt.Printf("delete Pipeline error: %+v\n", err) + return +} +fmt.Println("delete pipeline success") +``` + +需要注意的是,如果Pipeline有关联的Job未完成,则Pipeline无法被删除,必须等Job执行结束后才能成功删除。 + +## 更新指定的Pipeline + +如下代码可以对指定的pipeline进行更新。 + +```go +pipelineName := "test" +args, _ := MEDIA_CLIENT.GetPipelineUpdate(pipelineName) +//args := &api.UpdatePipelineArgs{} +args.Description = "update" +args.TargetBucket = "vwdemo" +args.SourceBucket = "vwdemo" + +config := &api.UpdatePipelineConfig{} +config.Capacity = 2 +config.Notification = "zz" +args.UpdatePipelineConfig = config +err := MEDIA_CLIENT.UpdatePipeline(pipelineName, args) +if err != nil { + fmt.Printf("update Pipeline error: %+v\n", err) + return +} +fmt.Println("update pipeline success") +``` + +# Transcoding-Job转码任务 + +Transcoding Job(任务)是音视频转码中最基本的执行单元,每个任务将一个原始的音视频资源转码成目标规格的音视频资源。因此,任务和转码的目标是一一对应的,也就是说如果用户需要将一个原始多媒体文件转换成三种目标规格,比如从AVI格式转码成FLV/MP4/HLS格式,那么用户将会需要创建三个任务。 + +## 创建Transcoding Job + +用户在创建转码任务时,需要为转码任务指定所属的Pipeline、所需应用的Preset以及原始音视频资源的BOS Key以及目标音视频资源BOS Key。 + +如下代码创建一个Job, 并获取新创建的jobID: + +```go +pipelineName := "go_sdk_test" +sourceKey := "test.mp4" +targetKey := "test-result.mp4" +presetName := "test_preset" +jobResponse, err := MEDIA_CLIENT.CreateJob(pipelineName, sourceKey, targetKey, presetName) +if err != nil { + fmt.Printf("create job error: %+v\n", err) + return +} +fmt.Println("create job success jobId:", jobResponse.JobId) +``` + +如下代码创建一个支持视频合并、去水印、加水印(Job上而不是Preset上指定watermarkId)的Job, 并获取新创建的jobID: + +```go +args := &api.CreateJobArgs{} +args.PipelineName = "go_sdk_test" +source := &api.Source{Clips: &[]api.SourceClip{{ + SourceKey: "01.mp4", + EnableDelogo: false, + DurationInMillisecond: 6656, + StartTimeInSecond: 2}}} +args.Source = source +target := &api.Target{} +targetKey := "clips_playback_watermark_delogo_crop2.mp4" +watermarkId := "wmk-xxxx" +target.TargetKey = targetKey +watermarkIdSlice := append(target.WatermarkIds, watermarkId) +target.WatermarkIds = watermarkIdSlice +presetName := "go_test_customize_audio_video" +target.PresetName = presetName + +delogoArea := &api.Area{} +delogoArea.X = 10 +delogoArea.Y = 10 +delogoArea.Width = 30 +delogoArea.Height = 40 +target.DelogoArea = delogoArea + +args.Target = target + +jobResponse, err := MEDIA_CLIENT.CreateJobCustomize(args) +if err != nil { + fmt.Printf("create job error: %+v\n", err) + return +} +fmt.Println("create job success jobId:", jobResponse.JobId) +``` + +如下代码创建一个支持视频合并、去水印、加水印、去黑边、插入多样叠加效果(Insert)的Job, 并获取新创建的jobID: + + + + + + + + + + + +## 列出指定Pipeline的所有Transcoding Job + +如下代码通过指定pipelineName查询该Pipeline下的所有Job: + +```go +pipelineName := "test" +listTranscodingJobsResponse, err := MEDIA_CLIENT.ListTranscodingJobs(pipelineName) +if err != nil { + fmt.Printf("list job error: %+v\n", err) + return +} +fmt.Printf("list job success : %+v\n", listTranscodingJobsResponse) +``` + +## 查询指定的Transcoding Job信息 + +可以通过如下代码通过jobId读取某个Job: + +```go +jobId := "job-xxxxxxxxx" +getTranscodingJobResponse, err := MEDIA_CLIENT.GetTranscodingJob(jobId) +if err != nil { + fmt.Printf("get job error: %+v\n", err) + return +} +fmt.Printf("get job success : %+v\n", getTranscodingJobResponse) +``` + +# Preset模板 + +模板是系统预设的对于一个视频资源在做转码计算时所需定义的集合。用户可以更简便的将一个模板应用于一个和多个视频的转码任务,以使这些任务输出相同规格的目标视频资源。 + +音视频转码为用户预设了丰富且完备的系统模板,以满足用户对于目标规格在格式、码率、分辨率、加解密、水印等诸多方向上的普遍需求,对于不希望过多了解音视频复杂技术背景的用户来说,是最佳的选择。百度为那些在音视频技术上有着丰富积累的用户,提供了可定制化的转码模板,以帮助他们满足复杂业务条件下的转码需求。 + +当用户仅需对于音视频的容器格式做变化时,百度提供Transmux模板帮助用户以秒级的延迟快速完成容器格式的转换,比如从MP4转换成HLS,而保持原音视频的属性不变。 + +## 查询当前用户Preset及所有系统Preset + +用户可以通过如下代码查询所有的Preset + +```go +listPresetsResponse, err := MEDIA_CLIENT.ListPresets() +if err != nil { + fmt.Printf("list preset error: %+v\n", err) + return +} +fmt.Printf("list preset success: %+v\n", listPresetsResponse) +``` + +## 查询指定的Preset信息 + +如下代码通过指定pipelineName查询该Pipeline下的所有Job: + +```go +presetName := "test" +getPresetResponse, err := MEDIA_CLIENT.GetPreset(preset) +if err != nil { + fmt.Printf("list preset error: %+v\n", err) + return +} +fmt.Printf("list preset success: %+v\n", getPresetResponse) +``` + +## 创建Preset + +如果系统预设的Preset无法满足用户的需求,用户可以自定义自己的Preset。根据不同的转码需求,可以使用不同的接口创建Preset。 + +### 创建仅支持容器格式转换的Preset + +如下代码创建仅执行容器格式转换Preset + +```go +presetName := "test" +description := "测试创建模板" +container := "mp4" +err := MEDIA_CLIENT.CreatePreset(presetName, description, container) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +### 创建音频文件的转码Preset,不需要截取片段和加密 + +如果创建一个不需要截取片段和加密的音频文件转码Preset,可以参考如下代码 + +```go +preset := &api.Preset{} +preset.PresetName = "go_test_customize" +preset.Description = "自定义创建模板" +preset.Container = "mp3" + +audio := &api.Audio{} +audio.BitRateInBps = 256000 +preset.Audio = audio + +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +### 创建音频文件转码Preset,需要设置片段截取属性和加密属性 + +如果创建一个支持截取片段和加密的音频文件转码Preset,可以参考如下代码 + +```go +preset := &api.Preset{} +preset.PresetName = "go_test_customize_encryption_clip" +preset.Description = "自定义创建模板" +preset.Container = "mp3" + +audio := &api.Audio{} +audio.BitRateInBps = 256000 +preset.Audio = audio + +clip := &api.Clip{} +clip.StartTimeInSecond = 2 +clip.DurationInSecond = 10 +preset.Clip = clip + +encryption := &api.Encryption{} +encryption.Strategy = "PlayerBinding" +preset.Encryption = encryption + +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +### 创建视频文件转码Preset,不需要截取片段、加密和水印属性 + +如果创建一个不需要截取片段,加密和水印的视频文件转码Preset,可以参考如下代码 + +```go +preset := &api.Preset{} +preset.PresetName = "go_test_customize_audio_video" +preset.Description = "自定义创建模板" +preset.Container = "mp4" + +audio := &api.Audio{} +audio.BitRateInBps = 256000 +preset.Audio = audio + +video := &api.Video{} +video.BitRateInBps = 1024000 +preset.Video = video + +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +### 创建视频文件转码Preset,需要设置片段截取、加密和水印属性 + +如果创建一个需要截取片段,加密和添加水印的视频文件转码Preset,可以参考如下代码 + +```go +preset := &api.Preset{} +preset.PresetName = "go_test_customize_clp_aud_vid_en_wat" +preset.Description = "自定义创建模板" +preset.Container = "mp4" + +clip := &api.Clip{} +clip.StartTimeInSecond = 0 +clip.DurationInSecond = 60 +preset.Clip = clip + +audio := &api.Audio{} +audio.BitRateInBps = 256000 +preset.Audio = audio + +video := &api.Video{} +video.BitRateInBps = 1024000 +preset.Video = video + +encryption := &api.Encryption{} +encryption.Strategy = "PlayerBinding" +preset.Encryption = encryption + +preset.WatermarkID = "wmk-xxxxxx" + +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +### 创建Preset,指定所有的参数 + +如果需要定制所有配置参数,可以参考如下代码 + +```go +preset := &api.Preset{} +preset.PresetName = "go_test_customize_full_args" +preset.Description = "全参数" +preset.Container = "hls" +preset.Transmux = false + +clip := &api.Clip{} +clip.StartTimeInSecond = 0 +clip.DurationInSecond = 60 +preset.Clip = clip + +audio := &api.Audio{} +audio.BitRateInBps = 256000 +preset.Audio = audio + +video := &api.Video{} +video.BitRateInBps = 1024000 +preset.Video = video + +encryption := &api.Encryption{} +encryption.Strategy = "PlayerBinding" +preset.Encryption = encryption + +water := &api.Watermarks{} +water.Image = []string{"wmk-pc0rdhzbm8ff99qw"} +preset.Watermarks = water + +transCfg := &api.TransCfg{} +transCfg.TransMode = "normal" +preset.TransCfg = transCfg + +extraCfg := &api.ExtraCfg{} +extraCfg.SegmentDurationInSecond = 6.66 +preset.ExtraCfg = extraCfg + +err := MEDIA_CLIENT.CreatePrestCustomize(preset) +if err != nil { + fmt.Printf("create preset error: %+v\n", err) + return +} +fmt.Println("create preset success") +``` + +## 更新Preset + +用户可以根据模板名更新自己创建的模板: + +```go +preset, _ := MEDIA_CLIENT.GetPreset("go_test_customize") +preset.Description = "test update preset" +err := MEDIA_CLIENT.UpdatePreset(preset) +if err != nil { + fmt.Printf("update preset error: %+v\n", err) + return +} +fmt.Println("update preset success") +``` + +# Mediainfo媒体信息 + +对于BOS中某个Object,可以通过下面代码获取媒体信息 + +```go +bucket := "bucekt" +key := "key" +info, err := MEDIA_CLIENT.GetMediaInfoOfFile(bucket, key) +if err != nil { + fmt.Printf("get media information error: %+v\n", err) + return +} +fmt.Printf("get media information success: %+v\n", info) +``` + +# Thumbnail-Job缩略图任务 + +缩略图是图片、视频经压缩方式处理后的小图。因其小巧,加载速度非常快,故用于快速浏览。缩略图任务可用于为BOS中的多媒体资源创建缩略图。 + +## 创建Thumbnail Job + +通过pipeline,BOS Key以及其他配置信息为指定媒体生成缩略图,并获取返回的缩略图任务jobId。可以参考如下代码: + +```go +pipelineName := "go_test" +sourcekey := "01.mp4" +target := &api.ThumbnailTarget{} +target.Format = "jpg" +target.SizingPolicy = "keep" +capture := &api.ThumbnailCapture{} +capture.Mode = "manual" +capture.StartTimeInSecond = 0.0 +capture.EndTimeInSecond = 5.0 +capture.IntervalInSecond = 1.0 +createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob(pipelineName, sourcekey, TargetOp(target), CaptureOp(capture)) +if err != nil { + fmt.Printf("create thumbanil job error: %+v\n", err) + return +} +fmt.Println("create thumbanil job success jobId: ", createJobResponse.JobId) +``` + +创建去水印的缩略图,可以参考如下代码: + +```go +pipelineName := "go_test" +sourcekey := "01.mp4" +target := &api.ThumbnailTarget{} +target.KeyPrefix = "taget_key_prefix_test_delogo3" +delogo := &api.Area{} +delogo.X = 20 +delogo.Y = 20 +delogo.Height = 50 +delogo.Width = 80 + +createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob(pipelineName, sourcekey, TargetOp(target), DelogoAreaOp(delogo)) +if err != nil { + fmt.Printf("create thumbanil job error: %+v\n", err) + return +} +fmt.Println("create thumbanil job success jobId: ", createJobResponse.JobId) +``` + +创建去水印、去黑边的缩略图,可以参考如下代码: + +```go +pipelineName := "go_test" +sourcekey := "01.mp4" +target := &api.ThumbnailTarget{} +target.KeyPrefix = "taget_key_prefix_test_delogo_crop" +delogo := &api.Area{} +delogo.X = 20 +delogo.Y = 20 +delogo.Height = 50 +delogo.Width = 80 + +crop := &api.Area{} +crop.X = 120 +crop.Y = 120 +crop.Height = 100 +crop.Width = 80 + +createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob(pipelineName, sourcekey, + TargetOp(target), DelogoAreaOp(delogo), CropOp(crop)) +if err != nil { + fmt.Printf("create thumbanil job error: %+v\n", err) + return +} +fmt.Println("create thumbanil job success jobId: ", createJobResponse.JobId) +``` + +创建去水印缩略图任务,其中指定了缩略图格式为jpg、尺寸为与原视频保持一致(keep),抽帧模式(SizingPolicy)为split,根据指定的起止时间和张数截取缩略图,FrameNumber则指定了缩略图张数,代码如下: + +```go +pipelineName := "go_test" +sourcekey := "01.mp4" +target := &api.ThumbnailTarget{} +target.Format = "jpg" +target.SizingPolicy = "keep" + +capture := &api.ThumbnailCapture{} +capture.Mode = "split" +capture.FrameNumber = 30 + +delogo := &api.Area{} +delogo.X = 20 +delogo.Y = 20 +delogo.Height = 50 +delogo.Width = 80 + +createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob(pipelineName, sourcekey, + TargetOp(target), CaptureOp(capture), DelogoAreaOp(delogo)) +if err != nil { + fmt.Printf("create thumbanil job error: %+v\n", err) + return +} +fmt.Println("create thumbanil job success jobId: ", createJobResponse.JobId) +``` + +如果只想创建一个简单的缩略图任务可以参考如下代码: + +```go +pipelineName := "go_test" +sourcekey := "01.mp4" +createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob(pipelineName, sourcekey) +if err != nil { + fmt.Printf("create thumbanil job error: %+v\n", err) + return +} +fmt.Println("create thumbanil job success jobId: ", createJobResponse.JobId) +``` + +## 查询指定Thumbnail Job + +如果需要获取一个已创建的缩略图任务的信息,可以参考如下代码: + +```go +jobId := "job-xxxxxxx" +jobResponse, err := MEDIA_CLIENT.GetThumbanilJob(jobId) +if err != nil { + fmt.Printf("get thumbanil job error: %+v\n", err) + return +} +fmt.Printf("get thumbanil job success job: %+v\n", jobResponse) +``` + +## 查询指定队列的Thumbnail Jobs + +如果需要获取一个队列里的全部缩略图任务的信息,可以参考如下代码: + +```go +pipelineName := "go_sdk_test" +listThumbnailJobsResponse, err := MEDIA_CLIENT.ListThumbnailJobs(pipelineName) +if err != nil { + fmt.Printf("list thumbanil job error: %+v\n", err) + return +} +for _, job := range listThumbnailJobsResponse.Thumbnails { + fmt.Printf("list thumbanil job success : %+v\n", job) +} +``` + +# Watermark水印 + +数字水印是向数据多媒体(如图像、音频、视频信号等)中添加某些数字信息以达到文件真伪鉴别、版权保护等功能。嵌入的水印信息隐藏于宿主文件中,不影响原始文件的可观性和完整性。 + +用户可以将BOS中的一个Object创建为水印,获得对应的watermarkId。然后在转码任务中将此水印添加到目的多媒体文件。 + +## 创建水印 + +如果需要创建一个水印, 指定水印的位置, 并获得水印的唯一ID。其中bucket是水印文件所在bucket名称,key是水印文件在该bucket中的文件名。可以参考如下代码: + +```go +args := &api.CreateWaterMarkArgs{} +args.Bucket = "go-test" +args.Key = "01.jpg" +args.HorizontalAlignment = "right" +args.VerticalAlignment = "top" +createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) +if err != nil { + fmt.Printf("create watermark job error: %+v\n", err) + return +} +fmt.Println("create watermark job success Id: ", createWaterMarkResponse.WatermarkId) +``` + +如果需要创建一个水印, 指定水印的位置、显示时间段、重复显示次数(动态水印)、自动缩放, 并获得水印的唯一ID,可以参考如下代码: + +```go +args := &api.CreateWaterMarkArgs{} +args.Bucket = "go-test" +args.Key = "01.jpg" +args.HorizontalAlignment = "left" +args.VerticalAlignment = "top" +args.HorizontalOffsetInPixel = 20 +args.VerticalOffsetInPixel = 10 +timeline := &api.Timeline{} +timeline.StartTimeInMillisecond = 1000 +timeline.DurationInMillisecond = 3000 +args.Timeline = timeline +args.Repeated = 1 +args.AllowScaling = true +createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) +if err != nil { + fmt.Printf("create watermark job error: %+v\n", err) + return +} +fmt.Println("create watermark job success Id: ", createWaterMarkResponse.WatermarkId) +``` + +## 查询指定水印 + +如果需要查询已创建的水印,可以参考如下代码: + +```go +waterMarkId := "wmk-xxx" +response, err := MEDIA_CLIENT.GetWaterMark(waterMarkId) +if err != nil { + fmt.Printf("get watermark job error: %+v\n", err) + return +} +fmt.Printf("get watermark job success: %+v\n", response) +``` + +## 查询当前用户水印 + +如果需要查询出本用户所创建的全部水印,可以参考如下代码: + +```go +response, err := MEDIA_CLIENT.ListWaterMark() +if err != nil { + fmt.Printf("get watermark job error: %+v\n", err) + return +} +for _, watermark := range response.Watermarks { + fmt.Printf("watermark job: %+v\n", watermark) +} +``` + +## 删除水印 + +如果需要删除某个已知watermarkId的水印,可以参考如下代码: + +```go +waterMarkId := "wmk-xxx" +err := MEDIA_CLIENT.DeleteWaterMark(waterMarkId) +if err != nil { + fmt.Printf("delete watermark job error: %+v\n", err) + return +} +fmt.Println("delete watermark success") +``` + +# Notification通知 + +通知功能可以在音视频转码任务状态转换时主动向开发者服务器推送消息。 + +## 创建通知 + +如果需要创建通知可以参考如下代码: + +```go +name := "test" +endpoint := "http://www.baidu.com" +err := MEDIA_CLIENT.CreateNotification(name, endpoint) +if err != nil { + fmt.Printf("create notification error: %+v\n", err) + return +} +fmt.Println("create notification success") +``` + +## 查询指定通知 + +如果需要查询已创建的通知,可以参考如下代码: + +```go +name := "test" +response, err := MEDIA_CLIENT.GetNotification(test) +if err != nil { + fmt.Printf("get notification error: %+v\n", err) + return +} +fmt.Printf("get notification success : %+v\n", response) +``` + +## 查询当前用户通知 + +如果需要查询出本用户所创建的全部通知,可以参考如下代码: + +```go +response, err := MEDIA_CLIENT.ListNotification() +if err != nil { + fmt.Printf("list user`s notification error: %+v\n", err) + return +} +for _, notification := range response.Notifications { + fmt.Printf("list notification success : %+v\n", notification) +} +``` + +## 删除通知 + +如果需要删除某个通知,可以参考如下代码: + +```go +name := "test" +err := MEDIA_CLIENT.DeleteNotification(name) +if err != nil { + fmt.Printf("delete notification error: %+v\n", err) + return +} +fmt.Println("delete notification success") +``` + +# 错误处理 + +GO语言以error类型标识错误,MCP支持两种错误见下表: + +| 错误类型 | 说明 | +| --------------- | ------------------ | +| BceClientError | 用户操作产生的错误 | +| BceServiceError | MCP服务返回的错误 | + +用户使用SDK调用MCP相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// MEDIA_CLIENT 为已创建的MCP Client对象 +result, err := MEDIA_CLIENT.ListPipelines() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向MCP发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当MCP服务端出现异常时,MCP服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[MCP错误码](https://cloud.baidu.com/doc/MCT/s/bjwvz5h3i) + +## SDK日志 + +MCP GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +MCP GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +```go +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +```go +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the MCP go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the MCP go sdk") +``` +# 版本变更记录 + +首次发布: + +- MCP支持go-sdk啦,现在您可以通过golang调用MCP-SDK服务。当前SDK能力支持pipeline队列操作、Transcoding-Job转码任务操作、Preset模板操作、Thumbnail-Job缩略图任务操作、Watermark水印任务操作、MediaInfo媒资信息操作、Notification通知操作。 \ No newline at end of file diff --git a/bce-sdk-go/doc/MMS.md b/bce-sdk-go/doc/MMS.md new file mode 100644 index 0000000..9b21e41 --- /dev/null +++ b/bce-sdk-go/doc/MMS.md @@ -0,0 +1,568 @@ +# MMS - 多模态媒资检索 + +## 介绍 + +- 百度智能云多模态媒资(Multimodal Media Search,简称 MMS)基于视频指纹特征与视频内容理解,实现多模态的搜索能力,主要包含以视频搜视频、以图搜视频、以图搜图等功能,赋予用户多模态的高效、精准、智能的搜索能力。 + +## 接口调用准备 + +### Endpoint + +- 目前统一为: mms.bj.baidubce.com + +### AK/SK + +- 要使用百度云 SMS,您需要拥有一个有效的 AK(Access Key ID)和 SK(Secret Access Key)用来进行签名认证。AK/SK 是由系统分配给用户的,均为字符串,用于标识用户,为访问 SMS 做签名验证。 +- 可以通过如下步骤获得并了解您的 AK/SK 信息:[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal)、[创建 AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +### AK/SK 应用及客户端初始化 + +```golang + AK := "" // 填充Access Key ID + SK := "" // 填充Secret Access Key + ENDPOINT := "http://xxx" // 填充Endpoint + CLIENT, err := mms.NewClient(AK, SK, ENDPOINT) // 初始化客户端 +``` + +## 视频入库 + +### 接口描述 + +- 本接口用于向视频库中插入视频特征。 +- 入库接口为异步接口,可通过[查询视频入库结果](#查询视频入库结果)接口查询入库结果。或通过通知服务回调结果。 + +### 请求结构 + + PUT /v{version}/videolib/{libName} + host: mms.bj.baidubce.com + Authorization: + { + "source": videoUrl, + "description": desc, + "notification": notificationName + } + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ------------ | ------ | -------- | --------- | -------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | Body 参数 | 入库视频的 URL | +| description | String | 否 | Body 参数 | 用户对此次请求的描述 | +| notification | String | 否 | Body 参数 | 入库结果通知的名称 | + +- 注:如使用 notification 参数,需提前配置通知名称及对于的回调地址。 + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| -------- | ------ | -------- | +| status | String | 请求结果 | + +### 请求示例 + + PUT /v2/videolib/baiduyun_test + host: mms.bj.baidubce.com + Authorization: + { + "source": "http://test.mp4", + "description": "test", + "notification": "notification_name" + } + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success" + } + +### 通知服务回调结果示例 + + { + "messageId": "a114f8e1-0de0-473f-9f6c-d47e33df5d7d", + "messageBody": "{\"taskId\":\"n7CCcHIBTmikKXpp-AS8\",\"status\":\"success\",\"source\":\"http://test.mp4\",\"duration\":6.5,\"description\":\"\",\"createTime\":\"2020-06-01T15:32:11Z\",\"startTime\":\"2020-06-01T15:32:11Z\",\"updateTime\":\"2020-06-01T15:32:13Z\",\"finishTime\":\"2020-06-01T15:32:13Z\"}" + } + +## 查询视频入库结果 + +### 接口描述 + +- 本接口用于查询视频入库结果。 + +### 请求结构 + + GET /v{version}/videolib/{libName}?source=videoUrl + host: mms.bj.baidubce.com + Authorization: + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| -------- | ------ | -------- | -------- | ---------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | URL 参数 | 入库视频的 URL | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| ----------- | ------ | --------------------------------------------------------------------------------------- | +| status | String | 入库任务状态,取值为 provision/processing/success/failed,分别为排队中/处理中/成功/失败 | +| description | String | 用户入库请求传入的 description 字段 | +| taskId | String | 视频入库任务 ID | +| source | String | 入库视频的 URL | + +### 请求示例 + + GET /v2/videolib/baiduyun_test?source=http://test.mp4 + host: mms.bj.baidubce.com + Authorization: + +### 响应示例 + +- 入库中 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "createTime": "2020-05-13T07:57:25Z", + "description": "", + "source": "http://test.mp4", + "startTime": "2020-05-13T07:57:26Z", + "status": "processing", + "taskId": "VUcJDXIBrTeiQx_QzcWe" + } + +- 入库完成 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "createTime": "2020-05-13T07:55:55Z", + "description": "", + "duration": 6.5, + "finishTime": "2020-05-13T07:55:57Z", + "source": "http://test.mp4", + "startTime": "2020-05-13T07:55:55Z", + "status": "success", + "taskId": "VUcJDXIBrTeiQx_QzcWe" + } + +## 图片入库 + +### 接口描述 + +- 本接口用于向图片库中插入图片特征。 + +### 请求结构 + + PUT /v{version}/imagelib/{libName} + host: mms.bj.baidubce.com + Authorization: + { + "source": imageUrl, + "description": desc + } + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ----------- | ------ | -------- | --------- | -------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的图片库名称 | +| source | String | 是 | Body 参数 | 入库图片的 URL | +| description | String | 否 | Body 参数 | 用户对此次请求的描述 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| -------- | ------ | -------- | +| status | String | 入库结果 | + +### 请求示例 + + PUT /v2/imagelib/baiduyun_test + host: mms.bj.baidubce.com + Authorization: + { + "source": "http://test.jpg", + "description": "test" + } + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success" + } + +## 删除视频库中的视频 + +### 接口描述 + +- 本接口用于删除视频库中某个视频 + +### 请求结构 + + POST /v{version}/videolib/{libName}?deleteVideo=&source=videoUrl + host: mms.bj.baidubce.com + Authorization: + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ----------- | ------ | -------- | -------- | -------------------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | URL 参数 | 要删除视频的 URL(入库时的 URL) | +| deleteVideo | String | 是 | URL 参数 | 标识参数,无内容 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| -------- | ------ | -------- | +| status | String | 删除结果 | + +### 请求示例 + + POST /v2/videolib/baiduyun_test?deleteVideo=&source=http://test.mp4 + host: mms.bj.baidubce.com + Authorization: + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success" + } + +## 删除图片库中的图片 + +### 接口描述 + +- 本接口用于删除图片库中某张图片 + +### 请求结构 + + POST /v{version}/imagelib/{libName}?deleteImage=&source=imageUrl + host: mms.bj.baidubce.com + Authorization: + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ----------- | ------ | -------- | -------- | -------------------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的图片库名称 | +| source | String | 是 | URL 参数 | 要删除图片的 URL(入库时的 URL) | +| deleteImage | String | 是 | URL 参数 | 标识参数,无内容 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| -------- | ------ | -------- | +| status | String | 删除结果 | + +### 请求示例 + + POST /v2/imagelib/baiduyun_test?deleteImage=&source=http://test.jpg + host: mms.bj.baidubce.com + Authorization: + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success" + } + +## 视频检索视频 + +### 接口描述 + +- 本接口使用视频来检索库中存在的相似视频。 +- 本接口为异步接口,可通过[查询视频检索结果](#查询视频检索视频结果)接口查询检索结果。或通过通知服务回调结果。 + +### 请求结构 + + POST /v{version}/videolib/{libName}?searchByVideo + host: mms.bj.baidubce.com + Authorization: + { + "source": videoUrl, + "description": desc, + "notification": notificationName + } + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ------------ | ------ | -------- | --------- | -------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | Body 参数 | 检索视频的 URL | +| description | String | 否 | Body 参数 | 用户对此次请求的描述 | +| notification | String | 否 | Body 参数 | 检索结果通知的名称 | + +- 注:如使用 notification 参数,需提前配置通知名称及对于的回调地址。 + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| -------- | ------ | --------------- | +| status | String | 请求结果 | +| taskId | String | 视频检索任务 ID | + +### 请求示例 + + POST /v2/videolib/baiduyun_test?searchByVideo + host: mms.bj.baidubce.com + Authorization: + { + "source": "https://test.mp4", + "description": "test", + "notification": "notification_name" + } + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success", + "taskId": "VkcZDXIBrTeiQx_QrcXT" + } + +### 通知服务回调结果示例 + + { + "messageId": "360d15ea-14ae-440e-9be1-f1f431beae19", + "messageBody": "{\"taskId\":\"ybjMcnIBFaqg3FXUVQrA\",\"status\":\"success\",\"lib\":\"video_lib\",\"source\":\"https://test.mp4\",\"duration\":6.5,\"description\":\"\",\"createTime\":\"2020-06-02T02:11:33Z\",\"startTime\":\"2020-06-02T02:12:05Z\",\"updateTime\":\"2020-06-02T02:12:08Z\",\"finishTime\":\"2020-06-02T02:12:08Z\",\"results\":[{\"id\":\"n7CCcHIBTmikKXpp-AS8\",\"name\":\"search_hit.mp4\",\"source\":\"http://hit.mp4\",\"duration\":6.5,\"description\":\"\",\"type\":\"SEARCH_VIDEO_BY_VIDEO\",\"score\":100,\"clips\":[{\"inputStartTime\":0.0,\"inputEndTime\":6.5,\"outputStartTime\":0.0,\"outputEndTime\":6.5}]}]}" + } + +## 查询视频检索视频结果 + +### 接口描述 + +- 本接口用于查询视频检索视频任务的结果。 + +请求结构 + + GET /v{version}/videolib/{libName}?searchByVideo&source=videoUrl + host: mms.bj.baidubce.com + Authorization: + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ------------- | ------ | -------- | -------- | ---------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | URL 参数 | 发起检索任务视频的 URL | +| searchByVideo | String | 是 | URL 参数 | 标识参数,无内容 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| ----------------- | ------ | ------------------------------------------------------------------ | +| status | String | 任务状态,取值为 processing/success/failed,分别为处理中/成功/失败 | +| lib | String | 检索的视频库名称 | +| source | String | 检索视频的 URL | +| description | String | 用户传入的请求描述信息 | +| results | List | 检索视频的结果 | +| +score | Double | 检索视频的相似度,取值范围为[0, 100] | +| +source | String | 结果视频的 URL | +| +description | String | 结果视频的描述 | +| +clips | List | 请求成功时才会有此值 | +| ++inputStartTime | Double | 检索视频片段的开始时间,单位:秒 | +| ++inputEndTime | Double | 检索视频片段的结束时间,单位:秒 | +| ++outputStartTime | Double | 底库视频片段的开始时间,单位:秒 | +| ++outputEndTime | Double | 底库视频片段的结束时间,单位:秒 | +| error | Object | 请求失败时才会有此值 | +| +code | String | 请求失败时才会有此值,表示错误码 | +| +message | String | 请求失败时才会有此值,表示错误信息 | + +### 请求示例 + + GET /v2/videolib/baiduyun_test?searchByVideo&source=https://test.mp4 + host: mms.bj.baidubce.com + Authorization: + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status":"success", + "lib":"baiduyun_test", + "source":"https://test.mp4", + "description":"test", + "results":[ + { + "source":"https://test.mp4", + "description":"test", + "score":100, + "clips":[ + { + "inputStartTime":0.08, + "inputEndTime":20.16, + "outputStartTime":0.08, + "outputEndTime":20.16 + } + ] + } + ] + } + +## 图片检索图片 + +### 接口描述 + +- 本接口使用图片来检索库中存在的相似图片。 + +### 请求结构 + + POST /v{version}/imagelib/{libName}?searchByImage + host: mms.bj.baidubce.com + Authorization: + { + "source": imageUrl, + "description": desc + } + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ----------- | ------ | -------- | --------- | -------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的图片库名称 | +| source | String | 是 | Body 参数 | 检索图片的 URL | +| description | String | 否 | Body 参数 | 用户对此次请求的描述 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| ------------ | ------ | ---------------------------------------------- | +| status | String | 请求结果 | +| lib | String | 检索的图片库名称 | +| source | String | 用户传入的图片 URL | +| description | String | 用户传入的请求描述信息 | +| results | List | 检索图片的结果 | +| +distance | Double | 检索图片的相似度,取值范围为[0, 1],越小越相似 | +| +source | String | 结果图片的 URL | +| +description | String | 结果图片的描述 | +| error | Object | 请求失败时才会有此值 | +| +code | String | 请求失败时才会有此值,表示错误码 | +| +message | String | 请求失败时才会有此值,表示错误信息 | + +### 请求示例 + + POST /v2/imagelib/baiduyun_test?searchByImage + host: mms.bj.baidubce.com + Authorization: + { + "source": "http://test.jpg", + "description": "nothing to desc" + } + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success", + "lib":" baiduyun_test", + "source": "http://test.jpg", + "description": "nothing to desc", + "results": [ + { + "distance": 0.12, + "source": "http://test2.jpg", + "description":"nothing to desc" + } + ] + } + +## 图片检索视频 + +### 接口描述 + +- 本接口使用图片来检索库中存在的包含相似图片的视频。 + +### 请求结构 + + POST /v{version}/videolib/{libName}?searchByImage + host: mms.bj.baidubce.com + Authorization: + { + "source": imageUrl, + "description": desc + } + +### 请求参数 + +| 参数名称 | 类型 | 是否必需 | 参数位置 | 描述 | +| ----------- | ------ | -------- | --------- | -------------------- | +| version | String | 是 | URL 参数 | API 版本号 | +| libName | String | 是 | URL 参数 | 用户的视频库名称 | +| source | String | 是 | Body 参数 | 检索图片的 URL | +| description | String | 否 | Body 参数 | 用户对此次请求的描述 | + +### 响应参数 + +| 参数名称 | 类型 | 描述 | +| ------------ | ------ | -------------------------------------------------- | +| status | String | 请求结果 | +| lib | String | 检索的视频库名称 | +| source | String | 用户传入的图片 URL | +| description | String | 用户传入的请求描述信息 | +| results | List | 检索视频的结果 | +| +source | String | 结果视频的 URL | +| +distance | Double | 结果视频中命中最相似图片的相似度,取值范围为[0, 1] | +| +description | String | 结果视频的描述 | +| +frames | List | 结果视频中对应的图片 | +| ++distance | Double | 视频中对应图片的相似度,取值范围为[0, 1] | +| ++timestamp | Double | 视频中对应图片的时间戳,单位为秒(s) | +| error | Object | 请求失败时才会有此值 | +| +code | String | 请求失败时才会有此值,表示错误码 | +| +message | String | 请求失败时才会有此值,表示错误信息 | + +### 请求示例 + + POST /v2/videolib/baiduyun_test?searchByImage + host: mms.bj.baidubce.com + Authorization: + { + "source": "http://test.jpg", + "description": "nothing to desc" + } + +### 响应示例 + + HTTP/1.1 200 OK + Content-Type: application/json;charset=UTF-8 + { + "status": "success", + "lib":" baiduyun_test", + "source": "http://test.jpg", + "description": "nothing to desc", + "results": [ + { + "source": "http://test2.jpg", + "description": "nothing to desc", + "distance": 0.12, + "frames": [ + { + "distance": 0.12, + "timestamp": 3.4333 + } + ] + } + ] + } diff --git a/bce-sdk-go/doc/RDS.md b/bce-sdk-go/doc/RDS.md new file mode 100644 index 0000000..fde4ff9 --- /dev/null +++ b/bce-sdk-go/doc/RDS.md @@ -0,0 +1,2435 @@ +# RDS服务 + +# 概述 + +本文档主要介绍RDS GO SDK的使用。在使用本文档前,您需要先了解RDS的一些基本知识,并已开通了RDS服务。若您还不了解RDS,可以参考[产品描述](https://cloud.baidu.com/doc/RDS/s/ujwvyzdzg)和[操作指南](https://cloud.baidu.com/doc/RDS/s/Qjwvz0ikk)。 + +本SDK基于官方开放API进行封装,详细参数解释可参考相关API说明文档[API参考](https://cloud.baidu.com/doc/RDS/s/ajwvz0x1m)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[RDS服务域名](https://cloud.baidu.com/doc/RDS/s/Ejwvz0uoq)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华北-保定”、“华南-广州”、“华东-苏州”、“金融华中-武汉”、“华东-上海”、“中国香港”、“新加坡”区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +北京 | rds.bj.baidubce.com | HTTP and HTTPS +保定 | rds.bj.baidubce.com | HTTP and HTTPS +广州 | rds.gz.baidubce.com | HTTP and HTTPS +苏州 | rds.su.baidubce.com | HTTP and HTTPS +武汉 | rds.fwh.baidubce.com| HTTP and HTTPS +上海 | rds.fsh.baidubce.com| HTTP and HTTPS +香港 | rds.hkg.baidubce.com| HTTP and HTTPS +新加坡 | rds.sin.baidubce.com| HTTP and HTTPS +## 获取密钥 + +要使用百度云RDS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问RDS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建RDS Client + +RDS Client是RDS服务的客户端,为开发者与RDS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建RDS Client + +通过AK/SK方式访问RDS,用户可以参考如下代码新建一个RDS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/rds" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个RDSClient + rdsClient, err := rds.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`rds.bj.baidubce.com`。 + +### 使用STS创建RDS Client + +**申请STS token** + +RDS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问RDS,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建RDS Client** + +申请好STS后,可将STS Token配置到RDS Client中,从而实现通过STS Token创建RDS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建RDS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/rds" //导入RDS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建RDS服务的Client对象,Endpoint使用默认值 + rdsClient, err := rds.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "rds.bj.baidubce.com") + if err != nil { + fmt.Println("create rds client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + rdsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置RDS Client时,无论对应RDS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问RDS + +RDS支持HTTPS传输协议,您可以通过在创建RDS Client对象时指定的Endpoint中指明HTTPS的方式,在RDS GO SDK中使用HTTPS访问RDS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +ENDPOINT := "https://rds.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +rdsClient, _ := rds.NewClient(AK, SK, ENDPOINT) +``` + +## 配置RDS Client + +如果用户需要配置RDS Client的一些细节的参数,可以在创建RDS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问RDS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +//创建RDS Client对象 +AK, SK := , +ENDPOINT := "rds.bj.baidubce.com" +client, _ := rds.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +AK, SK := , +ENDPOINT := "rds.bj.baidubce.com" +client, _ := rds.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +AK, SK := , +ENDPOINT := "rds.bj.baidubce.com" +client, _ := rds.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问RDS时,创建的RDS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建RDS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# RDS管理 + +云数据库 RDS (Relational Database Service)是专业、高性能、高可靠的云数据库服务。云数据库 RDS 提供 Web 界面进行配置、操作数据库实例,还为您提供可靠的数据备份和恢复、完备的安全管理、完善的监控、轻松扩展等功能支持。相对于自建数据库,云数据库 RDS 具有更经济、更专业、更高效、更可靠、简单易用等特点,使您能更专注于核心业务。 + +## 创建RDS主实例 + +使用以下代码可以创建一个RDS主实例 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.CreateRdsArgs{ + // 指定rds的数据库引擎,取值mysql,sqlserver,postgresql,必选 + Engine: "mysql", + // 指定rds的数据库版本,必选 + EngineVersion: "5.6", + // 计费相关参数,PaymentTiming取值为 预付费:Prepaid,后付费:Postpaid;Reservation:支付方式为后支付时不需要设置,预支付时必须设置;必选 + Billing: rds.Billing{ + PaymentTiming: "Postpaid", + //Reservation: rds.Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + // 预付费时可指定自动续费参数 AutoRenewTime 和 AutoRenewTimeUnit + // 自动续费时长(续费单位为year 不大于3,续费单位为month 不大于9) + // AutoRenewTime: 1, + // 自动续费单位("year";"month") + // AutoRenewTimeUnit: "year", + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //磁盘类型, normal_io:本地盘ssd磁盘, cloud_high:高性能云磁盘, cloud_nor:通用型SSD, cloud_enha:增强型SSD, 必选 + DiskIoType: "normal_io", + //批量创建云数据库 RDS 实例个数, 最大不超过10,默认1,可选 + PurchaseCount: 1, + //rds实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //所属系列,Basic:单机基础版,Standard:双机高可用版。仅SQLServer 2012sp3 支持单机基础版。默认Standard,可选 + Category: "Standard", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames: []string{"cn-bj-d"}, + //vpc,如果不提供则属于默认vpc,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []rds.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateRds(args) +if err != nil { + fmt.Printf("create rds error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create rds success, instanceId: ", e) +} +``` + +> 注意: +> - 实例可选套餐详见(https://cloud.baidu.com/doc/RDS/s/9jwvz0wd3) +> - 创建计费方式为后付费的实例需要账户现金余额+通用代金券大于100;预付费方式的实例则需要账户现金余额大于等于实例费用。 +> - 支持批量创建,且如果创建过程中有一个实例创建失败,所有实例将全部回滚,均创建失败。 +> - 创建接口为异步创建,可通过查询指定实例详情接口查询实例状态。 + +## 创建RDS只读实例 + +使用以下代码可以创建一个RDS只读实例 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.CreateReadReplicaArgs{ + //主实例ID,必选 + SourceInstanceId: "sourceInstanceId" + // 计费相关参数,只读实例只支持后付费Postpaid,必选 + Billing: rds.Billing{ + PaymentTiming: "Postpaid", + }, + // CPU核数,必选 + CpuCount: 1, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 1, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 5, + //批量创建云数据库 RDS 只读实例个数, 目前只支持一次创建一个,可选 + PurchaseCount: 1, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + ZoneNames: []string{"cn-bj-d"}, + //与主实例 vpcId 相同,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []rds.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateReadReplica(args) +if err != nil { + fmt.Printf("create rds readReplica error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create rds readReplica success, instanceId: ", e) +} +``` + +> 注意: +> - 需要在云数据库 RDS 主实例的基础上进行创建 +> - 实例可选套餐详见(https://cloud.baidu.com/doc/RDS/s/9jwvz0wd3) +> - 仅数据库类型为 MySQL 的主实例支持创建只读实例 +> - 只读实例的数据库引擎和数据库版本与主实例相同,无需设置,主实例版本最低是 MySQL 5.6 +> - 只读实例的磁盘容量不能小于主实例的磁盘容量 +> - 只读实例的 vpcId 需跟主实例一致 +> - 一个云数据库 RDS 实例,最多只能有 5 个只读实例,且一次只能创建一个 +> - 只读实例只支持后付费方式购买 + +## 创建RDS代理实例 + +使用以下代码可以创建一个RDS代理实例 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.CreateRdsProxyArgs{ + //主实例ID,必选 + SourceInstanceId: "sourceInstanceId" + // 计费相关参数,代理实例只支持后付费Postpaid,必选 + Billing: rds.Billing{ + PaymentTiming: "Postpaid", + }, + // 代理实例节点数。取值范围2,4,6,8,16,必选 + NodeAmount: 2, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + InstanceName: "instanceName", + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a",建议与主实例的可用区保持一致 + ZoneNames: []string{"cn-bj-d"}, + //与主实例 vpcId 相同,可选 + VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + Subnets: []rds.SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-IyWRnII7", + }, + }, + // 实例绑定的标签信息,可选 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateRdsProxy(args) +if err != nil { + fmt.Printf("create rds proxy error: %+v\n", err) + return +} + +for _, e := range result.InstanceIds { + fmt.Println("create rds proxy success, instanceId: ", e) +} +``` + +> 注意: +> - 需要在云数据库 RDS 主实例的基础上进行创建 +> - 仅数据库类型为 MySQL 的主实例支持创建只读实例 +> - 代理实例套餐和主实例套餐绑定,主实例版本最低是MySQL 5.6 +> - 每个主实例最多可以创建1个代理实例 +> - 需与主实例在同一vpc中 +> - 代理实例只支持后付费方式购买 + +## 查询RDS列表 + +使用以下代码可以查询RDS列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.ListRdsArgs{ + // 批量获取列表的查询的起始位置,是一个由系统生成的字符串,可选 + Marker: "marker", + // 指定每页包含的最大数量(主实例),最大数量不超过1000,缺省值为1000,可选 + MaxKeys: 1, +} +result, err := client.ListRds(args) +if err != nil { + fmt.Printf("list rds error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("rds list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("rds list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("rds list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("rds list maxKeys: ", result.MaxKeys) +// 获取rds的列表信息 +for _, e := range result.Instances { + fmt.Println("rds instanceId: ", e.InstanceId) + fmt.Println("rds instanceName: ", e.InstanceName) + fmt.Println("rds engine: ", e.Engine) + fmt.Println("rds engineVersion: ", e.EngineVersion) + fmt.Println("rds instanceStatus: ", e.InstanceStatus) + fmt.Println("rds cpuCount: ", e.CpuCount) + fmt.Println("rds memoryCapacity: ", e.MemoryCapacity) + fmt.Println("rds volumeCapacity: ", e.VolumeCapacity) + fmt.Println("rds usedStorage: ", e.UsedStorage) + fmt.Println("rds paymentTiming: ", e.PaymentTiming) + fmt.Println("rds instanceType: ", e.InstanceType) + fmt.Println("rds instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("rds instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("rds publicAccessStatus: ", e.PublicAccessStatus) + fmt.Println("rds task: ", e.Task) + fmt.Println("rds vpcId: ", e.VpcId) +} +``` + +> 注意: +> - 只能查看属于自己账号的实例列表。 +> - 接口将每个主实例和其只读、代理实例分成一组,参数maxKeys代表分组数,也就是主实例的个数. + +## 查询指定RDS实例信息 + +使用以下代码可以查询指定RDS实例信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.GetDetail(instanceId) +if err != nil { + fmt.Printf("get rds detail error: %+v\n", err) + return +} + +fmt.Println("rds instanceId: ", result.InstanceId) +fmt.Println("rds instanceName: ", result.InstanceName) +fmt.Println("rds engine: ", result.Engine) +fmt.Println("rds engineVersion: ", result.EngineVersion) +fmt.Println("rds instanceStatus: ", result.InstanceStatus) +fmt.Println("rds cpuCount: ", result.CpuCount) +fmt.Println("rds memoryCapacity: ", result.MemoryCapacity) +fmt.Println("rds volumeCapacity: ", result.VolumeCapacity) +fmt.Println("rds usedStorage: ", result.UsedStorage) +fmt.Println("rds paymentTiming: ", result.PaymentTiming) +fmt.Println("rds instanceType: ", result.InstanceType) +fmt.Println("rds instanceCreateTime: ", result.InstanceCreateTime) +fmt.Println("rds instanceExpireTime: ", result.InstanceExpireTime) +fmt.Println("rds publicAccessStatus: ", result.PublicAccessStatus) +fmt.Println("rds vpcId: ", result.VpcId) + +``` + +## 删除RDS实例 + +使用以下代码可以删除RDS实例。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +//多个实例间用英文半角逗号","隔开,最多可输入10个 +if err := client.DeleteRds(instanceIds); err != nil { + fmt.Printf("delete rds error: %+v\n", err) + return +} +fmt.Printf("delete rds success\n") +``` + +> 注意: +> - 只有付费类型为Postpaid或者付费类型为Prepaid且已过期的实例才可以释放。 +> - 如果主实例被释放,那么和主实例关联的只读实例和代理实例也会被释放。 + +## RDS实例扩缩容 + +使用以下代码可以对RDS实例扩缩容操作。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.ResizeRdsArgs{ + // cpu核数 + CpuCount: 2, + // 内存大小,单位GB + MemoryCapacity: 8, + // 磁盘大小,单位GB,每5G递增 + VolumeCapacity: 20, + // 代理实例节点数,代理实例变配时此项必填 + NodeAmount: 2, + // 是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, +} +err = client.ResizeRds(instanceId, args) +if err != nil { + fmt.Printf("resize rds error: %+v\n", err) + return +} + +fmt.Println("resize rds success.") +``` + +> 注意: +> - 实例可选套餐详见(https://cloud.baidu.com/doc/RDS/s/9jwvz0wd3) +> - 主实例或只读实例变配时至少填写cpuCount、memoryCapacity、volumeCapacity其中的一个。 +> - 实例计费方式采用后付费时,可弹性扩缩容;采用预付费方式,不能进行缩容操作。 +> - 只有实例available状态时才可以进行扩缩容操作。 +> - 实例扩缩容之后会重启一次。 +> - 为异步接口,可通过查询实例详情接口查看instanceStatus是否恢复。 + +## 重启实例 + +使用以下代码可以重启实例。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +err := client.RebootInstance(instanceId) +if err != nil { + fmt.Printf("reboot rds error: %+v\n", err) + return +} +``` + +## 修改实例名称 + +使用以下代码可以修改RDS实例名称。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.UpdateInstanceNameArgs{ + InstanceName: "instanceName", +} +err = client.UpdateInstanceName(instanceId, args) +if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return +} +fmt.Printf("update instance name success\n") +``` +> 注意: +> +> - 实例名称支持大小写字母、数字以及-_ /.等特殊字符,必须以字母开头,长度1-64。 + +## 已创建实例自动续费 + +使用以下代码可以为已创建的预付费实例创建自动续费 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.AutoRenewArgs{ + // 自动续费时长(续费单位为year 不大于3,续费单位为month 不大于9)必选 + AutoRenewTime: 1, + // 自动续费单位("year";"month")必选 + AutoRenewTimeUnit: "year", + // 实例id集合 必选 + InstanceIds: []string{ + "rds-y9dJu77d", + "rds-aQFOoncr", + }, +} +err := client.AutoRenew(args) +if err != nil { + fmt.Printf("create auto renew error: %+v\n", err) + return +} +``` +> 注意: +> +> - 用于已创建的实例开启自动续费。 +> - 可以传入多个实例id,多个实例需保证在同一地域。 + + +## 修改同步模式 + +使用以下代码可以修改RDS实例同步模式。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.ModifySyncModeArgs{ + //"Async"异步复制,"Semi_sync"半同步复制。 + SyncMode: "Async", +} +err = client.ModifySyncMode(instanceId, args) +if err != nil { + fmt.Printf("modify syncMode error: %+v\n", err) + return +} +fmt.Printf("modify syncMode success\n") +``` + +## 修改连接信息 + +使用以下代码可以修改RDS域名前缀。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.ModifyEndpointArgs{ + Address: "newAddress", +} +err = client.ModifyEndpoint(instanceId, args) +if err != nil { + fmt.Printf("modify endpoint error: %+v\n", err) + return +} +fmt.Printf("modify endpoint success\n") +``` + +> 注意: +> +> - 只传输域名前缀即可。域名前缀由小写字母和数字组成,以小写字母开头,长度在3-30之间。 + +## 开关公网访问 + +使用以下代码可以修改RDS域名前缀。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.ModifyPublicAccessArgs{ + // true or false + PublicAccess: true, +} +err = client.ModifyPublicAccess(instanceId, args) +if err != nil { + fmt.Printf("modify public access error: %+v\n", err) + return +} +fmt.Printf("modify public access success\n") +``` + + +> 注意: +> +> - true:开启公网访问; false:关闭公网访问。 +## 修改时间窗口 + +使用以下代码可以修改操作时间窗口 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.MaintainTimeArgs{ + MaintainStartTime: "14:00:00", + MaintainDuration: 2, +} +err = client.UpdateMaintainTime(instanceId, args) +if err != nil { + fmt.Printf("update maintain time error: %+v\n", err) + return +} +fmt.Printf("update maintain time success\n") +``` +## 实例开启关闭修改存储自动扩容配置 + +使用以下代码可以开启关闭修改存储自动扩容配置 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.DiskAutoResizeArgs{ + FreeSpaceThreshold: 10, + DiskMaxLimit: 2000, +} +err = client.ConfigDiskAutoResize(instanceId,"open", args) +if err != nil { + fmt.Printf("config disk auto resize error: %+v\n", err) + return +} +fmt.Printf("config disk auto resize success\n") +``` + +## 获取指定实例的自动扩容配置信息 + +使用以下代码可以获取指定实例的自动扩容配置信息 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err = client.GetAutoResizeConfig(instanceId) +if err != nil { + fmt.Printf("get config error: %+v\n", err) + return +} +fmt.Printf("get config success\n") +``` + +## 实例是否支持启用自动扩容 + +使用以下代码可以实例是否支持启用自动扩容 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err = client.EnableAutoExpansion(instanceId) +if err != nil { + fmt.Printf("get enable auto expansion error: %+v\n", err) + return +} +fmt.Printf("get enable auto expansion success\n") +``` + +## 可用区迁移 + +使用以下代码可以操作实例可用区迁移 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.AzoneMigration{ + MasterAzone: "cn-bj-d", + BackupAzone: "cn-bj-e", + ZoneNames: []string{"cn-bj-d", "cn-bj-e"}, + Subnets: []SubnetMap{ + { + ZoneName: "cn-bj-d", + SubnetId: "sbn-nedt51qre6r2", + }, + { + ZoneName: "cn-bj-e", + SubnetId: "sbn-hc20wss3idai", + }, + }, + EffectiveTime: "timewindow", +} +result, err = client.AzoneMigration(instanceId, args) +if err != nil { + fmt.Printf("azone migration error: %+v\n", err) + return +} +fmt.Printf("azone migration success\n") +``` +# 账号管理 + +## 创建账号 + +使用以下代码可以在某个主实例下创建一个新的账号。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +args := &rds.CreateAccountArgs{ + // 账号名称,不能为保留关键字,必选 + AccountName: "accountName", + // 账号的密码,由字母、数字或下划线组成,长度6~32位,密码需要加密传输,禁止明文传输,必选 + Password: "password", + // 账号权限类型,Common:普通帐号,Super:super账号。默认为普通账号,可选 + AccountType: "Common", + // MySQL和SQL Server实例可设置此项,可选 + DatabasePrivileges: []rds.DatabasePrivilege{ + { + //数据库名称 + DbName: "user_photo_001", + //授权类型。ReadOnly:只读,ReadWrite:读写 + AuthType: "ReadOnly", + }, + }, + // 帐号的描述信息,可选 + Desc: "账号user1", + // 帐号归属类型,OnlyMaster:主实例上使用的帐号,RdsProxy:该主实例对应的代理实例上使用的帐号。默认为OnlyMaster账号,可选 + Type: "OnlyMaster", +} +err = client.CreateAccount(instanceId, args) +if err != nil { + fmt.Printf("create account error: %+v\n", err) + return +} + +fmt.Println("create account success.") +``` + +> 注意: +> - 实例状态为Available,实例必须是主实例。 +> - 没有超出实例最大账号数量。 +> - 若实例的数据库引擎为PostgreSQL,则只允许创建Super账号。其它账号和数据库操作通过这个Super账号来管理。 +> - 若实例的数据库引擎为MySQL,则允许创建任意类型的账号。 +> - 若实例的数据库引擎为SQLServer,则只允许创建Common账号。 + +## 查询账号列表 + +使用以下代码可以查询指定实例的账号列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.ListAccount(instanceId) +if err != nil { + fmt.Printf("list account error: %+v\n", err) + return +} + +// 获取account的列表信息 +for _, e := range result.Accounts { + fmt.Println("rds accountName: ", e.AccountName) + fmt.Println("rds desc: ", e.Desc) + fmt.Println("rds status: ", e.Status) + fmt.Println("rds type: ", e.Type) + fmt.Println("rds accountType: ", e.AccountType) +} +``` + +## 查询特定账号信息 + +使用以下代码可以查询特定账号信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.GetAccount(instanceId,accountName) +if err != nil { + fmt.Printf("get account error: %+v\n", err) + return +} + +// 获取account的列表信息 +fmt.Println("rds accountName: ", result.AccountName) +fmt.Println("rds desc: ", result.Desc) +fmt.Println("rds status: ", result.Status) +fmt.Println("rds type: ", result.Type) +fmt.Println("rds accountType: ", result.AccountType) +``` +## 更新账号描述信息 + +使用以下代码可以更新账号描述信息 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &ModifyAccountDesc{ + Remark: "test", +} +err := client.ModifyAccountDesc(instanceId,accountName,args) +if err != nil { + fmt.Printf("modify account desc error: %+v\n", err) + return +} +fmt.Printf("modify account desc success\n") +``` + +## 删除特定账号信息 + +使用以下代码可以删除特定账号信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.DeleteAccount(instanceId,accountName) +if err != nil { + fmt.Printf("delete account error: %+v\n", err) + return +} +fmt.Printf("delete account success\n") +``` +## 更新账号密码 + +使用以下代码可以更新账号密码 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &UpdatePasswordArgs{ + Password: "test", +} +err := client.UpdateAccountPassword(instanceId,accountName,args) +if err != nil { + fmt.Printf("update account password error: %+v\n", err) + return +} +fmt.Printf("update account password success\n") +``` +## 更新账号权限 +使用以下代码可以更新账号密码 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &UpdateAccountPrivileges{ + DatabasePrivileges: []DatabasePrivilege{{ + DbName: "test_db", + AuthType: "ReadOnly", + }}, +} +err := client.UpdateAccountPrivileges(instanceId,accountName,args) +if err != nil { + fmt.Printf("update account privilege error: %+v\n", err) + return +} +fmt.Printf("update account privilege success\n") +``` +# 参数管理 + +## 获取参数列表 + +使用以下代码可以获取一个实例下的数据库参数列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ListParameters(instanceId) +if err != nil { + fmt.Printf("get parameter list error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Printf("get parameter list success\n") +fmt.Println(result.Etag) +``` + +> 注意: +> +> - 在修改配置参数时需要通过该接口获取Etag。 + +## 修改配置参数 + +使用以下代码可以云数据库 RDS for MySQL 的参数配置。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ListParameters(instanceId) +if err != nil { + fmt.Printf("get parameter list error: %+v\n", err) + return +} +fmt.Printf("get parameter list success\n") +fmt.Println(result.Etag) + +args := &rds.UpdateParameterArgs{ + Parameters: []rds.KVParameter{ + { + Name: "connect_timeout", + Value: "15", + }, + }, + } +er := client.UpdateParameter(instanceId, result.Etag, args) +if er != nil { + fmt.Printf("update parameter error: %+v\n", er) + return +} +fmt.Printf("update parameter success\n") +``` + +> 注意: +> +> - 在修改配置参数时需要通过获取参数列表接口获取最新的Etag。 + +## 参数修改历史 + +使用以下代码可以云数据库 RDS for MySQL 的参数配置。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ParameterHistory(instanceId) +if err != nil { + fmt.Printf("get parameter history error: %+v\n", err) + return +} +fmt.Printf("get parameter history success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +# 数据库管理 + +## 修改数据库端口 + +使用以下代码可以修改数据库端口 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.UpdateDatabasePortArgs{ + EntryPort: 3309, +}) +err := client.UpdateDatabasePort(instanceId, args) +if err != nil { + fmt.Printf("update database port error: %+v\n", err) + return +} +fmt.Printf("update database port success\n") + +``` + +## 获取数据库列表 + +使用以下代码可以获取数据库列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ListDatabases(instanceId) +if err != nil { + fmt.Printf("get database list error: %+v\n", err) + return +} +fmt.Printf("get database list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) + +``` + +## 修改数据库描述 + +使用以下代码可以修改数据库描述 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ModifyDatabaseDesc{ + Remark: "test", +} +err := client.ModifyDatabaseDesc(instanceId, "test_db", args) +if err != nil { + fmt.Printf("modify database discription error: %+v\n", err) + return +} +fmt.Printf("modify database discriptio success\n") + +``` + +## 删除数据库 + +使用以下代码可以删除数据库 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +err := client.DeleteDatabase(instanceId, "test_db") +if err != nil { + fmt.Printf("delete database error: %+v\n", err) + return +} +fmt.Printf("delete database success\n") + +``` +## 创建数据库 + +使用以下代码可以创建数据库 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.CreateDatabaseArgs{ + CharacterSetName: "utf8", + DbName: "test_db", + Remark: "test_db", + AccountPrivileges: []AccountPrivilege{ + { + AccountName: "baidu", + AuthType: "ReadOnly", + }, + }, +} +err := client.CreateDatabase(instanceId, args) +if err != nil { + fmt.Printf("create database error: %+v\n", err) + return +} +fmt.Printf("create database success\n") + +``` + +# 任务管理 + +## 获取任务列表 + +使用以下代码可以获取任务列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.TaskListArgs{ + InstanceId: instanceId, +} +result, err := client.TaskList(args) +if err != nil { + fmt.Printf("get task list error: %+v\n", err) + return +} +fmt.Printf("get task list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +# 回收站管理 + +## 查询回收站实例列表 + +使用以下代码可以获取回收站实例列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ListRdsArgs{} +result, err := client.ListRecyclerInstance(args) +if err != nil { + fmt.Printf("get recycle list error: %+v\n", err) + return +} +fmt.Printf("get recycle list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 实例开机 + +使用以下代码可以开机。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.RecyclerRecoverArgs{ + InstanceIds: []string{instanceId}, +} +err := client.RecyclerRecover(args) +if err != nil { + fmt.Printf("recycler recover error: %+v\n", err) + return +} +fmt.Printf("recycler recover success\n") +``` +## 删除单个回收站实例 + +使用以下代码可以删除单个回收站实例 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +err := client.DeleteRecyclerInstance(instanceId) +if err != nil { + fmt.Printf("delete error: %+v\n", err) + return +} +fmt.Printf("delete success\n") +``` + +# 实例组管理 + +## 创建实例组 +使用以下代码可以创建实例组。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.InstanceGroupArgs{ + Name: "test_group", + LeaderId: instanceId, +} +err := client.CreateInstanceGroup(args) +if err != nil { + fmt.Printf("create instance group error: %+v\n", err) + return +} +fmt.Printf("create instance group success\n") +``` + +## 实例组列表 +使用以下代码可以获取实例组列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ListInstanceGroupArgs{ + Manner: "page", +} +result, err := client.ListInstanceGroup(args) +if err != nil { + fmt.Printf("get instance group list error: %+v\n", err) + return +} +fmt.Printf("get instance group list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 实例组详情 +使用以下代码可以获取实例组详情 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.InstanceGroupDetail(groupId) +if err != nil { + fmt.Printf("get instance group detail error: %+v\n", err) + return +} +fmt.Printf("get instance group detail success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 实例组前置检查-GTID检查 +使用以下代码可以进行GTID检查 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.CheckGtidArgs{ + InstanceId: instanceId, +} +result, err := client.InstanceGroupCheckGtid(args) +if err != nil { + fmt.Printf("GTID check error: %+v\n", err) + return +} +fmt.Printf("GTID check success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 实例组前置检查-连通性检查 +使用以下代码可以进行连通性检查 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.CheckPingArgs{ + SourceId: instanceId, + TargetId: instanceId, +} +result, err := client.InstanceGroupCheckPing(args) +if err != nil { + fmt.Printf("check ping error: %+v\n", err) + return +} +fmt.Printf("check ping success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 实例组前置检查-数据检查 +使用以下代码可以进行数据检查 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.CheckDataArgs{ + InstanceId: instanceId, +} +result, err := client.InstanceGroupCheckData(args) +if err != nil { + fmt.Printf("check data error: %+v\n", err) + return +} +fmt.Printf("check data success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 小版本前置检查 +使用以下代码可以进行小版本前置检查 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.CheckVersionArgs{ + LeaderId: instanceId, + FollowerId: instanceId, +} +result, err := client.InstanceGroupCheckVersion(args) +if err != nil { + fmt.Printf("check data error: %+v\n", err) + return +} +fmt.Printf("check data success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 修改热活实例组的名称 +使用以下代码可以修改热活实例组的名称 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.InstanceGroupNameArgs{ + Name: "test_group_name", +} +err := client.UpdateInstanceGroupName(groupId, args) +if err != nil { + fmt.Printf("update instance group name error: %+v\n", err) + return +} +fmt.Printf("update instance group name success\n") +``` + +## 加入热活实例组 +使用以下代码可以加入热活实例组 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.InstanceGroupAddArgs{ + FollowerId: instanceId, +} +err := client.InstanceGroupAdd(groupId, args) +if err != nil { + fmt.Printf("add instance group error: %+v\n", err) + return +} +fmt.Printf("add instance group success\n") +``` + +## 批量加入热活实例组 +使用以下代码可以批量加入热活实例组 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.InstanceGroupBatchAddArgs{ + FollowerIds: []string{instanceId}, + Name: "test_group_name", + LeaderId: instanceId, +} +err := client.InstanceGroupBatchAdd(args) +if err != nil { + fmt.Printf("batch add instance group error: %+v\n", err) + return +} +fmt.Printf("batch add instance group success\n") +``` +## 强制切换热活实例组 +使用以下代码可以强制切换热活实例组 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ForceChangeArgs{ + LeaderId: instanceId, + Force: 0, +} +err := client.InstanceGroupForceChange(groupId, args) +if err != nil { + fmt.Printf("instance group force change error: %+v\n", err) + return +} +fmt.Printf("instance group force change success\n") +``` +## 主角色变更 +使用以下代码可以主角色变更 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GroupLeaderChangeArgs{ + LeaderId: instanceId, +} +err := client.InstanceGroupLeaderChange(groupId, args) +if err != nil { + fmt.Printf("instance group leader change error: %+v\n", err) + return +} +fmt.Printf("instance group leader change success\n") +``` + +## 退出热活实例组 +使用以下代码可以退出热活实例组 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +err := client.InstanceGroupRemove(groupId, instanceId) +if err != nil { + fmt.Printf("instance group remove error: %+v\n", err) + return +} +fmt.Printf("instance group remove success\n") +``` + +## 删除热活实例组 +使用以下代码可以删除热活实例组 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +err := client.DeleteInstanceGroup(groupId) +if err != nil { + fmt.Printf("delete instance group error: %+v\n", err) + return +} +fmt.Printf("delete instance group success\n") +``` + +# 版本管理 +## 查看实例允许升级的小版本列表 + +使用以下代码可以查看实例允许升级的小版本列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.InstanceMinorVersionList(instanceId) +if err != nil { + fmt.Printf("get instance minor version list error: %+v\n", err) + return +} +fmt.Printf("get instance minor version list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) + +``` +## 实例升级小版本 + +使用以下代码可以升级小版本 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.UpgradeMinorVersionArgs{ + TargetMinorVersion: "5.7.38", + EffectiveTime: "immediate", +} +err := client.InstanceUpgradeMinorVersion(instanceId, args) +if err != nil { + fmt.Printf("update instance minor version list error: %+v\n", err) + return +} +fmt.Printf("update instance minor version list success\n") + +``` +# SmartDBA +## 查询慢SQL诊断开通状态 +使用以下代码可以查询慢SQL诊断开通状态 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.SlowSqlFlowStatus(instanceId) +if err != nil { + fmt.Printf("get slow sql flow status error: %+v\n", err) + return +} +fmt.Printf("get slow sql flow status success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 开通慢SQL诊断 + +使用以下代码可以开通慢SQL诊断 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +err := client.EnableSlowSqlFlow(instanceId) +if err != nil { + fmt.Printf("enable slow sql flow error: %+v\n", err) + return +} +fmt.Printf("enable slow sql flow success\n") + +``` +## 关闭慢SQL诊断 + +使用以下代码可以关闭慢SQL诊断 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +err := client.DisableSlowSqlFlow(instanceId) +if err != nil { + fmt.Printf("disable slow sql flow error: %+v\n", err) + return +} +fmt.Printf("disable slow sql flow success\n") + +``` + +## 获取慢SQL诊断列表 + +使用以下代码可以获取慢SQL诊断列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &GetSlowSqlArgs{} +result, err := client.GetSlowSqlList(instanceId, args) +if err != nil { + fmt.Printf("get slow sql flow list error: %+v\n", err) + return +} +fmt.Printf("get slow sql flow list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 根据SQLID获取慢SQL + +使用以下代码可以根据SQLID获取慢SQL + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowSqlBySqlId(instanceId, sqlId) +if err != nil { + fmt.Printf("get slow sql detail by sqlid error: %+v\n", err) + return +} +fmt.Printf("get slow sql detail by sqlid success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL说明 + +使用以下代码可以获取慢SQL说明 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowSqlExplain(instanceId, sqlId, db) +if err != nil { + fmt.Printf("get slow sql explain error: %+v\n", err) + return +} +fmt.Printf("get slow sql explain success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 获取SQL模板维度的统计信息 + +使用以下代码可以获取SQL模板维度的统计信息 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetSlowSqlArgs{} +result, err := client.GetSlowSqlStatsDigest(instanceId, args) +if err != nil { + fmt.Printf("get slow sql stats digest error: %+v\n", err) + return +} +fmt.Printf("get slow sql stats digest success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 获取慢SQL耗时分布 + +使用以下代码可以获取慢SQL耗时分布 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetSlowSqlDurationArgs{} +result, err := client.GetSlowSqlDuration(instanceId, args) +if err != nil { + fmt.Printf("get slow sql duration error: %+v\n", err) + return +} +fmt.Printf("get slow sql duration success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL来源IP分布 + +使用以下代码可以获取慢SQL来源IP分布 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetSlowSqlSourceArgs{} +result, err := client.GetSlowSqlSource(instanceId, args) +if err != nil { + fmt.Printf("get slow sql source error: %+v\n", err) + return +} +fmt.Printf("get slow sql source success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL中的表 + +使用以下代码可以获取慢SQL中的表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowSqlSchema(instanceId, sqlId, db) +if err != nil { + fmt.Printf("get slow sql schema error: %+v\n", err) + return +} +fmt.Printf("get slow sql schema success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL中的列 + +使用以下代码可以获取慢SQL中的列 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowSqlTable(instanceId, sqlId, db, table) +if err != nil { + fmt.Printf("get slow sql table error: %+v\n", err) + return +} +fmt.Printf("get slow sql table success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 获取慢SQL表中的索引 + +使用以下代码可以获取慢SQL表中的索引 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetSlowSqlIndexArgs{ + SqlId: "e9fa9802-0d0e-41b4-b3ba-6496466b6cad", + Schema: "db1", + Table: "table1", +} +result, err := client.GetSlowSqlIndex(instanceId, args) +if err != nil { + fmt.Printf("get slow sql index error: %+v\n", err) + return +} +fmt.Printf("get slow sql index success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL趋势 + +使用以下代码可以获取慢SQL趋势 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetSlowSqlTrendArgs{ + Start: "2023-05-05T05:30:13.000Z", + End: "2023-05-06T05:30:13.000Z", +} +result, err := client.GetSlowSqlTrend(instanceId, args) +if err != nil { + fmt.Printf("get slow sql trend error: %+v\n", err) + return +} +fmt.Printf("get slow sql trend success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取慢SQL调优建议 + +使用以下代码可以获取慢SQL调优建议 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowSqlAdvice(instanceId, sqlId, db) +if err != nil { + fmt.Printf("get slow sql advice error: %+v\n", err) + return +} +fmt.Printf("get slow sql advice success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取库表空间概况 + +使用以下代码可以获取库表空间概况 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetDiskInfo(instanceId) +if err != nil { + fmt.Printf("get disk info error: %+v\n", err) + return +} +fmt.Printf("get disk info success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取数据空间的数据库列表 + +使用以下代码可以获取数据空间的数据库列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetDbListSize(instanceId) +if err != nil { + fmt.Printf("get db list size info error: %+v\n", err) + return +} +fmt.Printf("get db list size info success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取数据空间表的详情 + +使用以下代码可以获取数据空间表的详情 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetTableListArgs{ + DbName: "db1", +} +result, err := client.GetTableListInfo(instanceId, args) +if err != nil { + fmt.Printf("get table list error: %+v\n", err) + return +} +fmt.Printf("get table list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取指定会话kill类型的相关参数 + +使用以下代码可以获取指定会话kill类型的相关参数 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetKillSessionTypes(instanceId) +if err != nil { + fmt.Printf("get kill session types error: %+v\n", err) + return +} +fmt.Printf("get kill session types success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取指定实例的会话概览 + +使用以下代码可以获取指定实例的会话概览 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSessionSummary(instanceId) +if err != nil { + fmt.Printf("get kill session summary error: %+v\n", err) + return +} +fmt.Printf("get kill session summary success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取指定实例的实时会话 + +使用以下代码可以获取指定实例的实时会话 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.SessionDetailArgs{} +result, err := client.GetSessionDetail(instanceId, args) +if err != nil { + fmt.Printf("get session detail error: %+v\n", err) + return +} +fmt.Printf("get session detail success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 校验执行kill操作的数据库用户及密码是否正确 + +使用以下代码可以校验执行kill操作的数据库用户及密码是否正确 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.KillSessionAuthArgs{} +result, err := client.CheckKillSessionAuth(instanceId, args) +if err != nil { + fmt.Printf("check kill session auth error: %+v\n", err) + return +} +fmt.Printf("check kill session auth success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取指定实例的会话kill记录 + +使用以下代码可以获取指定实例的会话kill记录 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.KillSessionHistory{} +result, err := client.GetKillSessionHistory(instanceId, args) +if err != nil { + fmt.Printf("get kill session history error: %+v\n", err) + return +} +fmt.Printf("get kill session history success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 根据传入的kill类型及类型所对应的值执行kill会话的操作 + +使用以下代码可以根据传入的kill类型及类型所对应的值执行kill会话的操作 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.KillSessionArgs{} +result, err := client.KillSession(instanceId, args) +if err != nil { + fmt.Printf("kill session error: %+v\n", err) + return +} +fmt.Printf("kill session success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取指定实例的会话统计 + +使用以下代码可以获取指定实例的会话统计 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSessionStatistics(instanceId) +if err != nil { + fmt.Printf("get session statistics error: %+v\n", err) + return +} +fmt.Printf("get session statistics success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 查询错误日志服务是否开启 + +使用以下代码可以查询错误日志服务是否开启 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetErrorLogStatus(instanceId) +if err != nil { + fmt.Printf("get error log status error: %+v\n", err) + return +} +fmt.Printf("get error log status success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 开启错误日志服务 + +使用以下代码可以开启错误日志服务 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.EnableErrorLog(instanceId) +if err != nil { + fmt.Printf("enable error log status error: %+v\n", err) + return +} +fmt.Printf("enable error log status success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 关闭错误日志服务 + +使用以下代码可以关闭错误日志服务 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.DisableErrorLog(instanceId) +if err != nil { + fmt.Printf("disable error log status error: %+v\n", err) + return +} +fmt.Printf("disable error log status success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取错误日志列表 + +使用以下代码可以获取错误日志列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ErrorLogListArgs{} +result, err := client.GetErrorLogList(instanceId, args) +if err != nil { + fmt.Printf("get error log list error: %+v\n", err) + return +} +fmt.Printf("get error log list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 获取实例限流规则列表 + +使用以下代码可以获取实例限流规则列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSqlFilterList(instanceId) +if err != nil { + fmt.Printf("get sql filter list error: %+v\n", err) + return +} +fmt.Printf("get sql filter list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 获取某个限流规则详情 + +使用以下代码可以获取某个限流规则详情 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSqlFilterDetail(instanceId, filterId) +if err != nil { + fmt.Printf("get sql filter detail error: %+v\n", err) + return +} +fmt.Printf("get sql filter detail success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 添加一条限流规则 + +使用以下代码可以添加一条限流规则 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.SqlFilterArgs{ + FilterType: "SELECT", + FilterKey: "123", + FilterLimit: 0, +} +result, err := client.AddSqlFilter(instanceId, args) +if err != nil { + fmt.Printf("add sql filter error: %+v\n", err) + return +} +fmt.Printf("add sql filter success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 更新一条限流规则 + +使用以下代码可以更新一条限流规则 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.SqlFilterArgs{ + FilterType: "SELECT", + FilterKey: "123", + FilterLimit: 0, +} +result, err := client.UpdateSqlFilter(instanceId, args) +if err != nil { + fmt.Printf("update sql filter error: %+v\n", err) + return +} +fmt.Printf("update sql filter success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 开启关闭某个限流规则 + +使用以下代码可以开启关闭某个限流规则 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.StartOrStopSqlFilterArgs{ + Action: "OFF", +} +result, err := client.StartOrStopSqlFilter(instanceId, filterId, args) +if err != nil { + fmt.Printf("start or stop sql filter error: %+v\n", err) + return +} +fmt.Printf("start or stop sql filter success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 删除某个限流规则 + +使用以下代码可以删除某个限流规则 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.DeleteSqlFilter(instanceId, filterId) +if err != nil { + fmt.Printf("delete sql filter error: %+v\n", err) + return +} +fmt.Printf("delete sql filter success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 实例是否支持限流 + +使用以下代码可以实例是否支持限流 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" + +result, err := client.IsAllowedSqlFilter(instanceId) +if err != nil { + fmt.Printf("is allowed sql filter error: %+v\n", err) + return +} +fmt.Printf("is allowed sql filter success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +# Performance +## Kill会话 + +使用以下代码可以Kill会话 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &ProcessArgs{ + Ids: []int64{123}, +} + +err := client.ProcessKill(instanceId, args) +if err != nil { + fmt.Printf("process kill error: %+v\n", err) + return +} +fmt.Printf("process kill success\n") +``` +## 查询innodbstatus快照数据 + +使用以下代码可以查询innodbstatus快照数据 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.InnodbStatus(instanceId) +if err != nil { + fmt.Printf("get innodb status error: %+v\n", err) + return +} +fmt.Printf("get innodb status success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` + +## 查询processlist快照数据 + +使用以下代码可以查询processlist快照数据 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ProcessList(instanceId) +if err != nil { + fmt.Printf("get process list error: %+v\n", err) + return +} +fmt.Printf("get process list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 查询事务列表 + +使用以下代码可以查询事务列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.TransactionList(instanceId) +if err != nil { + fmt.Printf("get transaction list error: %+v\n", err) + return +} +fmt.Printf("get transaction list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +## 查询连接列表 + +使用以下代码可以查询连接列表 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.ConnectionList(instanceId) +if err != nil { + fmt.Printf("get ConnectionList list error: %+v\n", err) + return +} +fmt.Printf("get ConnectionList list success\n") +jsonData, _ := json.Marshal(result) +fmt.Println(string(jsonData)) +``` +# 备份管理 + +## 获取备份列表 + +使用以下代码可以获取一个实例下的备份列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.GetBackupListArgs{} +_, err := client.GetBackupList(instanceId, args) +if err != nil { + fmt.Printf("get backup list error: %+v\n", err) + return +} +fmt.Printf("get backup list success\n") +``` + +> 注意: +> +> - 请求参数 marker 和 maxKeys 不是必须的。 + +## 获取备份详情 + +使用以下代码可以获取一个实例备份的详情信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetBackupDetail(instanceId, backupId) +if err != nil { + fmt.Printf("get backup detail error: %+v\n", err) + return +} +fmt.Printf("get backup detail success\n") +``` + +## 删除备份 + +使用以下代码可以删除手动备份 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +_, err := client.DeleteBackup(instanceId, backupId) +if err != nil { + fmt.Printf("delete backup detail error: %+v\n", err) + return +} +fmt.Printf("delete backup detail success\n") +``` +## 更新备份策略 + +使用以下代码可以更新一个实例的备份策略。 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ModifyBackupPolicyArgs{ + BackupDays: "1,3", + BackupTime: "10:00:00Z", + Persistent: true, + ExpireInDays: 20, +} +err := client.ModifyBackupPolicy(instanceId, args) +if err != nil { + fmt.Printf("modify backup policy error: %+v\n", err) + return +} +fmt.Printf("modify backup policy success\n") +``` +## 获取binlog列表 + +使用以下代码可以获取binlog列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetBinlogList(instanceId, detaTime) +if err != nil { + fmt.Printf("get binlog list error: %+v\n", err) + return +} +fmt.Printf("get binlog list success\n") +``` + +## 获取binlog信息 + +使用以下代码可以获取binlog信息 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetBinlogInfo(instanceId, binlogId, downloadValidTimeInSec) +if err != nil { + fmt.Printf("get binlog detail error: %+v\n", err) + return +} +fmt.Printf("get binlog detail success\n") +``` + +## 按时间点进行库表恢复 + +使用以下代码可以按时间点进行库表恢复 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +recoveryByDatetimeArgs := &RecoveryByDatetimeArgs{ + Datetime: "2022-01-11T16:05:52Z", + Data: []RecoveryData{ + { + DbName: "test_db", + NewDbname: "new_test_db", + RestoreMode: "database", + Tables: []TableData{ + { + TableName: "table_name", + NewTablename: "new_table_name", + }, + }, + }, + }, +} +err := client.RecoveryToSourceInstanceByDatetime(instanceId, recoveryByDatetimeArgs) +if err != nil { + fmt.Printf("recovery by datetime error: %+v\n", err) + return +} +fmt.Printf("recovery by datetime success\n") +``` +## 按备份集进行库表恢复 + +使用以下代码可以按备份集进行库表恢复 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +recoveryBySnapshotArgs := &RecoveryBySnapshotArgs{ + SnapshotId: "1691734023130272802", + Data: []RecoveryData{ + { + DbName: "test_db", + NewDbname: "new_test_db", + RestoreMode: "database", + Tables: []TableData{ + { + TableName: "table_name", + NewTablename: "new_table_name", + }, + }, + }, + }, +} +err := client.RecoveryToSourceInstanceBySnapshot(instanceId, recoveryBySnapshotArgs) +if err != nil { + fmt.Printf("recovery by snapshot error: %+v\n", err) + return +} +fmt.Printf("recovery by snapshot success\n") +``` +# 慢日志下载任务 + +## 慢日志下载任务列表 +``` go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowLogDownloadTaskList(instanceId, datetime) +if err != nil { + fmt.Printf("get slowlog download task list error: %+v\n", err) + return +} +fmt.Printf("get slowlog download task list success\n") +fmt.Printf(result) +``` +## 慢日志下载详情 +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSlowLogDownloadDetail(instanceId, logId, downloadValidTimeInSec) +if err != nil { + fmt.Printf("get slowlog download detail error: %+v\n", err) + return +} +fmt.Printf("get slowlog download detail success\n") +fmt.Printf(result) +``` +# 其它 + +## 获取可用区列表 + +使用以下代码可以获取可用区列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +err = client.GetZoneList() +if err != nil { + fmt.Printf("get zone list error: %+v\n", err) + return +} +fmt.Printf("get zone list success\n") +fmt.Println("rds instanceId: ", result.InstanceId) +``` + +## 获取子网列表 + +使用以下代码可以获取一个实例下的子网列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +args := &rds.ListSubnetsArgs{} +_, err := client.ListSubnets(args) +if err != nil { + fmt.Printf("get subnet list error: %+v\n", err) + return +} +fmt.Printf("get subnet list success\n") +``` + +> 注意: +> +> - 请求参数 vpcId 和 zoneName 不是必须的。 + +## 查看白名单 + +使用以下代码可以获取一个实例下的白名单列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSecurityIps(instanceId) +if err != nil { + fmt.Printf("get securityIp list error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println(result.Etag) +fmt.Printf("get securityIp list success\n") +``` + +> 注意: +> +> - 在更新白名单时需要通过该接口获取最新的Etag。 + +## 更新白名单 + +使用以下代码可以更新一个实例下的白名单列表。 + +```go +// import "github.com/baidubce/bce-sdk-go/services/rds" +result, err := client.GetSecurityIps(instanceId) +if err != nil { + fmt.Printf("get securityIp list error: %+v\n", err) + return +} +fmt.Println(result.Etag) +fmt.Printf("get securityIp list success\n") + +args := &rds.UpdateSecurityIpsArgs{ + SecurityIps: []string{ + "%", + "192.0.0.1", + "192.0.0.2", + }, + } +er := client.UpdateSecurityIps(instanceId, result.Etag, args) +if er != nil { + fmt.Printf("update securityIp list error: %+v\n", er) + return +} +fmt.Printf("update securityIp list success\n") +``` + +> 注意: +> +> - 在更新白名单时需要通过查看白名单接口获取最新的Etag。 +> - 白名单需要全量更新,每次更新需要把全部白名单列表都添加上。 + +# 错误处理 + +GO语言以error类型标识错误,RDS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | RDS服务返回的错误 + +用户使用SDK调用RDS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// rdsClient 为已创建的RDS Client对象 +result, err := client.ListRds() +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向RDS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当RDS服务端出现异常时,RDS服务端会返回给用户相应的错误信息,以便定位问题。 + +## SDK日志 + +RDS GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +RDS GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the RDS go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the RDS go sdk") +``` + + + +首次发布: + + - 支持创建RDS主实例、创建RDS只读实例、创建RDS代理实例、查询RDS列表、查询指定RDS实例信息、删除RDS实例、RDS实例扩缩容、创建账号、查询账号列表、查询特定账号信息、删除特定账号信息。 \ No newline at end of file diff --git a/bce-sdk-go/doc/SCS.md b/bce-sdk-go/doc/SCS.md new file mode 100644 index 0000000..606402d --- /dev/null +++ b/bce-sdk-go/doc/SCS.md @@ -0,0 +1,1537 @@ +# SCS服务 + +# 概述 + +本文档主要介绍SCS GO SDK的使用。在使用本文档前,您需要先了解SCS的一些基本知识。若您还不了解SCS,可以参考[产品描述](https://cloud.baidu.com/doc/SCS/s/Rjxbm160f)和[入门指南](https://cloud.baidu.com/doc/SCS/s/6jxyfy2ly)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[SCS访问域名](https://cloud.baidu.com/doc/SCS/s/fjwvxtrd9)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云SCS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问SCS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建SCS Client + +SCS Client是SCS服务的客户端,为开发者与SCS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建SCS Client + +通过AK/SK方式访问SCS,用户可以参考如下代码新建一个SCS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/scs" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个SCSClient + scsClient, err := scs.NewClient(ACCESS_KEY_ID, SECRET_ACCESS_KEY, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/SCS/s/ojwvynrqn)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为SCS的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`redis.bj.baidubce.com`。 + +### 使用STS创建SCS Client + +**申请STS token** + +SCS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问SCS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7)。 + +**用STS token新建SCS Client** + +申请好STS后,可将STS Token配置到SCS Client中,从而实现通过STS Token创建SCS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建SCS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/scs" //导入SCS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建SCS服务的Client对象,Endpoint使用默认值 + scsClient, err := scs.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create scs client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + scsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置SCS Client时,无论对应SCS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问SCS + +SCS支持HTTPS传输协议,您可以通过在创建SCS Client对象时指定的Endpoint中指明HTTPS的方式,在SCS GO SDK中使用HTTPS访问SCS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/scs" + +ENDPOINT := "https://redis.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +scsClient, _ := scs.NewClient(AK, SK, ENDPOINT) +``` + +## 配置SCS Client + +如果用户需要配置SCS Client的一些细节的参数,可以在创建SCS Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问SCS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/scs" + +//创建SCS Client对象 +AK, SK := , +ENDPOINT := "redis.bj.baidubce.com" +client, _ := scs.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/scs" + +AK, SK := , +ENDPOINT := "redis.bj.baidubce.com" +client, _ := scs.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/scs" + +AK, SK := , +ENDPOINT := "redis.bj.baidubce.com" +client, _ := scs.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问SCS时,创建的SCS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建SCS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# 主要接口 + +云数据库 SCS(Simple Cache Service)提供稳定、高效以及高可扩展性的分布式缓存服务。云数据库 SCS 兼容 Redis/Memcached 协议,基于 Redis 提供标准版和集群版的架构模式,并支持自定义副本数量,为您提供多样化的数据结构支持。 + +## 实例管理 + +### 创建实例 + +使用以下代码可以创建SCS实例,用于创建一个或多个redis实例 +```go +args := &scs.CreateInstanceArgs{ + // 选择付款方式,可以选择预付费或后付费 + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + // 购买个数,最大不超过10,默认1 + PurchaseCount: 1, + // 实例名 + // 要求:1)支持大小写字母、数字以及-_ /.等特殊字符,必须以字母开头;2)长度限制为1-64; + InstanceName: "sdk-scs", + // 端口号 1025 **提示:** +> 1. 创建SCS请求是一个异步请求,返回200表明订单生成,后续可以通过实例id查询实例创建进度。 +> 2. 本接口用于创建一个或多个同配置SCS实例。 +> 3. 创建实例需要实名认证,没有通过实名认证的可以前往百度开放云官网控制台中的安全认证下的实名认证中进行认证。 +> 4. 创建计费方式为后付费的实例需要账户现金余额+通用代金券大于100;预付费方式的实例则需要账户现金余额大于等于实例费用。 +> 5. 支支持批量创建,且如果创建过程中有一个实例创建失败,所有实例将全部回滚。 +> 6. 创建请求详细使用请参考SCS API 文档[创建实例](https://cloud.baidu.com/doc/SCS/s/hk0qhwxom) +> 7. 创建SCS需要使用指定规格, 详细请参考SCS API 文档[实例规格](https://cloud.baidu.com/doc/SCS/s/1jwvxtsh0#%E5%AE%9E%E4%BE%8B%E8%A7%84%E6%A0%BC) + + +### 查询实例列表 + +以下代码可以查询SCS实例列表 +```go +args := &scs.ListInstancesArgs{} + +result, err := client.ListInstances(args) +if err != nil { + fmt.Println("list instance failed:", err) +} else { + fmt.Println("list instance success: ", result) +} +``` + +### 查询指定实例详情 + +使用以下代码可以查询指定SCS虚机的详细信息 +```go +result, err := client.GetInstanceDetail(instanceId) +if err != nil { + fmt.Println("get instance detail failed:", err) +} else + fmt.Println("get instance detail success ", result) +} +``` +### 创建实例价格查询 + +使用以下代码可以查询指定SCS虚机的详细信息 +```go +args := &scs.CreatePriceArgs{ + Engine: 2, + Period: 1, + ChargeType: "prepay", + NodeType: "cache.n1.small", + ReplicationNum: 2, + ClusterType: "cluster", +} +result, err := client.GetCreatePrice(args) +if err != nil { + fmt.Println("get instance price failed:", err) +} else + fmt.Println("get instance price success ", result) +} +``` +### 变配实例价格查询 + +使用以下代码可以查询指定SCS虚机的详细信息 +```go +args := &scs.ResizePriceArgs{ + ChangeType: "nodeModify", + ShardNum: 2, + ReplicationNum: 1, + NodeType: "cache.n1.small", + Period: 1, +} +result, err := client.GetResizePrice(args) +if err != nil { + fmt.Println("get instance price failed:", err) +} else + fmt.Println("get instance price success ", result) +} +``` +### 修改实例名称 + +如下代码可以修改实例名称 +```go +args := &scs.UpdateInstanceNameArgs{ + InstanceName: "newInstanceName", +} +err := client.UpdateInstanceName(instanceId, args) +if err != nil { + fmt.Println("update instance name failed:", err) +} else { + fmt.Println("update instance name success") +} +``` + +> **提示:** +> +> - 只有实例Running状态时可以修改实例名称 + +### 释放实例 + +如下代码可以释放实例,实例将自动进入回收站,保留7天后删除 +```go +err := client.DeleteInstance(instanceId) +if err != nil { + fmt.Println("delete instance failed:", err) +} else { + fmt.Println("delete instance success") +} +``` +> **提示:** +> - 释放单个SCS实例,释放后实例将进入回收站,保留7天后自动删除所有物理资源。 +> - 可以从回收站内恢复实例或者彻底删除实例,预付费实例需要通过续费实例接口进行恢复。 + +## 重启实例 + +使用以下代码可以重启实例。支持用户决定是否延迟到维护窗口内重启。 + +```go +// 立即重启 +args := &scs.RestartInstanceArgs{ + // 需要延迟到维护窗口重启时需传入true + IsDefer: false, +} +err := SCS_CLIENT.RestartInstance(instanceId, args) +if err != nil { + fmt.Println("restart instance failed:", err) +} else { + fmt.Println("restart instance success") +} +``` + +## 续费实例 +使用以下代码可以对已有的预付费实例进行续费,如果实例在回收站中,续费后会从回收站中恢复。 +```go + +// 要恢复的实例Id列表 +instanceIds := []string{ + instancId, +} +args := &scs.RenewInstanceArgs{ + // 实例Id列表 + InstanceIds: instanceIds, + // 续费周期,单位为月 + Duration: 1, +} +result, err := client.RenewInstances(args) +if err != nil { + fmt.Printf("renew instances error: %+v\n", err) + return +} +// 异步任务,返回订单Id +fmt.Println("renew instances success. orderId:" + result.OrderId) +``` + +## 获取回收站中的实例列表 +使用以下代码可以获取回收站中的实例列表。 +```go + +// marker分页参数 +marker := &scs.Marker{MaxKeys: 10} +instances, err := client.ListRecycleInstances(marker) +if err != nil { + fmt.Printf("list recycler instances error: %+v\n", err) + return +} +for _, instance := range instances.Result { + fmt.Println("instanceId: ", instance.InstanceID) + fmt.Println("instanceName: ", instance.InstanceName) + fmt.Println("engine: ", instance.Engine) + fmt.Println("engineVersion: ", instance.EngineVersion) + fmt.Println("instanceStatus: ", instance.InstanceStatus) + // 进入回收站后isolatedStatus为Isolated,表示实例已隔离 + fmt.Println("isolatedStatus: ", instance.IsolatedStatus) + fmt.Println("paymentTiming: ", instance.PaymentTiming) + fmt.Println("clusterType: ", instance.ClusterType) + fmt.Println("domain: ", instance.Domain) + fmt.Println("port: ", instance.Port) + fmt.Println("vnetIP: ", instance.VnetIP) + fmt.Println("instanceCreateTime: ", instance.InstanceCreateTime) + fmt.Println("usedCapacity: ", instance.UsedCapacity) + fmt.Println("zoneNames: ", instance.ZoneNames) + fmt.Println("tags: ", instance.Tags) +} +``` + +## 从回收站中批量恢复实例 +使用以下代码可以从回收站中批量恢复实例,批量恢复仅支持后付费实例,回收站中的预付费实例请通过实例续费接口恢复。 +```go + +// 要恢复的实例Id列表 +instanceIds := []string{ + instanceId_1, + instanceId_2, +} +err := client.RecoverRecyclerInstances(instanceIds) +if err != nil { + fmt.Printf("recover recycler instances error: %+v\n", err) + return +} +fmt.Println("recover recycler instances success.") +``` + +## 从回收站中批量删除实例 +使用以下代码可以从回收站中批量删除实例,实例将被彻底删除。 +```go + +// 要删除的实例Id列表 +instanceIds := []string{ + instanceId_1, + instanceId_2, +} +err := client.DeleteRecyclerInstances(instanceIds) +if err != nil { + fmt.Printf("delete recycler instances error: %+v\n", err) + return +} +fmt.Println("delete recycler instances success.") +``` + +### 添加节点 + +```go +args := &scs.ReplicationArgs{ + ResizeType: "add", // add 添加从节点 add_readonly 添加只读节点 + ReplicationInfo: []Replication{ + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 1}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + }, + ClientToken: getClientToken(), +} +result, err := client.AddReplication(instanceId, args) + +if err != nil { + fmt.Println("resize instance failed:", err) +} else { + fmt.Println("resize instance success") +``` + +### 删除节点 + +```go +args := &scs.ReplicationArgs{ + ResizeType: "delete", // delete 删除节点 delete_readonly 删除只读节点 + ReplicationInfo: []Replication{ + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 1}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + }, + ClientToken: getClientToken(), +} +result, err := client.DeleteReplication(instanceId, args) + +if err != nil { + fmt.Println("resize instance failed:", err) +} else { + fmt.Println("resize instance success") +``` + +### 变更配置 + +```go +args := &scs.ResizeInstanceArgs{ + NodeType:"cache.n1.small", + ShardNum:2, + // 需要延迟到维护窗口变配时需传入true + IsDefer: false, +} +err := client.ResizeInstance(instanceId, args) +if err != nil { + fmt.Println("resize instance failed:", err) +} else { + fmt.Println("resize instance success") +``` + +> **提示:** +> - 实例计费方式为预付费时,不能进行缩容操作 +> - 实例计费方式为后付费时,可弹性扩缩容 +> - 只有实例正常运行状态时才可以进行扩缩容操作,变更接口为异步变更,可通过查询指定实例详情接口查询实例状态 + +### 查询实例套餐规格 + +如下代码可以查询当前可以创建的实例的套餐的规格 +```go +result, err := client.GetNodeTypeList() +if err != nil { + fmt.Println("list node type failed: ", err) +} else { + fmt.Println("list node type success: ", result) +} +``` + +### 获取子网列表 + +如下代码可以获取子网列表 + +```go +args := &scs.ListSubnetsArgs{} +_, err := client.ListSubnets(args) +if err != nil { + fmt.Println("get subnet list failed:", err) +} else { + fmt.Println("get subnet list success") +} +``` + +> **提示:** +> +> - 请求参数 vpcId 和 zoneName 不是必须的 + +### 修改实例域名 + +如下代码可以修改实例域名 + +```go +args := &scs.UpdateInstanceDomainNameArgs{ + Domain: "newDomainName", +} +err := client.UpdateInstanceDomainName(instanceId, args) +if err != nil { + fmt.Println("update instance domain name failed:", err) +} else { + fmt.Println("update instance domain name success") +} +``` + +> **提示:** +> +> - 只有实例Running状态时可以修改实例域名 + +### 获取可用区列表 + +使用以下代码可以获取可用区列表 + +```go +result, err := client.GetZoneList() +if err != nil { + fmt.Println("get zone list failed:", err) +} else + fmt.Println("get zone list success ", result) +} +``` + +### 修改访问密码 + +如下代码可以修改访问密码 + +```go +args := &scs.ModifyPasswordArgs{ + Password: "newPassword", +} +result, err := client.ModifyPassword(instanceId, args) +if err != nil { + fmt.Println("modify password failed:", err) +} else + fmt.Println("modify password success ", result) +} +``` + +> **提示:** +> +> - 密码长度8~16位,至少包含字母、数字和特殊字符中两种。允许的特殊字符包括 $^*()_+-= + +### 变更域名 + +如下代码可以变更域名 + +```go +args := &scs.RenameDomainArgs{ + Domain: "newDomain", + ClientToken: getClientToken(), +} +err := client.RenameDomain(instanceId, args) +if err != nil { + fmt.Println("rename domain failed:", err) +} +``` +### 域名交换 + +如下代码可以域名交换 + +```go +args := &scs.SwapDomainArgs{ + SourceInstanceId: SCS_TEST_ID, + TargetInstanceId: "scs-bj-xeelkkdsx", + ClientToken: getClientToken(), +} +err := client.SwapDomain(instanceId, args) +if err != nil { + fmt.Println("swap domain failed:", err) +} +``` +### 清空实例 + +如下代码可以清空实例 + +```go +args := &scs.FlushInstanceArgs{ + Password: "Password", +} +result, err := client.FlushInstance(instanceId, args) +if err != nil { + fmt.Println("flush instance failed:", err) +} else + fmt.Println("flush instance success ", result) +} +``` + +> **提示:** +> +> - 对redis实例,清空后表现为数据占用内存下降,数据被清空;对memcache实例,清空后表现为占用内存不会下降,但是数据被清空 +> - 如果没有设置密码,传递空字符串 + +### 绑定标签 + +如下代码可以绑定标签 + +```go +args := &scs.BindingTagArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, +} +result, err := client.BindingTag(instanceId, args) +if err != nil { + fmt.Println("bind tags failed:", err) +} else + fmt.Println("bind tags success ", result) +} +``` + +### 解绑标签 + +如下代码可以解绑标签 + +```go +args := &scs.BindingTagArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, +} +result, err := client.UnBindingTag(instanceId, args) +if err != nil { + fmt.Println("unbind tags failed:", err) +} else + fmt.Println("unbind tags success ", result) +} +``` + +> **提示:** +> +> - 解绑实例上定义的标签 +> - 可以同时解绑多个标签 + +### 设置集群为热活主地域 + +如下代码可以设置集群为热活主地域 + +```go +err := client.SetAsMaster(instanceId) +if err != nil { + fmt.Println("set as master failed:", err) +} else + fmt.Println("set as master success ", result) +} +``` +### 设置集群为热活从地域 + +如下代码可以设置集群为热活从地域 + +```go +args := &scs.SetAsSlaveArgs{ + MasterDomain: "masterDomain", + MasterPort: 6379, +} +err := client.SetAsSlave(instanceId, args) +if err != nil { + fmt.Println("set as slave failed:", err) +} else + fmt.Println("set as slave success ", result) +} +``` +### 查询IP白名单 + +如下代码可以查询允许访问实例的IP白名单 + +```go +result, err := client.GetSecurityIp(instanceId) +if err != nil { + fmt.Println("get security IP failed:", err) +} else + fmt.Println("get security IP success ", result) +} +``` + +> **提示:** +> +> - 返回参数 IP白名单列表, 包括常规地址: 如192.168.0.1,CIDR地址: 如192.168.1.0/24,0.0.0.0/0代表允许所有地址 + +### 增加IP白名单 + +如下代码可以增加访问实例的IP白名单 + +```go +args := &scs.SecurityIpArgs{ + SecurityIps: []string{ + "192.0.0.1", + }, +} +result, err := client.AddSecurityIp(instanceId, args) +if err != nil { + fmt.Println("add security IP failed:", err) +} else + fmt.Println("add security IP success ", result) +} +``` + +### 删除IP白名单 + +如下代码可以删除访问实例的IP白名单 + +```go +args := &scs.SecurityIpArgs{ + SecurityIps: []string{ + "192.0.0.1", + }, +} +result, err := client.DeleteSecurityIp(instanceId, args) +if err != nil { + fmt.Println("delete security IP failed:", err) +} else + fmt.Println("delete security IP success ", result) +} +``` + +## 获取VPC下的安全组 +使用以下代码可以获取指定VPC下的安全组列表。 +```go + +// vpcId := "vpc-j1vaxw1cx2mw" +securityGroups, err := client.ListSecurityGroupByVpcId(vpcId) +if err != nil { + fmt.Printf("list security group by vpcId error: %+v\n", err) + return +} +for _, group := range securityGroups.Groups { + fmt.Println("securityGroup id: ", group.SecurityGroupID) + fmt.Println("name: ", group.Name) + fmt.Println("description: ", group.Description) + fmt.Println("associateNum: ", group.AssociateNum) + fmt.Println("createdTime: ", group.CreatedTime) + fmt.Println("version: ", group.Version) + fmt.Println("defaultSecurityGroup: ", group.DefaultSecurityGroup) + fmt.Println("vpc name: ", group.VpcName) + fmt.Println("vpc id: ", group.VpcShortID) + fmt.Println("tenantId: ", group.TenantID) +} +fmt.Println("list security group by vpcId success.") +``` + +## 获取实例已绑定安全组 +使用以下代码可以获取指定实例已绑定的安全组列表。 +```go + +// instanceId := "scs-m1h4mma5" +result, err := client.ListSecurityGroupByInstanceId(instanceId) +if err != nil { + fmt.Printf("list security group by instanceId error: %+v\n", err) + return +} +for _, group := range result.Groups { + fmt.Println("securityGroupId: ", group.SecurityGroupID) + fmt.Println("securityGroupName: ", group.SecurityGroupName) + fmt.Println("securityGroupRemark: ", group.SecurityGroupRemark) + fmt.Println("projectId: ", group.ProjectID) + fmt.Println("vpcId: ", group.VpcID) + fmt.Println("vpcName: ", group.VpcName) + fmt.Println("inbound: ", group.Inbound) + fmt.Println("outbound: ", group.Outbound) +} +fmt.Println("list security group by instanceId success.") +``` + +## 绑定安全组 +使用以下代码可以批量将指定的安全组绑定到实例上。 +```go + +// instanceIds := []string{ +// "scs-mjafcdu0", +// } +// securityGroupIds := []string{ +// "g-iutg5rtcydsk", +// } +args := &scs.SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, +} + +err := client.BindSecurityGroups(args) +if err != nil { + fmt.Printf("bind security groups to instances error: %+v\n", err) + return +} +fmt.Println("bind security groups to instances success.") +``` +> 注意: +> - 实例状态必须为Available。 +> - 实例ID最多可以传入10个。 +> - 安全组ID最多可以传入10个。 +> - 每个实例最多可以绑定10个安全组。 + +## 解绑安全组 +使用以下代码可以从实例上批量解绑指定的安全组。 +```go +// securityGroupIds := []string{ +// "g-iutg5rtcydsk", +// } +args := &scs.UnbindSecurityGroupArgs{ + InstanceId: "scs-su-bbjhgxyqyddd", + SecurityGroupIds: securityGroupIds, +} +err := client.UnBindSecurityGroups(args) +if err != nil { + fmt.Printf("unbind security groups to instances error: %+v\n", err) + return +} +fmt.Println("unbind security groups to instances success.") +``` +> 注意: +> - 实例状态必须为Available。 +> - 当前版本实例ID最多可以传入1个。 +> - 安全组ID最多可以传入10个。 + +### 获取参数列表 + +使用以下代码可以获取Redis实例的配置参数和运行参数 + +```go +result, err := client.GetParameters(instanceId) +if err != nil { + fmt.Println("get parameter list failed:", err) +} else + fmt.Println("get parameter list success ", result) +} +``` + +### 修改参数 + +如下代码可以修改redis实例参数值 + +```go +args := &scs.ModifyParametersArgs{ + Parameter: InstanceParam{ + Name: "parameter name", + Value: "new value", + }, +} +result, err := client.ModifyParameters(instanceId, args) +if err != nil { + fmt.Println("modify parameters failed:", err) +} else + fmt.Println("modify parameters success ", result) +} +``` + +### 查看备份列表 + +使用以下代码可以查询某个实例备份列表 + +```go +result, err := client.GetBackupList(instanceId) +if err != nil { + fmt.Println("get backup list failed:", err) +} else + fmt.Println("get backup list success ", result) +} +``` +### 获取备份信息 + +使用以下代码可以获取某个实例备份信息 + +```go +result, err := client.GetBackupDetail(instanceId, backupId) +if err != nil { + fmt.Println("get backup detail failed:", err) +} else + fmt.Println("get backup detail success ", result) +} +``` +### 修改备份策略 + +如下代码可以修改redis实例自动备份策略 + +```go +args := &scs.ModifyBackupPolicyArgs{ + BackupDays: "Sun,Mon,Tue,Wed,Thu,Fri,Sta", + BackupTime: "01:05:00", + ExpireDay: 7, +} +result, err := client.ModifyBackupPolicy(instanceId, args) +if err != nil { + fmt.Println("modify backup policy failed:", err) +} else + fmt.Println("modify backup policy success ", result) +} +``` + +> **提示:** +> +> - BackupDays: 标识一周中哪几天进行备份备份周期:Mon(周一)Tue(周二)Wed(周三)Thu(周四)Fri(周五)Sat(周六)Sun(周日)逗号分隔,取值如:Sun,Wed,Thu,Fri,Sta +> - BackupTime: 标识一天中何时进行备份,UTC时间(+8为北京时间)取值如:01:05:00 +> - ExpireDay: 备份文件过期时间,取值如:3 + +# 日志管理 + +## 日志列表 +使用以下代码可以获取一个实例下的运行日志或者慢日志列表。 +```go +// import "time" + +// 一天前 +date := time.Now(). + AddDate(0, 0, -1). + Format("2006-01-02 03:04:05") +fmt.Println(date) +args := &scs.ListLogArgs{ + // 运行日志 runlog 慢日志 slowlog + FileType: "runlog", + // 开始时间格式 "yyyy-MM-dd hh:mm:ss" + StartTime: date, + // 结束时间,可选,默认返回开始时间+24小时内的日志 + // EndTime: date, +} +listLogResult, err := client.ListLogByInstanceId(instanceId, args) +if err != nil { + fmt.Printf("list logs of instance error: %+v\n", err) + return +} +fmt.Println("list logs of instance success.") +for _, shardLog := range listLogResult.LogList { + fmt.Println("shard id: ", shardLog.ShardID) + fmt.Println("logs size: ", shardLog.TotalNum) + for _, log := range shardLog.LogItem { + fmt.Println("log id: ", log.LogID) + fmt.Println("size: ", log.LogSizeInBytes) + fmt.Println("start time: ", log.LogStartTime) + fmt.Println("end time: ", log.LogEndTime) + fmt.Println("download url: ", log.DownloadURL) + fmt.Println("download url expires: ", log.DownloadExpires) + } +} +``` + +## 日志详情 +使用以下代码可以查询日志的详细信息,包括该日志文件有效的下载链接。 +```go +args := &GetLogArgs{ + // 下载链接有效时间,单位为秒,可选,默认为1800秒 + ValidSeconds: 60, +} +logId := "scs-bj-mktaypucksot_8742_slowlog_202104160330" +log, err := client.GetLogById(instanceId, logId, args) +if err != nil { + fmt.Printf("get log detail of instance error: %+v\n", err) + return +} +fmt.Println("get log detail success.") +fmt.Println("id: ", log.LogID) +// 日志文件下载链接 +fmt.Println("download url: ", log.DownloadURL) +// 下载链接截止该时间有效 +fmt.Println("download url expires: ", log.DownloadExpires) +``` + +# 维护窗口 +## 查询实例的维护时间窗口 +使用以下代码可以查询实例的维护时间窗口。 +```go +resp, err := client.GetMaintainTime(instanceId) +if err != nil { + fmt.Printf("get maintainTime of instance error: %+v\n", err) + return +} +fmt.Println("get maintainTime success.") +fmt.Println("start time: ", resp.MaintainTime.StartTime) +fmt.Println("dutation: ", resp.MaintainTime.Duration) +fmt.Println("period: ", resp.MaintainTime.Period) +``` + +> 注意: +> +> - startTime 为北京时间24小时制,例如14:00。 + +## 修改实例的维护时间窗口 +使用以下代码可以修改实例的维护时间窗口。 +```go +newMaintainTime := &scs.MaintainTime{ + StartTime: "16:00", + Duration: 1, + Period: []int{1, 2, 3}, +} +err := client.ModifyMaintainTime(instanceId, newMaintainTime) +if err != nil { + fmt.Printf("modify maintainTime of instance error: %+v\n", err) + return +} +fmt.Println("modify maintainTime success.") +``` + +# 热活实例组 + +## 前置检查 +热活实例组前置检查,检查数据配置及网络联通性,不通过检查不能创建。 +```go +args := &scs.GroupPreCheckArgs{ + Leader: GroupLeader{ + LeaderRegion: "bj", + LeaderId: SCS_TEST_ID, + }, + Followers: []GroupFollower{ + { + FollowerId: "scs-bdbl-dzkqigawuhzy", + FollowerRegion: "bd", + }, + }, +} +result, err := client.GroupPreCheck(args) +if err != nil { + fmt.Printf("group pre check error: %+v\n", err) + return +} +fmt.Println("group pre check success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` +## 创建热活实例组 +创建热活实例组 +```go +args := &scs.CreateGroupArgs{ + Leader: CreateGroupLeader{ + GroupName: "test_create", + LeaderRegion: "bj", + LeaderId: SCS_TEST_ID, + }, +} +result, err := client.CreateGroup(args) +if err != nil { + fmt.Printf("create group error: %+v\n", err) + return +} +fmt.Println("create group success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` +## 获取热活实例组列表 +获取热活实例组列表 +```go +args := &scs.GetGroupListArgs{ + PageNo: 1, + PageSize: 10 +} +result, err := client.GetGroupList(args) +if err != nil { + fmt.Printf("get group list error: %+v\n", err) + return +} +fmt.Println("get group list success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` +## 获取热活实例组详情 +获取热活实例组详情 +```go +result, err := client.GetGroupDetail(groupId) +if err != nil { + fmt.Printf("get group detail error: %+v\n", err) + return +} +fmt.Println("get group detail success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` + +## 释放热活实例组 +释放热活实例组 +```go +err := client.DeleteGroup(groupId) +if err != nil { + fmt.Printf("delete group error: %+v\n", err) + return +} +fmt.Println("delete group success.") +``` + +## 热活实例组添加从集群 +```go +args := &scs.FollowerInfo{ + FollowerId: "scs-bdbl-dzkqigawuhzy", + FollowerRegion: "bd", + SyncMaster: "sync", +} +err := client.GroupAddFollower(groupId, args) +if err != nil { + fmt.Printf("group add follower error: %+v\n", err) + return +} +fmt.Println("group add follower success.") +``` +## 热活实例组移除从集群 +```go + +err := client.GroupRemoveFollower(groupId, instanceId) +if err != nil { + fmt.Printf("group remove follower error: %+v\n", err) + return +} +fmt.Println("group remove follower success.") +``` +## 修改热活实例组名称 +```go +args := &scs.roupNameArgs{ + GroupName: "test_group", +} +err := client.UpdateGroupName(groupId, args) +if err != nil { + fmt.Printf("update group name error: %+v\n", err) + return +} +fmt.Println("update group name success.") +``` +## 变更主角色 +```go +err := client.SetAsLeader(groupId, instanceId) +if err != nil { + fmt.Printf("set as leader error: %+v\n", err) + return +} +fmt.Println("set as leader success.") +``` +## 实例组禁写修改 +```go +args := &scs.ForbidWriteArgs{ + ForbidWriteFlag: true, +} +err := client.GroupForbidWrite(groupId, args) +if err != nil { + fmt.Printf("update forbid write error: %+v\n", err) + return +} +fmt.Println("update forbid write success.") +``` +## 设置流控规则 +```go +args := &scs.GroupSetQpsArgs{ + ClusterShowId: "scs-bj-bftgjzjxbmex", + QpsWrite: 10, + QpsRead: 20, +} +err := client.GroupSetQps(groupId, args) +if err != nil { + fmt.Printf("group set qps error: %+v\n", err) + return +} +fmt.Println("group set qps success.") +``` +## 获取从角色同步状态 +```go +result, err := client.GroupSyncStatus(groupId) +if err != nil { + fmt.Printf("group sync status error: %+v\n", err) + return +} +fmt.Println("group sync status success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` +## 获取IP白名单列表 +```go +result, err := client.GroupWhiteList(groupId) +if err != nil { + fmt.Printf("group white list error: %+v\n", err) + return +} +fmt.Println("group white list success.") +data, _ := json.Marshal(result) +fmt.Println(string(data)) +``` +## 添加IP白名单 +```go +args := &scs.GroupWhiteList{ + WhiteLists: []string{"127.0.0.1"}, +} +err := client.GroupWhiteListAdd(groupId, args) +if err != nil { + fmt.Printf("group white add error: %+v\n", err) + return +} +fmt.Println("group white add success.") +``` +## 删除IP白名单 +```go +args := &scs.GroupWhiteList{ + WhiteLists: []string{"127.0.0.1"}, +} +err := client.GroupWhiteListDelete(groupId, args) +if err != nil { + fmt.Printf("group white delete error: %+v\n", err) + return +} +fmt.Println("group white delete success.") +``` + +## 设置从角色脏读 +```go +args := &scs.taleReadableArgs{ + FollowerId: SCS_TEST_ID, + StaleReadable: true, +} +err := client.GroupStaleReadable(groupId, args) +if err != nil { + fmt.Printf("groupstale readable error: %+v\n", err) + return +} +fmt.Println("groupstale readable success.") +``` + +# 参数模板管理 +## 创建参数模板 +```go + +args := &scs.CreateTemplateArgs{ + EngineVersion: "5.0", + TemplateType: 1, + ClusterType: "master_slave", + Engine: "redis", + Name: "test_template", + Comment: "test template", + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, +} +result, err := client.CreateParamsTemplate(args) +if err != nil { + fmt.Printf("create params template error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println("create params template success.") +``` + +## 获取参数模版列表 +```go + +args := &Marker{ + Marder: "-1", + MaxKeys: 1000 +} +result, err := client.GetParamsTemplateList(args) +if err != nil { + fmt.Printf("get params template error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println("get params template success.") +``` +## 获取参数模版详情 +```go +result, err := client.GetParamsTemplateDetail("scs-tmpl-vxslemqppzuz") +if err != nil { + fmt.Printf("get params template detail error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println("get params template detail success.") +``` +## 删除参数模板 +```go +err := client.DeleteParamsTemplate("scs-tmpl-vxslemqppzuz") +if err != nil { + fmt.Printf("delete params template error: %+v\n", err) + return +} +fmt.Println("delete params template success.") +``` + + +## 修改参数模板名称 +```go +args := &scs.RenameTemplateArgs{ + Name: "scs-test-template", +} +err := client.RenameParamsTemplate("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("rename params template error: %+v\n", err) + return +} +fmt.Println("rename params template success.") +``` + +## 应用参数模板 +```go +args := &ApplyTemplateArgs{ + RebootType: 0, + Extra: "0", + CacheClusterShowIdItem: []CacheClusterShowId{ + { + CacheClusterShowId: SCS_TEST_ID, + Region: "bj", + }, + }, + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, +} +err := client.ApplyParamsTemplate("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("apply params template error: %+v\n", err) + return +} +fmt.Println("apply params template success.") +``` +## 参数模版添加参数 +```go +args := &scs.AddParamsArgs{ + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, +} +err := client.TemplateAddParams("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("add params template error: %+v\n", err) + return +} +fmt.Println("add params template success.") +``` +## 参数模版修改参数 +```go +args := &scs.ModifyParamsArgs{ + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, +} +err := client.TemplateModifyParams("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("modify params template error: %+v\n", err) + return +} +fmt.Println("modify params template success.") +``` +## 参数模版删除参数 +```go +args := &scs.DeleteParamsArgs{ + Parameters: []string{"appendonly"}, +} +err := client.TemplateDeleteParams("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("template delete params error: %+v\n", err) + return +} +fmt.Println("template delete params success.") +``` + +## 获取系统参数模板 +```go +args := &scs.GetSystemTemplateArgs{ + Engine: "redis", + EngineVersion: "5.0", + ClusterType: "master_slave", +} +result, err := client.GetSystemTemplate(args) +if err != nil { + fmt.Printf("get system template error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println("get system template success.") +``` +## 获取应用参数模版记录 +```go +args := &scs.Marker{ + Marker: "-1", + MaxKeys: 100, +} +result, err := client.GetApplyRecords("scs-tmpl-kctbndsfdhya", args) +if err != nil { + fmt.Printf("get apply record error: %+v\n", err) + return +} +data, _ := json.Marshal(result) +fmt.Println(string(data)) +fmt.Println("get apply record success.") +``` +# 错误处理 + +GO语言以error类型标识错误,SCS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | SCS服务返回的错误 + +用户使用SDK调用SCS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// scsClient 为已创建的SCS Client对象 +instanceDetail, err := scsClient.GetInstanceDetail(instanceId) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("get instance detail success: ", instanceDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向SCS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError;当上传文件时发生IO异常时,也会抛出BceClientError。 + +## 服务端异常 + +当SCS服务端出现异常时,SCS服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[SCS错误返回](https://cloud.baidu.com/doc/SCS/s/Yjwvxtsti) + diff --git a/bce-sdk-go/doc/SMS.md b/bce-sdk-go/doc/SMS.md new file mode 100644 index 0000000..096993b --- /dev/null +++ b/bce-sdk-go/doc/SMS.md @@ -0,0 +1,508 @@ +# SMS简单消息服务 + +# 概述 +本文档主要介绍普通型SMS GO SDK的使用。在使用本文档前,您需要先了解普通型SMS的一些基本知识。若您还不了解普通型SMS,可以参考[产品描述](https://cloud.baidu.com/doc/SMS/s/0jwvxrjyt)和[入门指南](https://cloud.baidu.com/doc/SMS/s/sk4m6mxke)。 + +# 初始化 +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[SMS访问域名](https://cloud.baidu.com/doc/SMS/s/pjwvxrw6w)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +## 获取密钥 + +要使用百度云SMS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问SMS做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建SMS Client + +SMS Client是SMS服务的客户端,为开发者与SMS服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建SMS Client + +通过AK/SK方式访问SMS,用户可以参考如下代码新建一个SMS Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/sms" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个SmsClient + smsClient, err := sms.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [管理ACCESSKEY](https://cloud.baidu.com/doc/SMS/GettingStarted.html#.E7.AE.A1.E7.90.86ACCESSKEY) 》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为SMS的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://smsv3.bj.baidubce.com`。 + +### 使用STS创建SMS Client + +**申请STS token** + +SMS可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问SMS,用户需要先通过STS的client申请一个认证字符串,申请方式可参见[百度云STS使用介绍](https://cloud.baidu.com/doc/IAM/s/gjwvyc7n7) 。 + +**用STS token新建SMS Client** + +申请好STS后,可将STS Token配置到SMS Client中,从而实现通过STS Token创建SMS Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建SMS Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/sms" //导入SMS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建SMS服务的Client对象,Endpoint使用默认值 + smsClient, err := sms.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "") + if err != nil { + fmt.Println("create sms client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + smsClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置SMS Client时,无论对应SMS服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com 。上述代码中创建STS对象时使用此默认值。 + +## 配置HTTPS协议访问SMS + +SMS支持HTTPS传输协议,您可以通过在创建SMS Client对象时指定的Endpoint中指明HTTPS的方式,在SMS GO SDK中使用HTTPS访问SMS服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/sms" + +ENDPOINT := "https://smsv3.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +smsClient, _ := sms.NewClient(AK, SK, ENDPOINT) +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/sms" + +AK, SK := , +ENDPOINT := "smsv3.bj.baidubce.com" +client, _ := sms.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/sms" + +AK, SK := , +ENDPOINT := "smsv3.bj.baidubce.com" +client, _ := sms.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问SMS时,创建的SMS Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建SMS Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +# 主要接口 +## 发送消息 + +通过以下代码可以发送SMS消息 +```go + contentMap := make(map[string]interface{}) + contentMap["code"] = "123" + contentMap["minute"] = "1" + sendSmsArgs := &api.SendSmsArgs{ + Mobile: "13800138000", + Template: "your template id", + SignatureId: "your signature id", + ContentVar: contentMap, + } + result, err := client.SendSms(sendSmsArgs) + if err != nil { + fmt.Printf("send sms error, %s", err) + return + } + fmt.Printf("send sms success. %s", result) +``` +> **提示:** +> - 详细的参数配置及限制条件,可以参考SMS API 文档[短信下发](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb#%E7%9F%AD%E4%BF%A1%E4%B8%8B%E5%8F%91) + +## 签名 +### 申请签名 +通过以下代码,可以申请一个SMS签名 +```go + // Open file on disk. + f, _ := os.Open("/dir1/dir2/your_sign_pic.png") + // Read entire JPG into byte slice. + reader := bufio.NewReader(f) + content, _ := ioutil.ReadAll(reader) + // Encode as base64. + encoded := base64.StdEncoding.EncodeToString(content) + result, err := client.CreateSignature(&api.CreateSignatureArgs{ + Content: "Baidu", + ContentType: "Enterprise", + Description: "test", + CountryType: "DOMESTIC", + SignatureFileBase64: encoded, + SignatureFileFormat: "png", + }) + if err != nil { + fmt.Printf("create signature error, %s", err) + return + } + fmt.Printf("create signature success. %s", result) +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[申请签名](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 获取签名详情 +通过以下代码,可以获取一个SMS签名详情 +```go + result, err := client.GetSignature(&api.GetSignatureArgs{ + SignatureId: "your signature id", + }) + if err != nil { + fmt.Printf("get signature error, %s", err) + return + } + fmt.Printf("get signature success. %s", result) +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[获取签名详情](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 变更签名申请 +通过以下代码,可以变更一个SMS签名申请 +```go + // Open file on disk. + f, _ := os.Open("/dir1/dir2/your_sign_pic.png") + // Read entire JPG into byte slice. + reader := bufio.NewReader(f) + content, _ := ioutil.ReadAll(reader) + // Encode as base64. + encoded := base64.StdEncoding.EncodeToString(content) + err := client.ModifySignature(&api.ModifySignatureArgs{ + SignatureId: "your signature id", + Content: "Baidu", + ContentType: "MobileApp", + Description: "this is a test", + CountryType: "INTERNATIONAL", + SignatureFileBase64: encoded, + SignatureFileFormat: "png", + }) + if err != nil { + fmt.Printf("modify signature error, %s", err) + return + } + fmt.Printf("modify signature success.") +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[变更签名申请](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 删除签名 +通过以下代码,可以删除一个SMS签名 +```go + err := client.DeleteSignature( + &api.DeleteSignatureArgs{SignatureId: "your signature id"}) + if err != nil { + fmt.Printf("delete signature error, %s", err) + return + } + fmt.Printf("delete signature success.") +``` + +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[删除签名](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +## 模板 +### 申请模板 +通过以下代码,可以申请一个sms模板 +```go + result, err := client.CreateTemplate(&api.CreateTemplateArgs{ + Name: "my template", + Content: "${content}", + SmsType: "CommonNotice", + CountryType: "DOMESTIC", + Description: "this is a test", + }) + if err != nil { + fmt.Printf("create template error, %s", err) + return + } + fmt.Printf("create template success. %s", result) +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[申请模板](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 获取模板详情 +通过以下代码,可以获取一个sms模板详情 +```go + result, err := client.GetTemplate(&api.GetTemplateArgs{TemplateId: your template id"}) + if err != nil { + fmt.Printf("get template error, %s", err) + return + } + fmt.Printf("get template success. %s", result) +``` + +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[获取模板详情](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 变更模板 +通过以下代码,可以变更一个sms模板申请 +```go + err := client.ModifyTemplate(&api.ModifyTemplateArgs{ + TemplateId: "your template id", + Name: "my template", + Content: "${code}", + SmsType: "CommonVcode", + CountryType: "GLOBAL", + Description: "this is a test", + }) + if err != nil { + fmt.Printf("modify template error, %s", err) + return + } + fmt.Printf("modify template success.") +``` + +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[变更模板](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 删除模板 +通过以下代码,可以删除一个模板 +```go + err := client.DeleteTemplate(&api.DeleteTemplateArgs{TemplateId: "your template id"}) + if err != nil { + fmt.Printf("delete template error, %s", err) + return + } + fmt.Printf("delete template success.") +``` + +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[删除模板](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +## 配额频控 +### 查看配额和频控 +通过以下代码,可以查看配额和频控 +```go + result, err := client.QueryQuotaAndRateLimit() + if err != nil { + fmt.Printf("query quota or rate limit error, %s", err) + return + } + fmt.Printf("query quota or rate limit success. %s", result) +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[查看配额和频控](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +### 变更配额或频控 +通过以下代码,可以变更配额或频控 +```go + err := client.UpdateQuotaAndRateLimit(&api.UpdateQuotaRateArgs{ + QuotaPerDay: 200, + QuotaPerMonth: 200, + RateLimitPerDay: 60, + RateLimitPerHour: 20, + RateLimitPerMinute: 10, + }) + if err != nil { + fmt.Printf("update quota or rate limit template error, %s", err) + return + } + fmt.Printf("update quota or rate limit success") +``` +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[变更配额和频控](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + +## 手机号黑名单 +### 创建手机号黑名单 +通过以下代码,可以创建手机号黑名单 +```go + err := client.CreateMobileBlack(&api.CreateMobileBlackArgs{ + Type: "MerchantBlack", + SmsType: "CommonNotice", + SignatureIdStr: "sddd", + Phone: "12345678901", + }) + if err != nil { + fmt.Printf("CreateMobileBlack error, %s", err) + return + } + fmt.Printf("CreateMobileBlack success") +``` + +### 查询手机号黑名单 +通过以下代码,可以查询手机号黑名单 +```go + err := client.GetMobileBlack(&api.GetMobileBlackArgs{ + SmsType: "CommonNotice", + SignatureIdStr: "sddd", + Phone: "12345678901", + StartTime: "2023-07-18", + EndTime: "2023-07-19", + PageNo: "1", + PageSize: "10", + }) + if err != nil { + fmt.Printf("GetMobileBlack error, %s", err) + return + } + fmt.Printf("GetMobileBlack success") +``` + +### 删除手机号黑名单 +通过以下代码,可以删除手机号黑名单 +```go + err := client.DeleteMobileBlack(&api.DeleteMobileBlackArgs{ + Phones: "12345678901", + }) + if err != nil { + fmt.Printf("DeleteMobileBlack error, %s", err) + return + } + fmt.Printf("DeleteMobileBlack success") +``` + +> **提示:** +> - 详细参数配置及限制条件,可以参考SMS API 文档[手机号黑名单](https://cloud.baidu.com/doc/SMS/s/Yjwvxrwzb) + + + +# 错误处理 + +GO语言以error类型标识错误,SMS支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | SMS服务返回的错误 + +用户使用SDK调用SMS相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +```go +// smsClient 为已创建的SMS Client对象 +smsDetail, err := smsClient.SendSms(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} else { + fmt.Println("send sms success: ", smsDetail) +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向SMS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当SMS服务端出现异常时,SMS服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[短信发送接口响应码](https://cloud.baidu.com/doc/SMS/s/zjwvxry6e) + +# 版本变更记录 + +## v0.9.19 [2020-08-08] + +首次发布: +- 支持短信发送接口 +- 支持签名、模板管理接口 +- 支持配额、频控查看和变更 + +## v0.9.32 [2023-07-20] +- 配额频控查询增加申请信息字段 +- 增加手机号黑名单增、删、查接口 \ No newline at end of file diff --git a/bce-sdk-go/doc/SNIC.md b/bce-sdk-go/doc/SNIC.md new file mode 100644 index 0000000..356a371 --- /dev/null +++ b/bce-sdk-go/doc/SNIC.md @@ -0,0 +1,451 @@ +# SNIC服务 + +# 概述 + +本文档主要介绍SNIC GO SDK的使用。在使用本文档前,您需要先了解SNIC的一些基本知识,并已开通了SNIC服务。若您还不了解SNIC,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/sjwvytvh0 )和[操作指南](https://cloud.baidu.com/doc/VPC/s/6jwvyu1dn) 。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[ENDPOINT服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw )的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/ )。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云SNIC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问SNIC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建SNIC Client + +SNIC Client是SNIC服务的客户端,为开发者与ENDPOINT服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建ENDPOINT Client + +通过AK/SK方式访问ENDPOINT,用户可以参考如下代码新建一个ENDPOINT Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/endpoint" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个SNICClient + snicClient, err := endpoint.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/ )》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建SNIC Client + +**申请STS token** + +SNIC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问SNIC,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建SNIC Client** + +申请好STS后,可将STS Token配置到SNIC Client中,从而实现通过STS Token创建SNIC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建SNIC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/endpoint" //导入SNIC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建SNIC服务的Client对象,Endpoint使用默认值 + snicClient, err := endpoint.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create snic client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + snicClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置SNIC Client时,无论对应SNIC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问SNIC + +SNIC支持HTTPS传输协议,您可以通过在创建SNIC Client对象时指定的Endpoint中指明HTTPS的方式,在SNIC GO SDK中使用HTTPS访问SNIC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +snicClient, _ := endpoint.NewClient(AK, SK, ENDPOINT) +``` + +## 配置SNIC Client + +如果用户需要配置SNIC Client的一些细节的参数,可以在创建SNIC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问SNIC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +//创建SNIC Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := endpoint.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := endpoint.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := endpoint.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问SNIC时,创建的SNIC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + +1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建SNIC Client”小节。 +2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 +3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + +## 获取公共服务 + +使用以下代码获取公共服务 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +result, err := client.GetServices() + if err != nil { + fmt.Printf("list public services error: %+v\n", err) + return + } +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 创建SNIC + +使用以下代码可以申请一个SNIC。 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +args := &endpoint.CreateEndpointArgs{ + VpcId: "vpcId", + Name: "TestSDK-SNIC", + SubnetId: "subnetId", + Service: "service", + Description: "desc", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + ClientToken: getClientToken(), +} +result, err := client.CreateEndpoint(args) +if err != nil { + fmt.Printf("create snic error: %+v\n", err) + return +} + +fmt.Println("create snic success, snic: ", result.Id) +``` + + +## 查询SNIC 列表 + +使用以下代码可以查询SNIC列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +args := &endpoint.ListEndpointArgs{ + VpcId: "vpcId", + SubnetId: "subnetId", + } +res, err := client.ListEndpoints(args) + if err != nil { + fmt.Printf("list snic error: %+v\n", err) + return + } + // 返回标记查询的起始位置 + fmt.Println("snic list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("snic list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("snic list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("snic list maxKeys: ", result.MaxKeys) + // 获取snic的列表信息 + for _, e := range res.Endpoints { + fmt.Println("snic id: ", e.EndpointId) + fmt.Println("snic name: ", e.Name) + fmt.Println("snic ipAddress: ", e.IpAddress) + fmt.Println("snic status: ", e.Status) + fmt.Println("snic service: ", e.Service) + fmt.Println("snic subnetId: ", e.SubnetId) + fmt.Println("snic description: ", e.Description) + fmt.Println("snic createTime: ", e.CreateTime) + fmt.Println("snic vpcId: ", e.VpcId) + fmt.Println("snic productType: ", e.ProductType) + } +``` + +## 查询SNIC详情 + +使用以下代码可以实现查询SNIC的详情信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" +result,err:=endpoint.GetEndpointDetail("endpointId") + + if err != nil { + fmt.Printf("get snic detail error: %+v\n", err) + return + } + fmt.Println("snic id: ", e.EndpointId) + fmt.Println("snic name: ", e.Name) + fmt.Println("snic ipAddress: ", e.IpAddress) + fmt.Println("snic status: ", e.Status) + fmt.Println("snic service: ", e.Service) + fmt.Println("snic subnetId: ", e.SubnetId) + fmt.Println("snic description: ", e.Description) + fmt.Println("snic createTime: ", e.CreateTime) + fmt.Println("snic vpcId: ", e.VpcId) + fmt.Println("snic productType: ", e.ProductType) +} +``` + +## 更新SNIC + +使用以下代码可以实现SNIC的更新。 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" +args := &endpoint.UpdateEndpointArgs{ + ClientToken: getClientToken(), + Name: "snicTest", + Description: "Description", +} + err := client.UpdateEndpoint("endpointId", args) +if err != nil { + fmt.Printf("update snic error: %+v\n", err) + return +} +fmt.Printf("update snic success\n") +``` + + +## 释放SNIC + +使用以下代码可以释放指定的SNIC。 +```go +// import "github.com/baidubce/bce-sdk-go/services/endpoint" + +err := client.DeleteEndpoint("endpointId", clientToken) +if err != nil { + fmt.Printf("delete snic error: %+v\n", err) + return +} + +fmt.Printf("delete snic success\n") +``` + +> 注意: +> - 释放指定SNIC,被释放的SNIC无法找回 + +# 错误处理 + +GO语言以error类型标识错误,SNIC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | SNIC服务返回的错误 + +用户使用SDK调用SNIC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// snicClient 为已创建的SNIC Client对象 +args := &endpoint.ListEndpointArgs{} +result, err := client.ListEndpoints(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向SNIC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当SNIC服务端出现异常时,SNIC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[SNIC错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +SNIC GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +SNIC GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: +1. 日志默认输出级别为`DEBUG` +2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 +3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB +4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the SNIC go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the SNIC go sdk") +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/STS.md b/bce-sdk-go/doc/STS.md new file mode 100644 index 0000000..9c858f7 --- /dev/null +++ b/bce-sdk-go/doc/STS.md @@ -0,0 +1,98 @@ +# STS服务 + +# 概述 + +本文档主要介绍STS服务的使用。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 +若您还不了解STS,可以参考[产品描述](https://cloud.baidu.com/doc/IAM/s/xjwvybxhv)和[操作指南](https://cloud.baidu.com/doc/IAM/s/njwvyc2zd)。 + +# 使用方法 + +## 确认Endpoint + +目前使用STS服务时,STS的Endpoint都统一使用`http://sts.bj.baidubce.com`,为默认值。 + +## 获取密钥 + +要使用百度云STS,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问服务做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 创建Client对象并获取临时token + +STS Client是STS服务的客户端,为开发者与STS服务进行交互提供了获取临时token的方法。示例代码如下: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := "", "" + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + obj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", obj.AccessKeyId) + fmt.Println(" secretAccessKey:", obj.SecretAccessKey) + fmt.Println(" sessionToken:", obj.SessionToken) + fmt.Println(" createTime:", obj.CreateTime) + fmt.Println(" expiration:", obj.Expiration) + fmt.Println(" userId:", obj.UserId) +} +``` + +## 获取关联指定角色的临时身份凭证 + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 + "github.com/baidubce/bce-sdk-go/services/sts/api" +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := "", "" + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + AccountId, RoleName := "", "" + args := &api.AssumeRoleArgs{ + AccountId: AccountId, + RoleName: RoleName, + } + obj, err := client.AssumeRole(args) + if err != nil { + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", obj.AccessKeyId) + fmt.Println(" secretAccessKey:", obj.SecretAccessKey) + fmt.Println(" sessionToken:", obj.SessionToken) + fmt.Println(" createTime:", obj.CreateTime) + fmt.Println(" expiration:", obj.Expiration) + fmt.Println(" userId:", obj.UserId) + fmt.Println(" roleId:", obj.RoleId) +} +``` \ No newline at end of file diff --git a/bce-sdk-go/doc/VPC.md b/bce-sdk-go/doc/VPC.md new file mode 100644 index 0000000..a77d81c --- /dev/null +++ b/bce-sdk-go/doc/VPC.md @@ -0,0 +1,1811 @@ +# VPC服务 + +# 概述 + +本文档主要介绍VPC GO SDK的使用。在使用本文档前,您需要先了解VPC的一些基本知识,并已开通了VPC服务。若您还不了解VPC,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/Vjwvytu2v)和[操作指南](https://cloud.baidu.com/doc/VPC/s/qjwvyu0at)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[VPC访问域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云VPC,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问VPC做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建VPC Client + +VPC Client是VPC服务的客户端,为开发者与VPC服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建VPC Client + +通过AK/SK方式访问VPC,用户可以参考如下代码新建一个VPC Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/vpc" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个VpcClient + vpcClient, err := vpc.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`http://bcc.bj.baidubce.com`。 + +### 使用STS创建VPC Client + +**申请STS token** + +VPC可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问VPC,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建VPC Client** + +申请好STS后,可将STS Token配置到VPC Client中,从而实现通过STS Token创建VPC Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建VPC Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/vpc" //导入VPC服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建VPC服务的Client对象,Endpoint使用默认值 + vpcClient, err := vpc.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create vpc client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + vpcClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置VPC Client时,无论对应VPC服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问VPC + +VPC支持HTTPS传输协议,您可以通过在创建VPC Client对象时指定的Endpoint中指明HTTPS的方式,在VPC GO SDK中使用HTTPS访问VPC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpc" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +vpcClient, _ := vpc.NewClient(AK, SK, ENDPOINT) +``` + +## 配置VPC Client + +如果用户需要配置VPC Client的一些细节的参数,可以在创建VPC Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问VPC服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpc" + +//创建VPC Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpc.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpc" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpc.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpc" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpc.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问VPC时,创建的VPC Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建VPC Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + +# VPC管理 + +私有网络(Virtual private Cloud,VPC) 是一个用户能够自定义的虚拟网络,能够帮助用户构建属于自己的网络环境。通过指定IP地址范围和子网等配置,即可快速创建一个VPC,不同的VPC之间完全隔离,用户可以在VPC内创建和管理BCC实例。 + +## 创建VPC + +通过以下代码可以创建VPC实例: +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.CreateVPCArgs{ + // 设置创建vpc使用的名称 + Name: "test-vpc", + // 设置创建vpc使用的描述信息 + Description: "test-vpc-description", + // 设置创建vpc使用的cidr + Cidr: "102.168.0.0/24", + // 设置创建vpc使用的标签键值对列表 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +if result, err := client.CreateVPC(args); err != nil { + fmt.Println("create vpc failed: ", err) + return +} + +fmt.Println("create vpc success, vpc id: ", result.VPCID) +``` + +> 注意: 对请求参数的内容解释如下 +> - Name: 表示VPC名称,不能取值"default",长度不超过65个字符,可由数字,字符,下划线组成; +> - ClientToken: 表示幂等性Token,是一个长度不超过64位的ASCII字符串,详见[ClientToken幂等性](https://cloud.baidu.com/doc/VPC/s/gjwvyu77i/#%E5%B9%82%E7%AD%89%E6%80%A7) +> - Description: VPC描述,不超过200字符 +> - Cidr: VPC的cidr +> - Tags: 待创建的标签键值对列表 + +## 查询VPC列表 + +使用以下代码查询VPC列表信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ListVPCArgs{ + // 设置每页包含的最大数量,最大数量通常不超过1000,缺省值为1000 + MaxKeys: 2, + // 设置批量获取列表的查询的起始位置,是一个由系统生成的字符串 + Marker: marker, + // 设置是否为默认VPC,可选值:true、false;当不填写此参数时返回所有VPC + IsDefault: "false", +} +result, err := client.ListVPC(listArgs) +if err != nil { + fmt.Println("list vpc error: ", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("vpc list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("vpc list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("vpc list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("vpc list maxKeys: ", result.MaxKeys) +// 获取vpc的具体信息 +for _, v := range result.VPCs { + fmt.Println("vpc id: ", v.VPCID) + fmt.Println("vpc name: ", v.Name) + fmt.Println("vpc cidr: ", v.Cidr) + fmt.Println("vpc description: ", v.Description) + fmt.Println("vpc isDefault: ", v.IsDefault) + fmt.Println("vpc secondaryCidr: ", v.SecondaryCidr) + fmt.Println("vpc tags: ", v.Tags) +} +``` + +## 查询指定VPC + +根据特定的VPC ID可以查看相关VPC的详情信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +result, err := client.GetVPCDetail(vpcId) +if err != nil { + fmt.Println("get vpc detail error: ", err) + return +} + +// 查询得到vpc的id +fmt.Println("VPC id: ", result.VPC.VPCId) +// 查询得到vpc的名称 +fmt.Println("VPC name: ", result.VPC.Name) +// 查询得到vpc的网段及子网掩码 +fmt.Println("VPC cidr: ", result.VPC.Cidr) +// 查询得到vpc的描述 +fmt.Println("VPC description: ", result.VPC.Description) +// 查询得到是否为默认vpc +fmt.Println("VPC isDefault: ", result.VPC.IsDefault) +// 查询得到vpc中包含的子网 +fmt.Println("VPC subnets: ", result.VPC.Subnets) +// 查询得到vpc的辅助网段cidr列表 +fmt.Println("VPC secondaryCidr: ", result.VPC.SecondaryCidr) +``` + +查询得到的VPC详情信息包括名称、网段、创建时间、描述等信息。 + +## 删除VPC + +使用以下代码可以删除特定的VPC。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeleteVPC(vpcId, clientToken); err != nil { + fmt.Println("delete vpc error: ", err) + return +} + +fmt.Printf("delete vpc %s success.", vpcId) +``` + +> 注意: 参数中的clientToken表示幂等性Token,是一个长度不超过64位的ASCII字符串,详见[ClientToken幂等性](https://cloud.baidu.com/doc/VPC/s/gjwvyu77i/#%E5%B9%82%E7%AD%89%E6%80%A7) + +## 更新VPC + +使用以下代码可以更新指定VPC的名称和描述信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UpdateVPCArgs{ + // 设置vpc的新名称 + Name: "TestVPCUpdate", + // 设置vpc的新备注 + Description: "Test VPC description update", + // 设置幂等性Token + ClientToken: clientToken, +} +if err := client.UpdateVPC(vpcId, args); err != nil { + fmt.Println("update vpc error: ", err) + return +} + +fmt.Printf("update vpc %s success.", vpcId) +``` + +> 注意: 更新VPC时,对name和description字段的规范要求参考`创建VPC`一节。 + + +## 查询VPC内内网Ip的信息 +使用以下代码可以更新指定VPC的名称和描述信息。 +>PrivateIpRange的格式为"192.168.0.1-192.168.0-5" + 参数中PrivateIpAddresses或PrivateIpRange的ip数量大小不能超过100. + 若PrivateIpAddresses和PrivateIpRange同时存在,PrivateIpRange优先。 + +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &GetVpcPrivateIpArgs{ + VpcId: "vpc-2pa2x0bjt26i", + PrivateIpAddresses: []string{"192.168.0.1,192.168.0.2"}, + PrivateIpRange: "192.168.0.0-192.168.0.45", + } + +result, err := client.GetVPCDetail(vpcId) +if err != nil { + fmt.Println("get vpc privateIp address info error: ", err) + return +} + + +fmt.Println("privateIpAddresses size is : ", len(result.VpcPrivateIpAddresses)) + +``` +# 子网管理 + +子网是 VPC 内的用户可定义的IP地址范围,根据业务需求,通过CIDR(无类域间路由)可以指定不同的地址空间和IP段。未来用户可以将子网作为一个单位,用来定义Internet访问权限、路由规则和安全策略。 + +## 创建子网 + +通过以下代码可以在指定VPC中创建子网。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.CreateSubnetArgs{ + // 设置子网的名称 + Name: "TestSDK-Subnet", + // 设置子网的可用区名称 + ZoneName: "cn-bj-a", + // 设置子网的cidr + Cidr: "192.168.1.0/24", + // 设置子网所属vpc的id + VpcId: "vpc-4njbqurm0uag", + // 设置子网的类型,包括“BCC”、“BCC_NAT”、“BBC”三种 + SubnetType: vpc.SUBNET_TYPE_BCC, + // 设置子网的描述 + Description: "test subnet", + // 是否分配IPv6网段,true表示开启,默认false不开启 + EnableIpv6: true, + // 设置子网的标签键值对列表 + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, +} +result, err := client.CreateSubnet(args) +if err != nil { + fmt.Println("create subnet error: ", err) + return +} + +fmt.Println("create subnet success, subnet id: ", result.SubnetId) +``` + +> 注意: +> - 子网名称,不能取值"default",长度不超过65个字符,可由数字,字符,下划线组成 +> - 可用区名称, 其查询方式参考[查询可用区列表](https://cloud.baidu.com/doc/BCC/s/ijwvyo9im/) + +## 查询子网列表 + +使用以下代码可以查询符合条件的子网列表。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ListSubnetArgs{ + // 设置批量获取列表的查询的起始位置,是一个由系统生成的字符串 + Marker: marker, + // 设置每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 + MaxKeys: maxKeys, + // 设置所属vpc的id + VpcId: vpcId, + // 设置所属可用区的名称 + ZoneName: zoneName, + // 设置子网类型 + SubnetType: vpc.SUBNET_TYPE_BCC, +} +result, err := client.ListSubnets(args) +if err != nil { + fmt.Println("list subnets error: ", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("subnet list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("subnet list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("subnet list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("subnet list maxKeys: ", result.MaxKeys) +// 获取subnet的具体信息 +for _, sub := range result.Subnets { + fmt.Println("subnet id: ", sub.SubnetId) + fmt.Println("subnet name: ", sub.Name) + fmt.Println("subnet zoneName: ", sub.ZoneName) + fmt.Println("subnet cidr: ", sub.Cidr) + fmt.Println("subnet vpcId: ", sub.VPCId) + fmt.Println("subnet subnetType: ", sub.SubnetType) + fmt.Println("subnet description: ", sub.Description) + fmt.Println("subnet availableIp: ", sub.AvailableIp) + fmt.Println("subnet tags: ", sub.Tags) +} +``` + +根据该API,可以根据vpcId、zoneName、subnetType等条件查询符合要求的子网列表。 + +## 查询指定子网 + +根据以下代码可以查询指定子网的详细信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +result, err := client.GetSubnetDetail(subnetId) +if err != nil { + fmt.Println("get subnet detail error: ", err) + return +} + +// 查询得到子网的id +fmt.Println("subnet id: ", result.Subnet.SubnetId) +// 查询得到子网的名称 +fmt.Println("subnet name: ", result.Subnet.Name) +// 查询得到子网所属可用区的名称 +fmt.Println("subnet zoneName: ", result.Subnet.ZoneName) +// 查询得到子网的cidr +fmt.Println("subnet cidr: ", result.Subnet.Cidr) +// 查询得到子网所属vpc的id +fmt.Println("subnet vpcId: ", result.Subnet.VPCId) +// 查询得到子网的类型 +fmt.Println("subnet subnetType: ", result.Subnet.SubnetType) +// 查询得到子网的描述 +fmt.Println("subnet description: ", result.Subnet.Description) +// 查询得到子网内可用ip数 +fmt.Println("subnet availableIp: ", result.Subnet.AvailableIp) +// 查询得到子网绑定的标签列表 +fmt.Println("subnet tags: ", result.Subnet.Tags) +``` + +通过该接口可以得到子网的名称、可用区、cidr、类型、描述、可用ip数、标签列表等信息。 + +> 注意: 子网类型包括"BCC”、"BCC_NAT”、”BBC”三种。 + +## 删除子网 + +通过以下代码可以删除指定子网。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeleteSubnet(subnetId, clientToken); err != nil { + fmt.Println("delete subnet error: ", err) + return +} + +fmt.Printf("delete subnet %s success.", subnetId) +``` + +> 注意: 参数中的clientToken表示幂等性Token,是一个长度不超过64位的ASCII字符串,详见[ClientToken幂等性](https://cloud.baidu.com/doc/VPC/s/gjwvyu77i/#%E5%B9%82%E7%AD%89%E6%80%A7) + +## 更新子网 + +使用以下代码可以更新子网信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UpdateSubnetArgs{ + // 设置更新操作使用的幂等性token + ClientToken: clientToken, + // 设置更新后的子网名称 + Name: "TestSDK-Subnet-update", + // 设置更新后的子网描述 + Description: "subnet update", + // 是否分配IPv6网段,true表示开启,默认false不开启 + EnableIpv6: true, +} +if err := client.UpdateSubnet(subnetId, args); err != nil { + fmt.Println("update subnet error: ", err) + return +} + +fmt.Printf("update subnet %s success.", subnetId) +``` + +使用该接口可以实现对子网名称和描述信息的更新操作。 + +## 创建预留网段 + +使用以下代码可以创建预留网段。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + ak, sk, endpoint := "Your Ak", "Your Sk", "Your endpoint" // Initialize ak, sk, and endpoint + VPC_CLIENT, _ := vpc.NewClient(ak, sk, endpoint) // Initialize VPC client + + args := &vpc.CreateIpreserveArgs{ + SubnetId: "sbn-4fa15xxxxxxx", // ID of the subnet to create the reserved ip segment + IpCidr: "192.168.0.0/31", // Reserved CIDR + IpVersion: 4, // IP version (4 for IPv4, 6 for IPv6) + // Description: "test", // Description of the reserved CIDR, optional + // ClientToken: "", // Client token, optional + } + + result, err := VPC_CLIENT.CreateIpreserve(args) + + if err != nil { + fmt.Println("create reserved ip error: ", err) + return + } + + fmt.Println("create reserved ip success, reserved CIDR id: ", result.IpReserveId) + +``` +## 查询预留网段列表 + +使用以下代码可以查询符合条件的预留网段列表。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + + ak, sk, endpoint := "Your Ak", "Your Sk", "Your endpoint" // Initialize ak, sk, and endpoint + VPC_CLIENT, _ := vpc.NewClient(ak, sk, endpoint) // Initialize VPC client + + ipReserveId := "ipr-nc4xxxxx" // ID of the reserved CIDR to be deleted + clientToken := "" // optional yourclientToken + + err := VPC_CLIENT.DeleteIpreserve(ipReserveId, clientToken) + + if err != nil { + fmt.Println("DeleteIpreserve error: ", err) + return + } + + fmt.Printf("delete reserved CIDR %s success.", ipReserveId) + +``` +## 删除预留网段 + +使用以下代码可以删除指定预留网段。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + + // 设置AK、SK和Endpoint + ak, sk, endpoint := "Your Ak", "Your Sk", "Your endpoint" // Initialize ak, sk, and endpoint + + // 创建VPC客户端 + VPC_CLIENT, _ := vpc.NewClient(ak, sk, endpoint) + + args := &vpc.ListIpeserveArgs{ + SubnetId: "sbn-4fxx51yxxxx", + Marker: "", // 查询的起始位置,为空则从第一条开始查询 + MaxKeys: 10, + } + + // 添加查询保留IP范围的代码 + result, err := VPC_CLIENT.ListIpreserve(args) + if err != nil { + fmt.Printf("List reserved IP ranges failed with %s\n", err) + } + + // 输出子网ID和保留IP范围信息 + for _, IpReserve := range result.IpReserves { + fmt.Printf("IP Range: %s, Description: %s\n", IpReserve.IpCidr, IpReserve.SubnetId) + fmt.Println("isTruncated %d", result.IsTruncated) + } + +``` + +# 路由表管理 + +路由表是指路由器上管理路由条目的列表。 + +## 查询所有路由表 + +使用以下代码可以完成对路由表的查询。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +// 方式1: 通过路由表id进行查询 +result, err := client.GetRouteTableDetail(routeTableId, "") +if err != nil { + fmt.Println("get route table error: ", err) + return +} + +// 方式2: 通过vpc id进行查询 +result, err := client.GetRouteTableDetail("", vpcId) +if err != nil { + fmt.Println("get route table error: ", err) + return +} + +// 查询得到路由表id +fmt.Println("result of route table id: ", result.RouteTableId) +// 查询得到vpc id +fmt.Println("result of vpc id: ", result.VpcId) +// 查询得到所有的路由规则列表 +for _, route := range result.RouteRules { + fmt.Println("route rule id: ", route.RouteRuleId) + fmt.Println("route rule routeTableId: ", route.RouteTableId) + fmt.Println("route rule sourceAddress: ", route.SourceAddress) + fmt.Println("route rule destinationAddress: ", route.DestinationAddress) + fmt.Println("route rule nexthopId: ", route.NexthopId) + fmt.Println("route rule nexthopType: ", route.NexthopType) + fmt.Println("route rule description: ", route.Description) +} +``` + +> 注意: +> - 请求参数routeTableId和vpcId不可以同时为空 +> - 使用该接口可以查询得到所有相关的路由规则列表 + +## 创建路由规则 + +使用以下代码可以创建路由规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.CreateRouteRuleArgs{ + // 设置路由表id,必选 + RouteTableId: RouteTableID, + // 设置源网段,必选 + SourceAddress: "192.168.1.0/24", + // 设置目标网段,必选 + DestinationAddress: "172.17.0.0/16", + // 设置下一跳类型,必选, 创建单线路由必填 + NexthopType: vpc.NEXTHOP_TYPE_NAT, + // 多线路由下一跳信息,创建多线路由时该字段必填,NextHop的nexthopType目前只支持专线网关类型:"dcGateway" + NextHopList: []NextHop, + // 设置下一跳id,必选 + NexthopId: NatID, + // 设置路由规则的描述信息,可选 + Description: "test route rule", +} +result, err := client.CreateRouteRule(args) +if err != nil { + fmt.Println("create route rule error: ", err) + return +} +fmt.Println("create route rule success, route rule id: ", result.RouteRuleId) +``` + +创建路由表规则,有以下几点需要注意: +- 源网段选择自定义时,自定义网段需在已有子网范围内,0.0.0.0/0除外; +- 目标网段不能与当前所在VPC cidr重叠(目标网段或本VPC cidr为0.0.0.0/0时例外); +- 新增路由条目的源网段和目标网段,不能与路由表中已有条目源网段和目标网段完全一致。 +- 针对下一跳的类型,目前支持如下几种: + - Bcc类型是 "custom"; + - VPN类型是 "vpn"; + - NAT类型是 "nat"; + - 专线网关类型是 "dcGateway"; + - 创建单线路由时该字段必填NexthopType + - NextHopList 多线路由下一跳信息,创建多线路由时该字段必填 + - NextHop.nexthopId 下一跳ID + - NextHop.nexthopType 路由类型。目前只支持专线网关类型:"dcGateway" + - NextHop.pathType 多线模式。负载均衡取值为ecmp;主备模式取值ha:active、ha:standby,分别表示主、备路由 + - 对等连接类型是 "peerConn",nexthopId需要传端口ID(qpif-vx034sff4tsm) + - IPv6网关类型是 "ipv6gateway" + - 弹性网卡类型是 "enic" + - 高可用虚拟IP是 "havip" + +## 删除路由规则 + +使用以下代码可以删除特定的路由规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeleteRouteRule(routeRuleId, clientToken); err != nil { + fmt.Println("delete route rule error: ", err) + return +} + +fmt.Printf("delete route rule %s success.", routeRuleId) +``` + +> 注意: 参数中的clientToken表示幂等性Token,是一个长度不超过64位的ASCII字符串,详见[ClientToken幂等性](https://cloud.baidu.com/doc/VPC/s/gjwvyu77i/#%E5%B9%82%E7%AD%89%E6%80%A7) + + +## 更新路由规则 + +使用以下代码可以更新路由规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + + ak, sk, endpoint := "Your Ak", "Your Sk", "Your endpoint" + VPC_CLIENT, _ := vpc.NewClient(ak, sk, endpoint) + + args := &vpc.UpdateRouteRuleArgs{ + RouteRuleId: "rr-1zcxxxxxxyyy", + // SourceAddress: "Your SourceAddress", // optional + // DestinationAddress: "Your DestinationAddress", // optional + // NexthopId: "your NewNexthopId", // optional + // Description: "Your New Description", // optional + } + + err := VPC_CLIENT.UpdateRouteRule(args) + if err != nil { + fmt.Println("Route rule updated fail") + } + + fmt.Println("Route rule %s updated successfully", args.RouteRuleId) +``` + +## 查询路由规则 + +使用以下代码可以查询路由规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +ak, sk, endpoint := "Your Ak", "Your Sk", "Your endpoint" + VPC_CLIENT, _ := vpc.NewClient(ak, sk, endpoint) + + routeTableId := "rt-hf1ezardxxxx" + vpcId := "vpc-nx6bs5xxxxxx" + + // routeTableId and vpcId should not be empty at the same time + result, err := VPC_CLIENT.GetRouteTableDetail(routeTableId, vpcId) + if err != nil { + fmt.Println("get route table error: ", err) + } + + // print result + fmt.Println("result of route table id: ", result.RouteTableId) + fmt.Println("result of vpc id: ", result.VpcId) + + for _, route := range result.RouteRules { + fmt.Println("route rule id: ", route.RouteRuleId) + fmt.Println("route rule routeTableId: ", route.RouteTableId) + fmt.Println("route rule sourceAddress: ", route.SourceAddress) + fmt.Println("route rule destinationAddress: ", route.DestinationAddress) + fmt.Println("route rule nexthopId: ", route.NexthopId) + fmt.Println("route rule nexthopType: ", route.NexthopType) + fmt.Println("route rule description: ", route.Description) + } +``` + +# ACL管理 + +访问控制列表(Access Control List,ACL)作为应用在子网上的防火墙组件帮助用户实现子网级别的安全访问控制。 + +## 查询ACL + +使用以下代码可以完成acl信息的查询。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +result, err := client.ListAclEntrys(vpcId) +if err != nil { + fmt.Println("list acl entrys error: ", err) + return +} + +// 查询得到acl所属的vpc id +fmt.Println("acl entrys of vpcId: ", result.VpcId) +// 查询得到acl所属的vpc名称 +fmt.Println("acl entrys of vpcName: ", result.VpcName) +// 查询得到acl所属的vpc网段 +fmt.Println("acl entrys of vpcCidr: ", result.VpcCidr) +// 查询得到acl的详细信息 +for _, acl := range result.AclEntrys { + fmt.Println("subnetId: ", acl.SubnetId) + fmt.Println("subnetName: ", acl.SubnetName) + fmt.Println("subnetCidr: ", acl.SubnetCidr) + fmt.Println("aclRules: ", acl.AclRules) +} +``` + +根据该接口得到的AclEntry列表,包括subnetId、subnetName、subnetCidr、aclRules。 + +## 添加ACL规则 + +根据以下代码可以创建acl规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +requests := []vpc.AclRuleRequest{ + { + // 设置acl规则所属的子网id + SubnetId: "sbn-e4cg8e8zkizs", + // 设置acl规则的协议 + Protocol: vpc.ACL_RULE_PROTOCOL_TCP, + // 设置acl规则的源ip + SourceIpAddress: "192.168.2.0", + // 设置acl规则的目的ip + DestinationIpAddress: "192.168.0.0/24", + // 设置acl规则的源端口 + SourcePort: "8888", + // 设置acl规则的目的端口 + DestinationPort: "9999", + // 设置acl规则的优先级 + Position: 12, + // 设置acl规则的方向 + Direction: vpc.ACL_RULE_DIRECTION_INGRESS, + // 设置acl规则的策略 + Action: vpc.ACL_RULE_ACTION_ALLOW, + // 设置acl规则的描述信息 + Description: "test", + }, +} +args := &vpc.CreateAclRuleArgs{ + AclRules: requests, +} + +if err := client.CreateAclRule(args); err != nil { + fmt.Println("create acl rule error: ", err) + return +} + +fmt.Println("create acl rule success.") +``` + +使用该接口可以一次创建多条acl规则,对规则参数中的注意事项描述如下: +- protocol: 支持的协议包括all tcp udp icmp +- sourcePort: 源端口,例如1-65535,或8080 +- destinationPort: 目的端口,例如1-65535,或8080 +- position: 优先级 1-5000且不能与已有条目重复。数值越小,优先级越高,规则匹配顺序为按优先级由高到低匹配 +- direction: 规则的入站ingress, 规则的出站egress +- action: 支持的策略包括allow和deny + +## 查询ACL规则 + +使用以下代码可以查询acl规则信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ListAclRulesArgs{ + // 设置acl所属子网的id + SubnetId: subnetId, + // 设置批量获取列表的查询的起始位置 + Marker: marker, + // 设置每页包含的最大数量 + MaxKeys: maxKeys, +} + +result, err := client.ListAclRules(args) +if err != nil { + fmt.Println("list acl rules error: ", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("acl list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("acl list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("acl list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("acl list maxKeys: ", result.MaxKeys) +// 获取acl的列表信息 +for _, acl := range result.AclRules { + fmt.Println("acl rule id: ", acl.Id) + fmt.Println("acl rule subnetId: ", acl.SubnetId) + fmt.Println("acl rule description: ", acl.Description) + fmt.Println("acl rule protocol: ", acl.Protocol) + fmt.Println("acl rule sourceIpAddress: ", acl.SourceIpAddress) + fmt.Println("acl rule destinationIpAddress: ", acl.DestinationIpAddress) + fmt.Println("acl rule sourcePort: ", acl.SourcePort) + fmt.Println("acl rule destinationPort: ", acl.DestinationPort) + fmt.Println("acl rule position: ", acl.Position) + fmt.Println("acl rule direction: ", acl.Direction) + fmt.Println("acl rule action: ", acl.Action) +} +``` + +> 注意: +> - 使用该接口时,必需提供subnetId参数,以获取特定子网的acl规则列表信息。 +> - 系统为用户创建了2条默认ACL规则(无id)。其中入站和出站各一条,规则内容均为全入全出。默认规则,不支持更改和删除。 + +## 更新ACL规则 + +使用以下代码可以实现对特定acl规则的更新操作。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UpdateAclRuleArgs{ + // 设置acl的最新协议 + Protocol: vpc.ACL_RULE_PROTOCOL_TCP, + // 设置acl的源ip + SourceIpAddress: "192.168.2.0", + // 设置acl的目的ip + DestinationIpAddress: "192.168.0.0/24", + // 设置acl的源端口 + SourcePort: "3333", + // 设置acl的目的端口 + DestinationPort: "4444", + // 设置acl的优先级 + Position: 12, + // 设置acl的策略 + Action: vpc.ACL_RULE_ACTION_ALLOW, + // 设置acl最新的描述信息 + Description: "test", +} + +if err := client.UpdateAclRule(aclRuleId, args); err != nil { + fmt.Println("update acl rule error: ", err) + return +} + +fmt.Printf("update acl rule %s success.", aclRuleId) +``` + +以上接口可用于对acl规则各个字段的更新过程。 + +## 删除ACL规则 + +使用以下代码可以删除指定的acl规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeleteAclRule(aclRuleId, clientToken); err != nil { + fmt.Println("delete acl rule error: ", err) + return +} + +fmt.Printf("delete acl rule %s success.", aclRuleId) +``` + +> 注意: 参数中的clientToken表示幂等性Token,是一个长度不超过64位的ASCII字符串,详见[ClientToken幂等性](https://cloud.baidu.com/doc/VPC/s/gjwvyu77i/#%E5%B9%82%E7%AD%89%E6%80%A7) + + +# NAT网关管理 + +NAT(Network Address Translation)网关为私有网络提供访问Internet服务,支持SNAT和DNAT,可以使多台云服务器共享公网IP资源访问Internet,也可以使云服务器能够提供Internet服务。NAT网关可以绑定EIP实例及共享带宽,为云服务器实现从内网IP到公网IP的多对一或多对多的地址转换服务。 + +## 创建NAT网关 + +使用以下代码可以创建nat网关。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.CreateNatGatewayArgs{ + // 设置nat网关的名称 + Name: name, + // 设置nat网关所属的vpc id + VpcId: vpcId, + // 设置nat网关的规格 + Spec: vpc.NAT_GATEWAY_SPEC_SMALL, + // 设置nat网关的eip列表 + Eips: []string{eip}, + // 设置nat网关的计费信息 + Billing: &vpc.Billing{ + PaymentTiming: vpc.PAYMENT_TIMING_POSTPAID, + }, + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, +} +result, err := client.CreateNatGateway(args) +if err != nil { + fmt.Println("create nat gateway error: ", err) + return +} + +fmt.Println("create nat gateway success, nat gateway id: ", result.NatId) +``` + +> 注意: 创建过程中,应注意以下事项: +> - NAT网关的名称,由大小写字母、数字以及-_ /.特殊字符组成,必须以字母开头,长度1-65 +> - NAT网关的大小,有small(最多支持绑定5个公网IP)、medium(最多支持绑定10个公网IP)、large(最多支持绑定15个公网IP)三种 +> - NAT网关可以关联一个公网EIP或者共享带宽中的一个或多个EIP +> - 付款方式支持预支付(Prepaid)和后支付(Postpaid)两种,预支付当前仅支持按月,时长取值范围: [1,2,3,4,5,6,7,8,9,12,24,36] + +## 查询NAT网关列表 + +使用以下代码可以查询符合条件的nat网关列表。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ListNatGatewayArgs{ + // 设置nat网关所属的vpc id,必选 + VpcId: vpcId, + // 指定查询的NAT的Id + NatId: natId, + // 指定查询的NAT的名称 + Name: name, + // 指定查询的NAT绑定的EIP + Ip: ip, + // 设置nat网关批量获取列表的查询的起始位置 + Marker: marker, + // 设置nat网关每页包含的最大数量,最大数量不超过1000。缺省值为1000 + MaxKeys: maxKeys, +} +result, err := client.ListNatGateway(args) +if err != nil { + fmt.Println("list nat gateway error: ", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("nat list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("nat list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("nat list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("nat list maxKeys: ", result.MaxKeys) +// 获取nat的列表信息 +for _, nat := range result.Nats { + fmt.Println("nat id: ", nat.Id) + fmt.Println("nat name: ", nat.Name) + fmt.Println("nat vpcId: ", nat.VpcId) + fmt.Println("nat spec: ", nat.Spec) + fmt.Println("nat eips: ", nat.Eips) + fmt.Println("nat status: ", nat.Status) + fmt.Println("nat paymentTiming: ", nat.PaymentTiming) + fmt.Println("nat expireTime: ", nat.ExpiredTime) + fmt.Println("nat tags: ", nat.Tags) +} +``` + +> 注意: +> - 可根据NAT网关ID、NAT网关的name、NAT网关绑定的EIP来查询。 +> - 若不提供查询条件,则默认查询覆盖所有NAT网关 +> - vpcId为必选参数 + +## 查询NAT网关详情 + +使用以下代码可以查询特定nat网关的详细信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +result, err := client.GetNatGatewayDetail(natId) +if err != nil { + fmt.Println("get nat gateway details error: ", err) + return +} + +// 查询得到nat网关的id +fmt.Println("nat id: ", result.Id) +// 查询得到nat网关的名称 +fmt.Println("nat name: ", result.Name) +// 查询得到nat网关所属的vpc id +fmt.Println("nat vpcId: ", result.VpcId) +// 查询得到nat网关的大小 +fmt.Println("nat spec: ", result.Spec) +// 查询得到nat网关绑定的EIP的IP地址列表 +fmt.Println("nat eips: ", result.Eips) +// 查询得到nat网关的状态 +fmt.Println("nat status: ", result.Status) +// 查询得到nat网关的付费方式 +fmt.Println("nat paymentTiming: ", result.PaymentTiming) +// 查询得到nat网关的过期时间 +fmt.Println("nat expireTime: ", result.ExpiredTime) +``` + +## 更新NAT网关名称 + +使用以下代码可以对nat网关的名称进行更改。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UpdateNatGatewayArgs{ + // 设置nat网关的最新名称 + Name: "TestNatUpdate", +} + +if err := client.UpdateNatGateway(natId, args); err != nil { + fmt.Println("update nat gateway error: ", err) + return +} + +fmt.Printf("update nat gateway %s success.", natId) +``` + +> 注意: 目前该接口仅支持对网关名称属性的更改。 + +## 绑定EIP + +使用以下代码可以为nat网关绑定eip。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.BindEipsArgs{ + // 设置绑定的EIP ID列表 + Eips: []string{eip}, +} +if err := client.BindEips(natId, args); err != nil { + fmt.Println("bind eips error: ", err) + return +} + +fmt.Println("bind eips success.") +``` + +注意: +- 若该NAT已经绑定EIP,必须解绑后才可绑定。 +- 若该NAT已经绑定共享带宽,可以继续绑定该共享带宽中的其他IP。 + +## 解绑EIP + +使用以下代码可以为nat网关解绑eip。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UnBindEipsArgs{ + // 设置解绑的EIP ID列表 + Eips: []string{eip}, +} +if err := client.UnBindEips(natId, args); err != nil { + fmt.Println("unbind eips error: ", err) + return +} + +fmt.Println("unbind eips success.") +``` + +## 绑定DNAT EIP + +使用以下代码可以为nat网关绑定DNAT EIP。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.BindDnatEipsArgs{ + // 设置绑定的DNAT EIP ID列表 + DnatEips: []string{dnatEips}, +} +if err := client.BindDnatEips(natId, args); err != nil { + fmt.Println("bind DNAT Eips error: ", err) + return +} + +fmt.Println("bind DNAT Eips success.") +``` + +注意: +- 若该NAT DNAT已经绑定EIP,必须解绑后才可绑定。 +- 若该NAT DNAT已经绑定共享带宽,可以继续绑定该共享带宽中的其他IP。 + +## 解绑DNAT EIP + +使用以下代码可以为nat网关解绑DNAT EIP。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UnBindDnatEipsArgs{ + // 设置解绑的DNAT EIP ID列表 + DnatEips: []string{dnatEips}, +} +if err := client.UnBindDnatEips(natId, args); err != nil { + fmt.Println("unbind DNAT Eips error: ", err) + return +} + +fmt.Println("unbind DNAT Eips success.") +``` + +## 释放NAT网关 + +使用以下代码释放特定的nat网关。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeleteNatGateway(natId, clientToken); err != nil { + fmt.Println("delete nat gateway error: ", err) + return +} + +fmt.Printf("delete nat gateway %s success.", natId) +``` + +> 注意: 预付费未到期的NAT网关不能释放。 + +## NAT网关续费 + +使用以下接口完成nat网关的续费操作,延长过期时间。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.RenewNatGatewayArgs{ + // 设置nat网关续费的订单信息 + Billing: &vpc.Billing{ + Reservation: &vpc.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, +} +if err := client.RenewNatGateway(natId, args); err != nil { + fmt.Println("renew nat gateway error: ", err) + return +} + +fmt.Printf("renew nat gateway %s success.", natId) +``` + +> 注意: +- 后付费的NAT网关不能续费 + +## 创建SNAT规则 +使用以下代码可以创建nat网关的snat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &CreateNatGatewaySnatRuleArgs{ +RuleName: "sdk-test", +PublicIpsAddress: []string{"100.88.10.84"}, +SourceCIDR: "192.168.3.3", +} +result, err := VPC_CLIENT.CreateNatGatewaySnatRule("nat-b1jb3b5e34tc", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 删除SNAT规则 +使用以下代码可以删除nat网关的snat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +VPC_CLIENT.DeleteNatGatewaySnatRule("nat-b1jb3b5e34tc", "rule-hprz7sv9zvcx", getClientToken()) +``` + +## 修改SNAT规则 +使用以下代码可以修改nat网关的snat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &UpdateNatGatewaySnatRuleArgs{ +RuleName: "sdk-test-1", +SourceCIDR: "192.168.3.6", +} +VPC_CLIENT.UpdateNatGatewaySnatRule("nat-b1jb3b5e34tc", "rule-hprz7sv9zvcx", args) +``` + +## 查询SNAT规则 +使用以下代码可以查询nat网关的snat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &ListNatGatewaySnatRuleArgs{ +NatId: "nat-b1jb3b5e34tc", +} +result, err := VPC_CLIENT.ListNatGatewaySnatRules(args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 创建DNAT规则 +使用以下代码可以创建nat网关的dnat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &CreateNatGatewayDnatRuleArgs{ + RuleName: "dnat_go", + PublicIpAddress: "100.88.14.90", + PrivateIpAddress: "192.168.1.1", + Protocol: "TCP", + PublicPort: "1212", + PrivatePort: "1212", +} +result, err := VPC_CLIENT.CreateNatGatewayDnatRule("nat-b1jb3b5e34tc", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +## 删除DNAT规则 +使用以下代码可以删除nat网关的dnat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +VPC_CLIENT.DeleteNatGatewayDnatRule("nat-b1jb3b5e34tc", "rule-8gee5abqins0", getClientToken()) +``` + +## 修改DNAT规则 +使用以下代码可以修改nat网关的dnat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &UpdateNatGatewayDnatRuleArgs{ +RuleName: "sdk-test-3", +PrivateIpAddress: "192.168.1.5", +} +VPC_CLIENT.UpdateNatGatewayDnatRule("nat-b1jb3b5e34tc", "rule-8gee5abqins0", args) +``` + +## 查询DNAT规则 +使用以下代码可以查询nat网关的dnat规则。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &ListNatGatewaDnatRuleArgs{} +result, err := VPC_CLIENT.ListNatGatewayDnatRules("nat-b1jb3b5e34tc", args) +ExpectEqual(t.Errorf, nil, err) +r, err := json.Marshal(result) +fmt.Println(string(r)) +``` + +# 对等连接管理 + +对等连接(Peer Connection)为用户提供了VPC级别的网络互联服务,使用户实现在不同虚拟网络之间的流量互通,实现同区域/跨区域,同用户/不同用户之间稳定高速的虚拟网络互联。 + +## 创建对等连接 + +使用以下代码创建对等连接。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.CreatePeerConnArgs{ + // 设置对等连接的带宽 + BandwidthInMbps: 10, + // 设置对等连接的描述信息 + Description: "test peer conn", + // 设置对等连接的本端端口名称 + LocalIfName: "local-interface", + // 设置对等连接的本端vpc的id + LocalVpcId: vpcId, + // 设置对等连接的对端账户ID,只有在建立跨账号的对等连接时需要该字段 + peerAccountId: peerAccountId, + // 设置对等连接的对端vpc的id + PeerVpcId: peerVpcId, + // 设置对等连接的对端区域 + PeerRegion: region, + // 设置对等连接的对端接口名称,只有本账号的对等连接才允许设置该字段 + PeerIfName: "peer-interface", + // 设置对等连接的计费信息 + Billing: &vpc.Billing{ + PaymentTiming: vpc.PAYMENT_TIMING_POSTPAID, + }, + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, +} +result, err := client.CreatePeerConn(args) +if err != nil { + fmt.Println("create peerconn error: ", err) + return +} + +fmt.Println("create peerconn success, peerconn id: ", result.PeerConnId) +``` + +> 注意: +> - 对于本端区域和对端区域相同的对等连接,只支持后付费。 +> - 跨账号的对等连接,必须接受端接受后对等连接才可用。 +> - 对于同账号的对等连接,系统会触发对端自动接受。 +> - 任意两个VPC之间最多只能存在一条对等连接。 +> - 发起端和接收端的VPC不能是同一个。 +> - 如果本端vpc和对端vpc均为中继vpc,则不可以建立对等连接。 + +## 查询对等连接列表 + +使用以下代码可以查询对等连接的列表信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ListPeerConnsArgs{ + // 指定对等连接所属的vpc id + VpcId: vpcId, + // 指定批量获取列表的查询的起始位置 + Marker: marker, + // 指定每页包含的最大数量,最大数量不超过1000。缺省值为1000 + MaxKeys: maxKeys, +} +result, err := client.ListPeerConn(args) +if err != nil { + fmt.Println("list peer conns error: ", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("peerconn list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("peerconn list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("peerconn list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("peerconn list maxKeys: ", result.MaxKeys) +// 获取对等连接的列表信息 +for _, pc := range result.PeerConns { + fmt.Println("peerconn id: ", pc.PeerConnId) + fmt.Println("peerconn role: ", pc.Role) + fmt.Println("peerconn status: ", pc.Status) + fmt.Println("peerconn bandwithInMbp: ", pc.BandwidthInMbps) + fmt.Println("peerconn description: ", pc.Description) + fmt.Println("peerconn localIfId: ", pc.LocalIfId) + fmt.Println("peerconn localIfName: ", pc.LocalIfName) + fmt.Println("peerconn localVpcId: ", pc.LocalVpcId) + fmt.Println("peerconn localRegion: ", pc.LocalRegion) + fmt.Println("peerconn peerVpcId: ", pc.PeerVpcId) + fmt.Println("peerconn peerRegion: ", pc.PeerRegion) + fmt.Println("peerconn peerAccountId: ", pc.PeerAccountId) + fmt.Println("peerconn paymentTiming: ", pc.PaymentTiming) + fmt.Println("peerconn dnsStatus: ", pc.DnsStatus) + fmt.Println("peerconn createdTime: ", pc.CreatedTime) + fmt.Println("peerconn expiredTime: ", pc.ExpiredTime) +} +``` + +使用该接口可以查询得到所有符合条件的对等连接信息,其中,vpcId是可选参数。 + +## 查看对等连接详情 + +通过以下代码可以查询特定对等连接的详细信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +result, err := client.GetPeerConnDetail(peerConnId, vpc.PEERCONN_ROLE_INITIATOR) +if err != nil { + fmt.Println("get peer conn detail error: ", err) + return +} + +// 查询得到对等连接的id +fmt.Println("peerconn id: ", result.PeerConnId) +// 查询得到对等连接的角色, "initiator"表示发起端"acceptor"表示接受端 +fmt.Println("peerconn role: ", result.Role) +// 查询得到对等连接的状态 +fmt.Println("peerconn status: ", result.Status) +// 查询得到对等连接的带宽 +fmt.Println("peerconn bandwithInMbp: ", result.BandwidthInMbps) +// 查询得到对等连接的描述 +fmt.Println("peerconn description: ", result.Description) +// 查询得到对等连接的本端接口ID +fmt.Println("peerconn localIfId: ", result.LocalIfId) +// 查询得到对等连接的本端接口名称 +fmt.Println("peerconn localIfName: ", result.LocalIfName) +// 查询得到对等连接的本端VPC ID +fmt.Println("peerconn localVpcId: ", result.LocalVpcId) +// 查询得到对等连接的本端区域 +fmt.Println("peerconn localRegion: ", result.LocalRegion) +// 查询得到对等连接的对端VPC ID +fmt.Println("peerconn peerVpcId: ", result.PeerVpcId) +// 查询得到对等连接的对端区域 +fmt.Println("peerconn peerRegion: ", result.PeerRegion) +// 查询得到对等连接的对端账户ID +fmt.Println("peerconn peerAccountId: ", result.PeerAccountId) +// 查询得到对等连接的计费方式 +fmt.Println("peerconn paymentTiming: ", result.PaymentTiming) +// 查询得到对等连接的dns状态 +fmt.Println("peerconn dnsStatus: ", result.DnsStatus) +// 查询得到对等连接的创建时间 +fmt.Println("peerconn createdTime: ", result.CreatedTime) +// 查询得到对等连接的过期时间 +fmt.Println("peerconn expiredTime: ", result.ExpiredTime) +// 查询得到对等连接的标签 +fmt.Println("peerconn tags: ", result.Tags) +``` + +> 注意: "initiator"表示发起端"acceptor"表示接受端,同region的对等连接可以据此进行详情查询,若不设置该参数,同region则随机返回一端信息。 + +## 更新对等连接本端接口名称和备注 + +使用以下代码可以更新对等连接本端接口名称和备注。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.UpdatePeerConnArgs{ + // 设置对等连接的接口ID 不可更改,必选 + LocalIfId: localIfId, + // 设置对等连接的本端端口名称 + LocalIfName: "test-update", + // 设置对等连接的本端端口描述 + Description: "test-description", +} +if err := client.UpdatePeerConn(peerConnId, args); err != nil { + fmt.Println("update peer conn error: ", err) + return +} + +fmt.Printf("update peer conn %s success", peerConnId) +``` + +## 接受对等连接申请 + +使用以下代码可以接受对等连接的申请信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.AcceptPeerConnApply(peerConnId, clientToken); err != nil { + fmt.Println("accept peer conn error: ", err) + return +} + +fmt.Printf("accept peer conn %s success.", peerConnId) +``` + +> 注意: +> - 发起端发出的连接请求超时时间为7天,超时后发起端对等连接的状态为协商失败。 +> - 接收端拒绝后,发起端对等连接状态为协商失败。 + +## 拒绝对等连接申请 + +使用以下代码可以接受对等连接的申请信息。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.RejectPeerConnApply(peerConnId, clientToken); err != nil { + fmt.Println("reject peer conn error: ", err) + return +} + +fmt.Printf("reject peer conn %s success.", peerConnId) +``` + +## 释放对等连接 + +使用以下代码可以释放特定的对等连接。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +if err := client.DeletePeerConn(peerConnId, clientToken); err != nil { + fmt.Println("delete peer conn error: ", err) + return +} + +fmt.Printf("delete peer conn %s success", peerConnId) +``` + +> 注意: +> - 跨账号只有发起端可以释放。 +> - 预付费可用且未到期的对等连接不能释放。 +> - 预付费协商失败的可以释放。 + +## 对等连接带宽升降级 + +使用以下代码可以为指定的对等连接进行带宽升级操作。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.ResizePeerConnArgs{ + // 指定对等连接升降级的带宽 + NewBandwidthInMbps: 20, +} + +if err := client.ResizePeerConn(peerConnId, args); err != nil { + fmt.Println("resize peer conn error: ", err) + return +} + +fmt.Printf("resize peer conn %s success.", peerConnId) +``` + +> 注意: +> - 跨账号只有发起端才可以进行带宽的升降级操作。 +> - 预付费的对等连接只能进行带宽升级不能降级。 +> - 后付费的对等连接可以进行带宽的升级和降级。 + +## 对等连接续费 + +使用以下代码可以为对等连接进行续费操作,延长过期时间。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.RenewPeerConnArgs{ + // 指定对等连接的续费信息 + Billing: &vpc.Billing{ + Reservation: &vpc.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, +} + +if err := client.RenewPeerConn(peerConnId, args); err != nil { + fmt.Println("renew peer conn error: ", err) + return +} + +fmt.Printf("renew peer conn %s success.", peerConnId) +``` + +> 注意: +> - 后付费的对等连接不能续费。 +> - 跨账号续费操作只能由发起端来操作。 + +## 开启对等连接同步DNS + +使用以下代码可以开启对等连接同步DNS记录。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.PeerConnSyncDNSArgs{ + // 指定对等连接的角色,发起端"initiator" 接收端"acceptor" + Role: vpc.PEERCONN_ROLE_INITIATOR, +} + +if err := client.OpenPeerConnSyncDNS(peerConnId, args); err != nil { + fmt.Println("open peer conn sync dns error: ", err) + return +} + +fmt.Printf("open peer conn %s sync dns success.", peerConnId) +``` + +> 注意: +> - 对等连接的状态为可用的时候才能开启DNS。 +> - 对等连接的DNS状态为同步中或同步关闭中不可开启同步DNS。 + +## 关闭对等连接同步DNS + +使用以下代码可以关闭对等连接同步DNS记录。 +```go +//import "github.com/baidubce/bce-sdk-go/services/vpc" + +args := &vpc.PeerConnSyncDNSArgs{ + // 指定对等连接的角色,发起端"initiator" 接收端"acceptor" + Role: vpc.PEERCONN_ROLE_INITIATOR, +} + +if err := client.ClosePeerConnSyncDNS(peerConnId, args); err != nil { + fmt.Println("close peer conn sync dns error: ", err) + return +} + +fmt.Printf("close peer conn %s sync dns success.", peerConnId) +``` + +> 注意: +> - 对等连接的状态为可用的时候才能关闭DNS。 +> - 对等连接的DNS状态为同步中或同步关闭中不可关闭同步DNS。 + + +# 错误处理 + +GO语言以error类型标识错误,VPC支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | VPC服务返回的错误 + +用户使用SDK调用VPC相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// vpcClient 为已创建的VPC Client对象 +args := &vpc.ListVPCArgs{} +result, err := client.ListVPC(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向VPC发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当VPC服务端出现异常时,VPC服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[VPC错误信息格式](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +VPC GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +VPC GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the VPC go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the VPC go sdk") +``` + + +# 版本变更记录 +## v0.9.8 [2022-11-14] +- 路由支持:专线网关、对等连接、IPv6网关、弹性网卡、高可用虚拟IP类型 +## v0.9.7 [2022-11-14] +- NAT、对等连接创建、详情接口支持Tags +## v0.9.6 [2020-12-27] +- 增加vpc查询PrivateIpAddress信息接口 +## v0.9.5 [2019-09-24] + +首次发布: + + - 支持创建VPC、查询VPC列表、查询指定VPC、删除VPC、更新VPC接口; + - 支持创建子网、查询子网列表、查询指定子网、删除子网、更新子网接口; + - 支持查询路由表、创建路由规则、删除路由规则接口; + - 支持查询ACL、添加ACL规则、查询ACL规则、更新ACL规则、删除ACL规则接口; + - 支持创建NAT网关、查询NAT网关列表、查询NAT网关详情、更新NAT网关名称、绑定EIP、解绑EIP、释放NAT网关、NAT网关续费接口; + - 支持创建对等连接、查询对等连接列表、查看对等连接详情、更新对等连接本端接口名称和备注、接受对等连接申请、拒绝对等连接申请、释放对等连接、对等连接带宽升降级、对等连接续费、开启对等连接同步DNS、关闭对等连接同步DNS接口。 + \ No newline at end of file diff --git a/bce-sdk-go/doc/VPN.md b/bce-sdk-go/doc/VPN.md new file mode 100644 index 0000000..57917ec --- /dev/null +++ b/bce-sdk-go/doc/VPN.md @@ -0,0 +1,799 @@ +# VPN服务 + +# 概述 + +本文档主要介绍VPN GO SDK的使用。在使用本文档前,您需要先了解VPN的一些基本知识,并已开通了VPN服务。若您还不了解VPN,可以参考[产品描述](https://cloud.baidu.com/doc/VPC/s/sjwvytvh0)和[操作指南](https://cloud.baidu.com/doc/VPC/s/9jwvytzz8)。 + +# 初始化 + +## 确认Endpoint + +在确认您使用SDK时配置的Endpoint时,可先阅读开发人员指南中关于[VPN服务域名](https://cloud.baidu.com/doc/VPC/s/xjwvyuhpw)的部分,理解Endpoint相关的概念。百度云目前开放了多区域支持,请参考[区域选择说明](https://cloud.baidu.com/doc/Reference/s/2jwvz23xx/)。 + +目前支持“华北-北京”、“华南-广州”、“华东-苏州”、“香港”、“金融华中-武汉”和“华北-保定”六个区域。对应信息为: + +访问区域 | 对应Endpoint | 协议 +---|---|--- +BJ | bcc.bj.baidubce.com | HTTP and HTTPS +GZ | bcc.gz.baidubce.com | HTTP and HTTPS +SU | bcc.su.baidubce.com | HTTP and HTTPS +HKG| bcc.hkg.baidubce.com| HTTP and HTTPS +FWH| bcc.fwh.baidubce.com| HTTP and HTTPS +BD | bcc.bd.baidubce.com | HTTP and HTTPS + +## 获取密钥 + +要使用百度云VPN,您需要拥有一个有效的AK(Access Key ID)和SK(Secret Access Key)用来进行签名认证。AK/SK是由系统分配给用户的,均为字符串,用于标识用户,为访问VPN做签名验证。 + +可以通过如下步骤获得并了解您的AK/SK信息: + +[注册百度云账号](https://login.bce.baidu.com/reg.html?tpl=bceplat&from=portal) + +[创建AK/SK](https://console.bce.baidu.com/iam/?_=1513940574695#/iam/accesslist) + +## 新建VPN Client + +VPN Client是VPN服务的客户端,为开发者与VPN服务进行交互提供了一系列的方法。 + +### 使用AK/SK新建VPN Client + +通过AK/SK方式访问VPN,用户可以参考如下代码新建一个VPN Client: + +```go +import ( + "github.com/baidubce/bce-sdk-go/services/vpn" +) + +func main() { + // 用户的Access Key ID和Secret Access Key + ACCESS_KEY_ID, SECRET_ACCESS_KEY := , + + // 用户指定的Endpoint + ENDPOINT := + + // 初始化一个VPNClient + vpnClient, err := vpn.NewClient(AK, SK, ENDPOINT) +} +``` + +在上面代码中,`ACCESS_KEY_ID`对应控制台中的“Access Key ID”,`SECRET_ACCESS_KEY`对应控制台中的“Access Key Secret”,获取方式请参考《操作指南 [如何获取AKSK](https://cloud.baidu.com/doc/Reference/s/9jwvz2egb/)》。第三个参数`ENDPOINT`支持用户自己指定域名,如果设置为空字符串,会使用默认域名作为VPC的服务地址。 + +> **注意:**`ENDPOINT`参数需要用指定区域的域名来进行定义,如服务所在区域为北京,则为`bcc.bj.baidubce.com`。 + +### 使用STS创建VPN Client + +**申请STS token** + +VPN可以通过STS机制实现第三方的临时授权访问。STS(Security Token Service)是百度云提供的临时授权服务。通过STS,您可以为第三方用户颁发一个自定义时效和权限的访问凭证。第三方用户可以使用该访问凭证直接调用百度云的API或SDK访问百度云资源。 + +通过STS方式访问VPN,用户需要先通过STS的client申请一个认证字符串。 + +**用STS token新建VPN Client** + +申请好STS后,可将STS Token配置到VPN Client中,从而实现通过STS Token创建VPN Client。 + +**代码示例** + +GO SDK实现了STS服务的接口,用户可以参考如下完整代码,实现申请STS Token和创建VPN Client对象: + +```go +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/auth" //导入认证模块 + "github.com/baidubce/bce-sdk-go/services/vpn" //导入VPN服务模块 + "github.com/baidubce/bce-sdk-go/services/sts" //导入STS服务模块 +) + +func main() { + // 创建STS服务的Client对象,Endpoint使用默认值 + AK, SK := , + stsClient, err := sts.NewClient(AK, SK) + if err != nil { + fmt.Println("create sts client object :", err) + return + } + + // 获取临时认证token,有效期为60秒,ACL为空 + stsObj, err := stsClient.GetSessionToken(60, "") + if err != nil { + fmt.Println("get session token failed:", err) + return + } + fmt.Println("GetSessionToken result:") + fmt.Println(" accessKeyId:", stsObj.AccessKeyId) + fmt.Println(" secretAccessKey:", stsObj.SecretAccessKey) + fmt.Println(" sessionToken:", stsObj.SessionToken) + fmt.Println(" createTime:", stsObj.CreateTime) + fmt.Println(" expiration:", stsObj.Expiration) + fmt.Println(" userId:", stsObj.UserId) + + // 使用申请的临时STS创建VPN服务的Client对象,Endpoint使用默认值 + vpnClient, err := vpn.NewClient(stsObj.AccessKeyId, stsObj.SecretAccessKey, "bcc.bj.baidubce.com") + if err != nil { + fmt.Println("create vpn client failed:", err) + return + } + stsCredential, err := auth.NewSessionBceCredentials( + stsObj.AccessKeyId, + stsObj.SecretAccessKey, + stsObj.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return + } + vpnClient.Config.Credentials = stsCredential +} +``` + +> 注意: +> 目前使用STS配置VPN Client时,无论对应VPN服务的Endpoint在哪里,STS的Endpoint都需配置为http://sts.bj.baidubce.com。上述代码中创建STS对象时使用此默认值。 + +# 配置HTTPS协议访问VPN + +VPN支持HTTPS传输协议,您可以通过在创建VPN Client对象时指定的Endpoint中指明HTTPS的方式,在VPN GO SDK中使用HTTPS访问VPN服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +ENDPOINT := "https://bcc.bj.baidubce.com" //指明使用HTTPS协议 +AK, SK := , +vpnClient, _ := vpn.NewClient(AK, SK, ENDPOINT) +``` + +## 配置VPN Client + +如果用户需要配置VPN Client的一些细节的参数,可以在创建VPN Client对象之后,使用该对象的导出字段`Config`进行自定义配置,可以为客户端配置代理,最大连接数等参数。 + +### 使用代理 + +下面一段代码可以让客户端使用代理访问VPN服务: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +//创建VPN Client对象 +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpn.NewClient(AK, SK, ENDPOINT) + +//代理使用本地的8080端口 +client.Config.ProxyUrl = "127.0.0.1:8080" +``` + +### 设置网络参数 + +用户可以通过如下的示例代码进行网络参数的设置: + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpn.NewClient(AK, SK, ENDPOINT) + +// 配置不进行重试,默认为Back Off重试 +client.Config.Retry = bce.NewNoRetryPolicy() + +// 配置连接超时时间为30秒 +client.Config.ConnectionTimeoutInMillis = 30 * 1000 +``` + +### 配置生成签名字符串选项 + +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +AK, SK := , +ENDPOINT := "bcc.bj.baidubce.com" +client, _ := vpn.NewClient(AK, SK, ENDPOINT) + +// 配置签名使用的HTTP请求头为`Host` +headersToSign := map[string]struct{}{"Host": struct{}{}} +client.Config.SignOption.HeadersToSign = HeadersToSign + +// 配置签名的有效期为30秒 +client.Config.SignOption.ExpireSeconds = 30 +``` + +**参数说明** + +用户使用GO SDK访问VPN时,创建的VPN Client对象的`Config`字段支持的所有参数如下表所示: + +配置项名称 | 类型 | 含义 +-----------|---------|-------- +Endpoint | string | 请求服务的域名 +ProxyUrl | string | 客户端请求的代理地址 +Region | string | 请求资源的区域 +UserAgent | string | 用户名称,HTTP请求的User-Agent头 +Credentials| \*auth.BceCredentials | 请求的鉴权对象,分为普通AK/SK与STS两种 +SignOption | \*auth.SignOptions | 认证字符串签名选项 +Retry | RetryPolicy | 连接重试策略 +ConnectionTimeoutInMillis| int | 连接超时时间,单位毫秒,默认20分钟 + +说明: + + 1. `Credentials`字段使用`auth.NewBceCredentials`与`auth.NewSessionBceCredentials`函数创建,默认使用前者,后者为使用STS鉴权时使用,详见“使用STS创建VPN Client”小节。 + 2. `SignOption`字段为生成签名字符串时的选项,详见下表说明: + +名称 | 类型 | 含义 +--------------|-------|----------- +HeadersToSign |map[string]struct{} | 生成签名字符串时使用的HTTP头 +Timestamp | int64 | 生成的签名字符串中使用的时间戳,默认使用请求发送时的值 +ExpireSeconds | int | 签名字符串的有效期 + + 其中,HeadersToSign默认为`Host`,`Content-Type`,`Content-Length`,`Content-MD5`;TimeStamp一般为零值,表示使用调用生成认证字符串时的时间戳,用户一般不应该明确指定该字段的值;ExpireSeconds默认为1800秒即30分钟。 + 3. `Retry`字段指定重试策略,目前支持两种:`NoRetryPolicy`和`BackOffRetryPolicy`。默认使用后者,该重试策略是指定最大重试次数、最长重试时间和重试基数,按照重试基数乘以2的指数级增长的方式进行重试,直到达到最大重试测试或者最长重试时间为止。 + + + +## 创建VPN + +使用以下代码可以申请一个VPN。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &vpn.CreateVpnGatewayArgs{ + VpnName: "TestSDK-VPN", + Description: "vpn test", + VpcId: "vpcId", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_PREPAID, + Reservation: &Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, + ClientToken: getClientToken(), +} +result, err := client.CreateVpnGateway(args) +if err != nil { + fmt.Printf("create vpn error: %+v\n", err) + return +} + +fmt.Println("create vpn success, vpn: ", result.VpnId) +``` + + +## 查询VPN 列表 + +使用以下代码可以查询VPN列表。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &vpn.ListVpnGatewayArgs{ + MaxKeys: 1000, + VpcId: "vpcId", + } + result, err := client.ListVpnGateway(args) + if err != nil { + fmt.Printf("list vpn error: %+v\n", err) + return + } + // 返回标记查询的起始位置 + fmt.Println("vpn list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("vpn list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("vpn list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("vpn list maxKeys: ", result.MaxKeys) + // 获取vpn的列表信息 + for _, e := range res.Vpns { + fmt.Println("vpn id: ", e.VpnId) + fmt.Println("vpn eip: ", e.Eip) + fmt.Println("vpn status: ", e.Status) + fmt.Println("vpn vpcId: ", e.VpcId) + fmt.Println("vpn description: ", e.Description) + fmt.Println("vpn expiredTime: ", e.ExpiredTime) + fmt.Println("vpn paymentTiming: ", e.ProductType) + fmt.Println("vpn vpnConnNum: ", e.VpnConnNum) + fmt.Println("vpn bandwidthInMbps: ", e.BandwidthInMbps) + fmt.Println("vpn vpnName: ", e.Name) + fmt.Println("vpn expireTime: ", e.ExpiredTime) + } +``` + +## 查询VPN详情 + +使用以下代码可以实现查询VPN的详情信息。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" +result,err:=vpn.GetVpnGatewayDetail("vpnId") + + if err != nil { + fmt.Printf("get vpn detail error: %+v\n", err) + return + } + fmt.Println("vpn id: ", result.VpnId) + fmt.Println("vpn eip: ", result.Eip) + fmt.Println("vpn status: ", result.Status) + fmt.Println("vpn vpcId: ", result.VpcId) + fmt.Println("vpn description: ", result.Description) + fmt.Println("vpn expiredTime: ", result.ExpiredTime) + fmt.Println("vpn paymentTiming: ", result.ProductType) + fmt.Println("vpn vpnConnNum: ", result.VpnConnNum) + fmt.Println("vpn bandwidthInMbps: ", result.BandwidthInMbps) + fmt.Println("vpn vpnName: ", result.Name) + fmt.Println("vpn expireTime: ", result.ExpiredTime) +} +``` + +## 更新VPN网关 + +使用以下代码可以实现VPN网关的更新。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" +args := &vpn.UpdateVpnGatewayArgs{ + ClientToken: getClientToken(), + Name: "vpnTest", + } + err := client.UpdateVpnGateway("vpnId", args) +if err != nil { + fmt.Printf("update vpn error: %+v\n", err) + return +} +fmt.Printf("update vpn success\n") +``` + + +## 释放VPN + +使用以下代码可以释放指定的VPN。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +err = client.DeleteVpn(vpnId, clientToken) +if err != nil { + fmt.Printf("delete vpn error: %+v\n", err) + return +} + +fmt.Printf("delete vpn success\n") +``` + +> 注意: +> - 释放指定VPN,被释放的VPN无法找回 +> - 预付费购买的VPN如需提前释放,请通过工单进行 + +## 绑定EIP + +使用以下代码可以将EIP绑定到VPN。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &vpn.BindEipArgs{ + ClientToken: ClientToken(), + Eip: Eip, + } +err := client.BindEip(vpnId, args) +if err != nil { + fmt.Printf("bind eip error: %+v\n", err) + return +} +``` + +> 注意: +> - 绑定EIP是一个异步过程,可以通过查询VPN的状态判断绑定是否成功 + + +## 解绑EIP + +使用以下代码可以将VPN的EIP进行解绑。 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" +if err := client.UnBindEip(vpnId, clientToken); err != nil { + fmt.Printf("unbind eip error: %+v\n", err) + return +} + +fmt.Printf("unbind eip success.") +``` + +> 注意: 解绑EIP是一个异步过程,可以通过查询VPN的状态判断绑定是否成功 + +## VPN网关续费 +使用以下代码可以延长VPN的到期时间 + ```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" +args := &vpn.RenewVpnGatewayArgs{ + ClientToken: ClientToken, + Billing: &Billing{ + Reservation: &Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, +} +if err := client.RenewVpnGateway(vpnId, args); err != nil { + fmt.Printf(" renew vpn error: %+v\n", err) + return +} + +fmt.Printf("renew vpn success.") +``` + +> 注意: +> - 仅预付费资源可以进行续费操作。 + +## 创建VPN隧道 +使用以下代码可以为指定的VPN创建隧道 + ```go + // import "github.com/baidubce/bce-sdk-go/services/vpn" + args := &vpn.CreateVpnConnArgs{ + VpnId: VpnId, + VpnConnName: VpnConnName, + LocalIp: LocalIp, + SecretKey: SecretKey, + LocalSubnets: []string{"subnet"}, + RemoteIp: RemoteIp, + RemoteSubnets: []string{"RemoteSubnet"}, + CreateIkeConfig: &CreateIkeConfig{ + IkeVersion: "v1", + IkeMode: "main", + IkeEncAlg: "aes", + IkeAuthAlg: "sha1", + IkePfs: "group2", + IkeLifeTime: 25500, + }, + CreateIpsecConfig: &CreateIpsecConfig{ + IpsecEncAlg: "aes", + IpsecAuthAlg: "sha1", + IpsecPfs: "group2", + IpsecLifetime: 25500, + }, + } + res, err := client.CreateVpnConn(args) + if err != nil { + fmt.Printf(" create vpnconn error: %+v\n", err) + return + } + fmt.Printf(" create vpnconn success,connId is: %+v",res.VpnConnId) + ``` +## 查询VPN隧道 +使用一下代码可以查询指定VPN的隧道信息 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + result,err:=vpn.ListVpnConn("vpnId") + if err != nil { + fmt.Printf("get vpn detail error: %+v\n", err) + return + } + for _, e := range result.VpnConns { + fmt.Println("vpnconn Id: ", e.VpnConnId) + fmt.Println("vpnconn LocalIp: ",e.LocalIp) + fmt.Println("vpnconn Description: ",e.Description) + fmt.Println("vpnconn CreatedTime: ",e.CreatedTime) + fmt.Println("vpnconn HealthStatus: ",e.HealthStatus) + fmt.Println("vpnconn LocalIp: ",e.LocalIp) + fmt.Println("vpnconn LocalSubnets: ",e.LocalSubnets) + fmt.Println("vpnconn RemoteSubnets: ",e.RemoteSubnets) + fmt.Println("vpnconn IkeConfig: ",e.IkeConfig) + fmt.Println("vpnconn IpsecConfig: ",e.IpsecConfig) + } +``` +## 更新VPN隧道 +使用以下代码可以修改指定的VPN隧道 + ```go + // import "github.com/baidubce/bce-sdk-go/services/vpn" + args := &vpn.UpdateVpnConnArgs{ + vpnConnId: vpnConnId, + updateVpnconn: &CreateVpnConnArgs{ + VpnId: VpnId, + VpnConnName: VpnConnName, + LocalIp: LocalIp, + SecretKey: SecretKey, + LocalSubnets: []string{"LocalSubnets"}, + RemoteIp: RemoteIp, + RemoteSubnets: []string{"RemoteSubnets"}, + CreateIkeConfig: &CreateIkeConfig{ + IkeVersion: "v1", + IkeMode: "main", + IkeEncAlg: "aes", + IkeAuthAlg: "sha1", + IkePfs: "group2", + IkeLifeTime: 25500, + }, + CreateIpsecConfig: &CreateIpsecConfig{ + IpsecEncAlg: "aes", + IpsecAuthAlg: "sha1", + IpsecPfs: "group2", + IpsecLifetime: 25500, + }, + }, + } + err := client.UpdateVpnConn(args) + if err != nil { + fmt.Printf(" uodate vpnconn error: %+v\n", err) + return + } + fmt.Printf(" update vpnconn success,connId is: %+v",res.VpnConnId) + ``` +## 删除VPN隧道 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +err = client.DeleteVpnConn(vpnconnId, clientToken) +if err != nil { + fmt.Printf("delete vpnconn error: %+v\n", err) + return +} + +fmt.Printf("delete vpnconn success\n") +``` +## 创建SSL-VPN服务端 +使用一下代码可以创建指定VPN的SSL-VPN服务端 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &vpn.CreateSslVpnServerArgs{ + ClientToken: ClientToken(), + VpnId: VpnId, + SslVpnServerName: SslVpnServerName, + InterfaceType: InterfaceType, + LocalSubnets: []string{"subnet"}, + RemoteSubnet: RemoteSubnet, + ClientDns: ClientDns, +} +res, err := client.CreateSslVpnServer(args) +ExpectEqual(t.Errorf, nil, err) +if err1 != nil { + fmt.Printf("create ssl-vpn server error: %+v\n", err) + return +} +fmt.Printf("create ssl-vpn server success,sslVpnServerId is: %+v",res.SslVpnServerId) +``` +> 一个vpn中已经存在的ssl-vpn server只能有一个,如果某个vpn已经存在ssl-vpn server,则再次创建会失败,只能先删除再创建 +## 查询SSL-VPN服务端 +使用一下代码可以查询指定VPN的SSL-VPN服务端 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +result,err := client.GetSslVpnServer(vpnId,clientToken) +if err != nil { + fmt.Printf("get ssl-vpn server error: %+v\n", err) + return +} + +// 查询得到vpn的id +fmt.Println("vpn id: ", result.VpnId) +// 查询得到SSL-VPN服务端的id +fmt.Println("SSL-VPN server id: ", result.SslVpnServerId) +// 查询得到SSL-VPN服务端的名称 +fmt.Println("SSL-VPN server name: ", result.SslVpnServerName) +// 查询得到SSL-VPN服务端的接口类型 +fmt.Println("SSL-VPN server interface type: ", result.InterfaceType) +// 查询得到SSL-VPN服务端状态 +fmt.Println("SSL-VPN server status: ", result.Status) +// 查询得到本端网络CIDR列表 +fmt.Println("SSL-VPN server local subnets: ", result.LocalSubnets) +// 查询得到客户端网络CIDR +fmt.Println("SSL-VPN server remote subnet: ", result.RemoteSubnet) +// 查询得到客户端的DNS地址 +fmt.Println("SSL-VPN server client dns: ", result.ClientDns) +// 查询得到SSL-VPN最大客户端连接数 +fmt.Println("SSL-VPN server max connection: ", result.MaxConnection) +``` +## 更新SSL-VPN服务端 +使用以下代码可以修改指定VPN的SSL-VPN服务端 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &vpn.UpdateSslVpnServerArgs{ + ClientToken: ClientToken(), + VpnId: VpnId, + SslVpnServerId: SslVpnServerId, + RemoteSubnets: []string{"subnet"}, + UpdateSslVpnServer: &UpdateSslVpnServer{ + SslVpnServerName: SslVpnServerName, + LocalSubnets: LocalSubnets, + RemoteSubnet: RemoteSubnet, + ClientDns: ClientDns, + }, +} +err := client.UpdateSslVpnServer(args) +if err != nil { + fmt.Printf("update ssl-vpn server error: %+v\n", err) + return +} +fmt.Printf("update ssl-vpn server success") +``` +## 删除SSL-VPN服务端 +使用以下代码可以删除指定VPN的SSL-VPN服务端 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +err := client.DeleteSslVpnServer(vpnId, sslVpnServerId, clientToken) +if err != nil { + fmt.Printf("delete ssl-vpn server error: %+v\n", err) + return +} + +fmt.Printf("delete ssl-vpn server success\n") +``` +> 删除SSL-VPN服务端需要保证这个server里面的用户全部删除之后,server才能删除成功 +## 批量创建SSL-VPN用户 +使用以下代码可以批量创建指定VPN的SSL-VPN用户 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &BatchCreateSslVpnUserArgs{ + ClientToken: ClientToken(), + VpnId: VpnId, + SslVpnUsers: []SslVpnUser{ + SslVpnUser{ + UserName: UserName, + Password: Password, + Description: Description, + }, + SslVpnUser{ + UserName: UserName + Password: UserName, + }, + }, +} +res, err := client.BatchCreateSslVpnUser(args) +ExpectEqual(t.Errorf, nil, err) +if err1 != nil { + fmt.Printf("batch create ssl-vpn user error: %+v\n", err) + return +} +fmt.Printf("batch create ssl-vpn user success,SslVpnUserIds are: %+v",res.SslVpnUserIds) +``` +## 查询SSL-VPN用户 +使用一下代码可以查询指定VPN的SSL-VPN用户列表 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &ListSslVpnUserArgs{ + MaxKeys: MaxKeys, + VpnId: VpnId, +} +result,err := client.ListSslVpnUser(args) +if err != nil { + fmt.Printf("get ssl-vpn user error: %+v\n", err) + return +} + +// 返回标记查询的起始位置 +fmt.Println("ssl-vpn user list marker: ", result.Marker) +// true表示后面还有数据,false表示已经是最后一页 +fmt.Println("ssl-vpn user list isTruncated: ", result.IsTruncated) +// 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 +fmt.Println("ssl-vpn user list nextMarker: ", result.NextMarker) +// 每页包含的最大数量 +fmt.Println("ssl-vpn user list maxKeys: ", result.MaxKeys) +// 获取ssl-vpn user的具体信息 +for _, v := range result.SslVpnUsers { + fmt.Println("ssl-vpn user id: ", v.UserId) + fmt.Println("ssl-vpn username: ", v.UserName) + fmt.Println("ssl-vpn user description: ", v.Description) +} +``` +## 更新SSL-VPN用户 +使用以下代码可以修改指定VPN的SSL-VPN用户信息 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +args := &UpdateSslVpnUserArgs{ + ClientToken: ClientToken(), + VpnId: VpnId, + UserId: UserId, + SslVpnUser: &UpdateSslVpnUser{ + Password: Password, + Description: Description, + }, +} +err := client.UpdateSslVpnUser(args) +if err != nil { + fmt.Printf("update ssl-vpn user error: %+v\n", err) + return +} +fmt.Printf("update ssl-vpn user success") +``` +> SSL-VPN用户的用户名不能更改,这个用户名一般是唯一不变的账户名称 +## 删除SSL-VPN用户 +```go +// import "github.com/baidubce/bce-sdk-go/services/vpn" + +err := client.DeleteSslVpnUser(vpnId, userId, clientToken) +if err != nil { + fmt.Printf("delete ssl-vpn user error: %+v\n", err) + return +} + +fmt.Printf("delete ssl-vpn user success\n") +``` +# 错误处理 + +GO语言以error类型标识错误,VPN支持两种错误见下表: + +错误类型 | 说明 +----------------|------------------- +BceClientError | 用户操作产生的错误 +BceServiceError | VPN服务返回的错误 + +用户使用SDK调用VPN相关接口,除了返回所需的结果之外还会返回错误,用户可以获取相关错误进行处理。实例如下: + +``` +// vpnClient 为已创建的VPN Client对象 +args := &vpn.ListVpnGatewayArgs{} +result, err := client.ListVpnGateway(args) +if err != nil { + switch realErr := err.(type) { + case *bce.BceClientError: + fmt.Println("client occurs error:", realErr.Error()) + case *bce.BceServiceError: + fmt.Println("service occurs error:", realErr.Error()) + default: + fmt.Println("unknown error:", err) + } +} +``` + +## 客户端异常 + +客户端异常表示客户端尝试向VPN发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用时,则会返回BceClientError。 + +## 服务端异常 + +当VPN服务端出现异常时,VPN服务端会返回给用户相应的错误信息,以便定位问题。常见服务端异常可参见[VPN错误码](https://cloud.baidu.com/doc/VPC/s/sjwvyuhe7) + +## SDK日志 + +VPN GO SDK支持六个级别、三种输出(标准输出、标准错误、文件)、基本格式设置的日志模块,导入路径为`github.com/baidubce/bce-sdk-go/util/log`。输出为文件时支持设置五种日志滚动方式(不滚动、按天、按小时、按分钟、按大小),此时还需设置输出日志文件的目录。 + +### 默认日志 + +VPN GO SDK自身使用包级别的全局日志对象,该对象默认情况下不记录日志,如果需要输出SDK相关日志需要用户自定指定输出方式和级别,详见如下示例: + +``` +// import "github.com/baidubce/bce-sdk-go/util/log" + +// 指定输出到标准错误,输出INFO及以上级别 +log.SetLogHandler(log.STDERR) +log.SetLogLevel(log.INFO) + +// 指定输出到标准错误和文件,DEBUG及以上级别,以1GB文件大小进行滚动 +log.SetLogHandler(log.STDERR | log.FILE) +log.SetLogDir("/tmp/gosdk-log") +log.SetRotateType(log.ROTATE_SIZE) +log.SetRotateSize(1 << 30) + +// 输出到标准输出,仅输出级别和日志消息 +log.SetLogHandler(log.STDOUT) +log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_MSG}) +``` + +说明: + 1. 日志默认输出级别为`DEBUG` + 2. 如果设置为输出到文件,默认日志输出目录为`/tmp`,默认按小时滚动 + 3. 如果设置为输出到文件且按大小滚动,默认滚动大小为1GB + 4. 默认的日志输出格式为:`FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG` + +### 项目使用 + +该日志模块无任何外部依赖,用户使用GO SDK开发项目,可以直接引用该日志模块自行在项目中使用,用户可以继续使用GO SDK使用的包级别的日志对象,也可创建新的日志对象,详见如下示例: + +``` +// 直接使用包级别全局日志对象(会和GO SDK自身日志一并输出) +log.SetLogHandler(log.STDERR) +log.Debugf("%s", "logging message using the log package in the VPN go sdk") + +// 创建新的日志对象(依据自定义设置输出日志,与GO SDK日志输出分离) +myLogger := log.NewLogger() +myLogger.SetLogHandler(log.FILE) +myLogger.SetLogDir("/home/log") +myLogger.SetRotateType(log.ROTATE_SIZE) +myLogger.Info("this is my own logger from the VPN go sdk") +``` + + +# 版本变更记录 + +## v0.9.5 [2020-05-26] + +首次发布: + + - 支持创建VPN、查询VPN列表、查询VPN详情、更新VPN、释放VPN、绑定/解绑EIP、创建VPN隧道、查询VPN隧道、更新VPN隧道、删除VPN隧道。 \ No newline at end of file diff --git a/bce-sdk-go/go.mod b/bce-sdk-go/go.mod new file mode 100644 index 0000000..6150974 --- /dev/null +++ b/bce-sdk-go/go.mod @@ -0,0 +1,3 @@ +module github.com/baidubce/bce-sdk-go + +go 1.11 diff --git a/bce-sdk-go/http/client.go b/bce-sdk-go/http/client.go new file mode 100644 index 0000000..e111d61 --- /dev/null +++ b/bce-sdk-go/http/client.go @@ -0,0 +1,175 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the execute function to send http request and get response + +// Package http defines the structure of request and response which used to access the BCE services +// as well as the http constant headers and and methods. And finally implement the `Execute` funct- +// ion to do the work. +package http + +import ( + "net" + "net/http" + "net/url" + "sync" + "time" +) + +const ( + defaultMaxIdleConnsPerHost = 500 + defaultResponseHeaderTimeout = 60 * time.Second + defaultDialTimeout = 30 * time.Second + defaultSmallInterval = 600 * time.Second + defaultLargeInterval = 1200 * time.Second +) + +// The httpClient is the global variable to send the request and get response +// for reuse and the Client provided by the Go standard library is thread safe. +var ( + httpClient *http.Client + transport *http.Transport +) + +type timeoutConn struct { + conn net.Conn + smallInterval time.Duration + largeInterval time.Duration +} + +func (c *timeoutConn) Read(b []byte) (n int, err error) { + c.SetReadDeadline(time.Now().Add(c.smallInterval)) + n, err = c.conn.Read(b) + c.SetReadDeadline(time.Now().Add(c.largeInterval)) + return n, err +} +func (c *timeoutConn) Write(b []byte) (n int, err error) { + c.SetWriteDeadline(time.Now().Add(c.smallInterval)) + n, err = c.conn.Write(b) + c.SetWriteDeadline(time.Now().Add(c.largeInterval)) + return n, err +} +func (c *timeoutConn) Close() error { return c.conn.Close() } +func (c *timeoutConn) LocalAddr() net.Addr { return c.conn.LocalAddr() } +func (c *timeoutConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } +func (c *timeoutConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) } +func (c *timeoutConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } +func (c *timeoutConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } + +type ClientConfig struct { + RedirectDisabled bool +} + +var customizeInit sync.Once + +func InitClient(config ClientConfig) { + customizeInit.Do(func() { + httpClient = &http.Client{} + transport = &http.Transport{ + MaxIdleConnsPerHost: defaultMaxIdleConnsPerHost, + ResponseHeaderTimeout: defaultResponseHeaderTimeout, + Dial: func(network, address string) (net.Conn, error) { + conn, err := net.DialTimeout(network, address, defaultDialTimeout) + if err != nil { + return nil, err + } + tc := &timeoutConn{conn, defaultSmallInterval, defaultLargeInterval} + tc.SetReadDeadline(time.Now().Add(defaultLargeInterval)) + return tc, nil + }, + } + httpClient.Transport = transport + if config.RedirectDisabled { + httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + }) +} + +// Execute - do the http requset and get the response +// +// PARAMS: +// - request: the http request instance to be sent +// +// RETURNS: +// - response: the http response returned from the server +// - error: nil if ok otherwise the specific error +func Execute(request *Request) (*Response, error) { + // Build the request object for the current requesting + httpRequest := &http.Request{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + } + + // Set the connection timeout for current request + httpClient.Timeout = time.Duration(request.Timeout()) * time.Second + + // Set the request method + httpRequest.Method = request.Method() + + // Set the request url + internalUrl := &url.URL{ + Scheme: request.Protocol(), + Host: request.Host(), + Path: request.Uri(), + RawQuery: request.QueryString()} + httpRequest.URL = internalUrl + + // Set the request headers + internalHeader := make(http.Header) + for k, v := range request.Headers() { + val := make([]string, 0, 1) + val = append(val, v) + internalHeader[k] = val + } + httpRequest.Header = internalHeader + + if request.Body() != nil { + if request.Length() > 0 { + httpRequest.ContentLength = request.Length() + httpRequest.Body = request.Body() + } else if request.Length() < 0 { + // if set body and ContentLength <= 0, will be chunked + httpRequest.Body = request.Body() + } // else {} body == nil and ContentLength == 0 + } + + // Set the proxy setting if needed + if len(request.ProxyUrl()) != 0 { + transport.Proxy = func(_ *http.Request) (*url.URL, error) { + return url.Parse(request.ProxyUrl()) + } + } + + // Perform the http request and get response + // It needs to explicitly close the keep-alive connections when error occurs for the request + // that may continue sending request's data subsequently. + start := time.Now() + + httpResponse, err := httpClient.Do(httpRequest) + + end := time.Now() + if err != nil { + transport.CloseIdleConnections() + return nil, err + } + if httpResponse.StatusCode >= 400 && + (httpRequest.Method == PUT || httpRequest.Method == POST) { + transport.CloseIdleConnections() + } + response := &Response{httpResponse, end.Sub(start)} + return response, nil +} diff --git a/bce-sdk-go/http/constants.go b/bce-sdk-go/http/constants.go new file mode 100644 index 0000000..404ee74 --- /dev/null +++ b/bce-sdk-go/http/constants.go @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// constants.go - defines constants of the BCE http package including headers and methods + +package http + +// Constants of the supported HTTP methods for BCE +const ( + GET = "GET" + PUT = "PUT" + POST = "POST" + DELETE = "DELETE" + HEAD = "HEAD" + OPTIONS = "OPTIONS" + PATCH = "PATCH" +) + +// Constants of the HTTP headers for BCE +const ( + // Standard HTTP Headers + AUTHORIZATION = "Authorization" + CACHE_CONTROL = "Cache-Control" + CONTENT_DISPOSITION = "Content-Disposition" + CONTENT_ENCODING = "Content-Encoding" + CONTENT_LANGUAGE = "Content-Language" + CONTENT_LENGTH = "Content-Length" + CONTENT_MD5 = "Content-Md5" + CONTENT_RANGE = "Content-Range" + CONTENT_TYPE = "Content-Type" + DATE = "Date" + ETAG = "Etag" + EXPIRES = "Expires" + HOST = "Host" + LAST_MODIFIED = "Last-Modified" + LOCATION = "Location" + RANGE = "Range" + SERVER = "Server" + TRANSFER_ENCODING = "Transfer-Encoding" + USER_AGENT = "User-Agent" + + // BCE Common HTTP Headers + BCE_PREFIX = "x-bce-" + BCE_ACL = "x-bce-acl" + BCE_GRANT_READ = "x-bce-grant-read" + BCE_GRANT_FULL_CONTROL = "x-bce-grant-full-control" + BCE_CONTENT_SHA256 = "x-bce-content-sha256" + BCE_CONTENT_CRC32 = "x-bce-content-crc32" + BCE_REQUEST_ID = "x-bce-request-id" + BCE_USER_METADATA_PREFIX = "x-bce-meta-" + BCE_SECURITY_TOKEN = "x-bce-security-token" + BCE_DATE = "x-bce-date" + + // BOS HTTP Headers + BCE_COPY_METADATA_DIRECTIVE = "x-bce-metadata-directive" + BCE_COPY_SOURCE = "x-bce-copy-source" + BCE_COPY_SOURCE_IF_MATCH = "x-bce-copy-source-if-match" + BCE_COPY_SOURCE_IF_MODIFIED_SINCE = "x-bce-copy-source-if-modified-since" + BCE_COPY_SOURCE_IF_NONE_MATCH = "x-bce-copy-source-if-none-match" + BCE_COPY_SOURCE_IF_UNMODIFIED_SINCE = "x-bce-copy-source-if-unmodified-since" + BCE_COPY_SOURCE_RANGE = "x-bce-copy-source-range" + BCE_DEBUG_ID = "x-bce-debug-id" + BCE_OBJECT_TYPE = "x-bce-object-type" + BCE_NEXT_APPEND_OFFSET = "x-bce-next-append-offset" + BCE_STORAGE_CLASS = "x-bce-storage-class" + BCE_PROCESS = "x-bce-process" + BCE_RESTORE_TIER = "x-bce-restore-tier" + BCE_RESTORE_DAYS = "x-bce-restore-days" + BCE_RESTORE = "x-bce-restore" + BCE_FORBID_OVERWRITE = "x-bce-forbid-overwrite" + BCE_SYMLINK_TARGET = "x-bce-symlink-target" + BCE_SYMLINK_BUCKET = "x-bce-symlink-bucket" + BCE_TRAFFIC_LIMIT = "x-bce-traffic-limit" + BCE_BUCKET_TYPE = "x-bce-bucket-type" +) diff --git a/bce-sdk-go/http/request.go b/bce-sdk-go/http/request.go new file mode 100644 index 0000000..964c570 --- /dev/null +++ b/bce-sdk-go/http/request.go @@ -0,0 +1,225 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// request.go - the custom HTTP request for BCE + +package http + +import ( + "fmt" + "io" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/util" +) + +// Reauest stands for the general http request structure to make request to the BCE services. +type Request struct { + protocol string + host string + port int + method string + uri string + proxyUrl string + timeout int + headers map[string]string + params map[string]string + + // Optional body and length fields to set the body stream and content length + body io.ReadCloser + length int64 +} + +func (r *Request) Protocol() string { + return r.protocol +} + +func (r *Request) SetProtocol(protocol string) { + r.protocol = protocol +} + +func (r *Request) Endpoint() string { + if r.host == "" { + return "" + } + return r.protocol + "://" + r.host +} + +func (r *Request) SetEndpoint(endpoint string) { + pos := strings.Index(endpoint, "://") + rest := endpoint + if pos != -1 { + r.protocol = endpoint[0:pos] + rest = endpoint[pos+3:] + } else { + r.protocol = "http" + } + + r.SetHost(rest) +} + +func (r *Request) Host() string { + return r.host +} + +func (r *Request) SetHost(host string) { + r.host = host + pos := strings.Index(host, ":") + if pos != -1 { + p, e := strconv.Atoi(host[pos+1:]) + if e == nil { + r.port = p + } + } + + if r.port == 0 { + if r.protocol == "http" { + r.port = 80 + } else if r.protocol == "https" { + r.port = 443 + } + } +} + +func (r *Request) Port() int { + return r.port +} + +func (r *Request) SetPort(port int) { + // Port can be set by the endpoint or host, this method is rarely used. + r.port = port +} + +func (r *Request) Headers() map[string]string { + return r.headers +} + +func (r *Request) SetHeaders(headers map[string]string) { + r.headers = headers +} + +func (r *Request) Header(key string) string { + if v, ok := r.headers[key]; ok { + return v + } + return "" +} + +func (r *Request) SetHeader(key, value string) { + if r.headers == nil { + r.headers = make(map[string]string) + } + r.headers[key] = value +} + +func (r *Request) Params() map[string]string { + return r.params +} + +func (r *Request) SetParams(params map[string]string) { + r.params = params +} + +func (r *Request) Param(key string) string { + if v, ok := r.params[key]; ok { + return v + } + return "" +} + +func (r *Request) SetParam(key, value string) { + if r.params == nil { + r.params = make(map[string]string) + } + r.params[key] = value +} + +func (r *Request) QueryString() string { + buf := make([]string, 0, len(r.params)) + for k, v := range r.params { + if len(v) == 0 { + buf = append(buf, util.UriEncode(k, true)) + } else { + buf = append(buf, util.UriEncode(k, true)+"="+util.UriEncode(v, true)) + } + } + return strings.Join(buf, "&") +} + +func (r *Request) Method() string { + return r.method +} + +func (r *Request) SetMethod(method string) { + r.method = method +} + +func (r *Request) Uri() string { + return r.uri +} + +func (r *Request) SetUri(uri string) { + r.uri = uri +} + +func (r *Request) ProxyUrl() string { + return r.proxyUrl +} + +func (r *Request) SetProxyUrl(url string) { + r.proxyUrl = url +} + +func (r *Request) Timeout() int { + return r.timeout +} + +func (r *Request) SetTimeout(timeout int) { + r.timeout = timeout +} + +func (r *Request) Body() io.ReadCloser { + return r.body +} + +func (r *Request) SetBody(stream io.ReadCloser) { + r.body = stream +} + +func (r *Request) Length() int64 { + return r.length +} + +func (r *Request) SetLength(l int64) { + r.length = l +} + +func (r *Request) GenerateUrl(addPort bool) string { + if addPort { + return fmt.Sprintf("%s://%s:%d%s?%s", + r.protocol, r.host, r.port, r.uri, r.QueryString()) + } else { + return fmt.Sprintf("%s://%s%s?%s", r.protocol, r.host, r.uri, r.QueryString()) + } +} + +func (r *Request) String() string { + header := make([]string, 0, len(r.headers)) + for k, v := range r.headers { + header = append(header, "\t"+k+"="+v) + } + return fmt.Sprintf("\t%s %s\n%v", + r.method, r.GenerateUrl(false), strings.Join(header, "\n")) +} diff --git a/bce-sdk-go/http/response.go b/bce-sdk-go/http/response.go new file mode 100644 index 0000000..98632fc --- /dev/null +++ b/bce-sdk-go/http/response.go @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// response.go - the custom HTTP response for BCE + +package http + +import ( + "io" + "net/http" + "time" +) + +// Response defines the general http response structure for accessing the BCE services. +type Response struct { + httpResponse *http.Response // the standard libaray http.Response object + elapsedTime time.Duration // elapsed time just from sending request to receiving response +} + +func (r *Response) StatusText() string { + return r.httpResponse.Status +} + +func (r *Response) StatusCode() int { + return r.httpResponse.StatusCode +} + +func (r *Response) Protocol() string { + return r.httpResponse.Proto +} + +func (r *Response) HttpResponse() *http.Response { + return r.httpResponse +} + +func (r *Response) SetHttpResponse(response *http.Response) { + r.httpResponse = response +} + +func (r *Response) ElapsedTime() time.Duration { + return r.elapsedTime +} + +func (r *Response) GetHeader(name string) string { + return r.httpResponse.Header.Get(name) +} + +func (r *Response) GetHeaders() map[string]string { + header := r.httpResponse.Header + ret := make(map[string]string, len(header)) + for k, v := range header { + ret[k] = v[0] + } + return ret +} + +func (r *Response) ContentLength() int64 { + return r.httpResponse.ContentLength +} + +func (r *Response) Body() io.ReadCloser { + return r.httpResponse.Body +} diff --git a/bce-sdk-go/init.go b/bce-sdk-go/init.go new file mode 100644 index 0000000..b733c7b --- /dev/null +++ b/bce-sdk-go/init.go @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// init.go - just import the sub packages + +// Package sdk imports all sub packages to build all of them when calling `go install', `go build' +// or `go get' commands. +package sdk + +import ( + _ "github.com/baidubce/bce-sdk-go/auth" + _ "github.com/baidubce/bce-sdk-go/bce" + _ "github.com/baidubce/bce-sdk-go/http" + _ "github.com/baidubce/bce-sdk-go/model" + _ "github.com/baidubce/bce-sdk-go/services/appblb" + _ "github.com/baidubce/bce-sdk-go/services/as" + _ "github.com/baidubce/bce-sdk-go/services/bbc" + _ "github.com/baidubce/bce-sdk-go/services/bcc" + _ "github.com/baidubce/bce-sdk-go/services/bcm" + _ "github.com/baidubce/bce-sdk-go/services/bec" + _ "github.com/baidubce/bce-sdk-go/services/bie" + _ "github.com/baidubce/bce-sdk-go/services/blb" + _ "github.com/baidubce/bce-sdk-go/services/bos" + _ "github.com/baidubce/bce-sdk-go/services/cdn" + _ "github.com/baidubce/bce-sdk-go/services/cert" + _ "github.com/baidubce/bce-sdk-go/services/cfc" + _ "github.com/baidubce/bce-sdk-go/services/dcc" + _ "github.com/baidubce/bce-sdk-go/services/ddc" + _ "github.com/baidubce/bce-sdk-go/services/ddc/v2" + _ "github.com/baidubce/bce-sdk-go/services/eccr" + _ "github.com/baidubce/bce-sdk-go/services/eip" + _ "github.com/baidubce/bce-sdk-go/services/etGateway" + _ "github.com/baidubce/bce-sdk-go/services/iam" + _ "github.com/baidubce/bce-sdk-go/services/mms" + _ "github.com/baidubce/bce-sdk-go/services/rds" + _ "github.com/baidubce/bce-sdk-go/services/scs" + _ "github.com/baidubce/bce-sdk-go/services/sms" + _ "github.com/baidubce/bce-sdk-go/services/sts" + _ "github.com/baidubce/bce-sdk-go/services/vca" + _ "github.com/baidubce/bce-sdk-go/services/vcr" + _ "github.com/baidubce/bce-sdk-go/services/vpc" + _ "github.com/baidubce/bce-sdk-go/services/vpn" + _ "github.com/baidubce/bce-sdk-go/util" + _ "github.com/baidubce/bce-sdk-go/util/crypto" + _ "github.com/baidubce/bce-sdk-go/util/log" +) diff --git a/bce-sdk-go/model/tag.go b/bce-sdk-go/model/tag.go new file mode 100644 index 0000000..581e477 --- /dev/null +++ b/bce-sdk-go/model/tag.go @@ -0,0 +1,6 @@ +package model + +type TagModel struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} diff --git a/bce-sdk-go/services/appblb/appblb.go b/bce-sdk-go/services/appblb/appblb.go new file mode 100644 index 0000000..2229e28 --- /dev/null +++ b/bce-sdk-go/services/appblb/appblb.go @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// appblb.go - the Application BLB APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateLoadBalancer - create a LoadBalancer +// +// PARAMS: +// - args: parameters to create LoadBalancer +// +// RETURNS: +// - *CreateLoadBalanceResult: the result of create LoadBalancer, contains new LoadBalancer's ID +// - error: nil if ok otherwise the specific error +func (c *Client) CreateLoadBalancer(args *CreateLoadBalancerArgs) (*CreateLoadBalanceResult, error) { + if args == nil || len(args.SubnetId) == 0 { + return nil, fmt.Errorf("unset subnet id") + } + + if len(args.VpcId) == 0 { + return nil, fmt.Errorf("unset vpc id") + } + + result := &CreateLoadBalanceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppBlbUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateLoadBalancer - update a LoadBalancer +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update LoadBalancer +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateLoadBalancer(blbId string, args *UpdateLoadBalancerArgs) error { + if args == nil { + args = &UpdateLoadBalancerArgs{} + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppBlbUriWithId(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeLoadBalancers - describe all LoadBalancers +// +// PARAMS: +// - args: parameters to describe all LoadBalancers +// +// RETURNS: +// - *DescribeLoadBalancersResult: the result all LoadBalancers's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLoadBalancers(args *DescribeLoadBalancersArgs) (*DescribeLoadBalancersResult, error) { + if args == nil { + args = &DescribeLoadBalancersArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeLoadBalancersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppBlbUri()). + WithQueryParamFilter("address", args.Address). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("blbId", args.BlbId). + WithQueryParamFilter("bccId", args.BccId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ExactlyMatch { + request.WithQueryParam("exactlyMatch", "true") + } + + err := request.Do() + return result, err +} + +// DescribeLoadBalancerDetail - describe a LoadBalancer +// +// PARAMS: +// - blbId: describe LoadBalancer's ID +// +// RETURNS: +// - *DescribeLoadBalancerDetailResult: the result LoadBalancer detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLoadBalancerDetail(blbId string) (*DescribeLoadBalancerDetailResult, error) { + result := &DescribeLoadBalancerDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppBlbUriWithId(blbId)). + WithResult(result). + Do() + + return result, err +} + +// DeleteLoadBalancer - delete a group +// +// PARAMS: +// - blbId: parameters to delete LoadBalancer +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteLoadBalancer(blbId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getAppBlbUriWithId(blbId)). + Do() +} diff --git a/bce-sdk-go/services/appblb/appipgroup.go b/bce-sdk-go/services/appblb/appipgroup.go new file mode 100644 index 0000000..77f8195 --- /dev/null +++ b/bce-sdk-go/services/appblb/appipgroup.go @@ -0,0 +1,300 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// appipgroup.go - the Application BLB Ip Group APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateAppIpGroup - create an ip group +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create IpGroup +// +// RETURNS: +// - *CreateAppIpGroupResult: the result of create IpGroup, contains new IpGroup's ID +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppIpGroup(blbId string, args *CreateAppIpGroupArgs) (*CreateAppIpGroupResult, error) { + if args == nil { + args = &CreateAppIpGroupArgs{} + } + + result := &CreateAppIpGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppIpGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateAppIpGroup - update an ip group +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update an ip group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppIpGroup(blbId string, args *UpdateAppIpGroupArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeAppIpGroup - describe all ip groups +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all ip groups +// +// RETURNS: +// - *DescribeAppIpGroupResult: the result of describe all ip groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppIpGroup(blbId string, args *DescribeAppIpGroupArgs) (*DescribeAppIpGroupResult, error) { + if args == nil { + args = &DescribeAppIpGroupArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeAppIpGroupResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppIpGroupUri(blbId)). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ExactlyMatch { + request.WithQueryParam("exactlyMatch", "true") + } + + err := request.Do() + return result, err +} + +// DeleteAppIpGroup - delete an ip group +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete an ip group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppIpGroup(blbId string, args *DeleteAppIpGroupArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("delete", ""). + WithBody(args). + Do() +} + +// CreateAppIpGroupBackendPolicy - create an ip group backend policy +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create an ip group backend policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppIpGroupBackendPolicy(blbId string, args *CreateAppIpGroupBackendPolicyArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + if len(args.Type) == 0 { + return fmt.Errorf("unset type") + } + + if args.Type == "UDP" && len(args.UdpHealthCheckString) == 0 { + return fmt.Errorf("unset udpHealthCheckString") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppIpGroupBackendPolicyUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppIpGroupBackendPolicy - update ip group backend policy +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update ip group backend policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppIpGroupBackendPolicy(blbId string, args *UpdateAppIpGroupBackendPolicyArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + if len(args.Id) == 0 { + return fmt.Errorf("unset ip group backend policy id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupBackendPolicyUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DeleteAppIpGroupBackendPolicy - delete an ip group backend policy +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete ip group backend policies +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppIpGroupBackendPolicy(blbId string, args *DeleteAppIpGroupBackendPolicyArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupBackendPolicyUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("delete", ""). + WithBody(args). + Do() +} + +// CreateAppIpGroupMember - create ip group members +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create ip group members +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppIpGroupMember(blbId string, args *CreateAppIpGroupMemberArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppIpGroupMemberUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppIpGroupMember - update ip group members +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update ip group members +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppIpGroupMember(blbId string, args *UpdateAppIpGroupMemberArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupMemberUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeAppIpGroupMember - describe ip group members +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe ip group members +// +// RETURNS: +// - *DescribeAppIpGroupMemberResult: the result of describe ip group members +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppIpGroupMember(blbId string, args *DescribeAppIpGroupMemberArgs) (*DescribeAppIpGroupMemberResult, error) { + if args == nil || len(args.IpGroupId) == 0 { + return nil, fmt.Errorf("unset ip group id") + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeAppIpGroupMemberResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppIpGroupMemberUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("ipGroupId", args.IpGroupId). + WithResult(result). + Do() + + return result, err +} + +// DeleteAppIpGroupMember - delete ip group members +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete ip group members +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppIpGroupMember(blbId string, args *DeleteAppIpGroupMemberArgs) error { + if args == nil || len(args.IpGroupId) == 0 { + return fmt.Errorf("unset ip group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppIpGroupMemberUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("delete", ""). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/appblb/appservergroup.go b/bce-sdk-go/services/appblb/appservergroup.go new file mode 100644 index 0000000..508732a --- /dev/null +++ b/bce-sdk-go/services/appblb/appservergroup.go @@ -0,0 +1,351 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// appservergroup.go - the Application BLB Server Group APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateAppServerGroup - create a LoadBalancer +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create ServerGroup +// +// RETURNS: +// - *CreateAppServerGroupResult: the result of create ServerGroup, contains new ServerGroup's ID +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppServerGroup(blbId string, args *CreateAppServerGroupArgs) (*CreateAppServerGroupResult, error) { + if args == nil { + args = &CreateAppServerGroupArgs{} + } + + result := &CreateAppServerGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppServerGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateAppServerGroup - update a server group +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update a server group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppServerGroup(blbId string, args *UpdateAppServerGroupArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppServerGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeAppServerGroup - describe all server groups +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all server groups +// +// RETURNS: +// - *DescribeAppServerGroupResult: the result of describe all server groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppServerGroup(blbId string, args *DescribeAppServerGroupArgs) (*DescribeAppServerGroupResult, error) { + if args == nil { + args = &DescribeAppServerGroupArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeAppServerGroupResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppServerGroupUri(blbId)). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ExactlyMatch { + request.WithQueryParam("exactlyMatch", "true") + } + + err := request.Do() + return result, err +} + +// DeleteAppServerGroup - delete a server group +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete a server group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppServerGroup(blbId string, args *DeleteAppServerGroupArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppServerGroupUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("delete", ""). + WithBody(args). + Do() +} + +// CreateAppServerGroupPort - create a server group port +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create a server group port +// +// RETURNS: +// - *CreateAppServerGroupPortResult: the result of create a server group port +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppServerGroupPort(blbId string, args *CreateAppServerGroupPortArgs) (*CreateAppServerGroupPortResult, error) { + if args == nil || len(args.SgId) == 0 { + return nil, fmt.Errorf("unset server group id") + } + + if len(args.Type) == 0 { + return nil, fmt.Errorf("unset type") + } + + if args.Type == "UDP" && len(args.UdpHealthCheckString) == 0 { + return nil, fmt.Errorf("unset udpHealthCheckString") + } + + result := &CreateAppServerGroupPortResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppServerGroupPortUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateAppServerGroupPort - update server group port +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update server group port +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppServerGroupPort(blbId string, args *UpdateAppServerGroupPortArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppServerGroupPortUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DeleteAppServerGroupPort - delete server group ports +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete server group ports +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppServerGroupPort(blbId string, args *DeleteAppServerGroupPortArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppServerGroupPortUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("batchdelete", ""). + WithBody(args). + Do() +} + +// CreateBlbRs - add backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to add backend servers +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateBlbRs(blbId string, args *CreateBlbRsArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getBlbRsUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateBlbRs - update backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update backend servers +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateBlbRs(blbId string, args *UpdateBlbRsArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBlbRsUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeBlbRs - describe backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe backend servers +// +// RETURNS: +// - *DescribeBlbRsResult: the result of describe backend servers +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeBlbRs(blbId string, args *DescribeBlbRsArgs) (*DescribeBlbRsResult, error) { + if args == nil || len(args.SgId) == 0 { + return nil, fmt.Errorf("unset server group id") + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeBlbRsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbRsUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("sgId", args.SgId). + WithResult(result). + Do() + + return result, err +} + +// DeleteBlbRs - delete backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete backend servers +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteBlbRs(blbId string, args *DeleteBlbRsArgs) error { + if args == nil || len(args.SgId) == 0 { + return fmt.Errorf("unset server group id") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBlbRsUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("batchdelete", ""). + WithBody(args). + Do() +} + +// DescribeRsMount - get all mount backend server list +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - sgId: ServerGroup's ID +// +// RETURNS: +// - *DescribeRsMountResult: the mount backend server list +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeRsMount(blbId, sgId string) (*DescribeRsMountResult, error) { + if len(sgId) == 0 { + return nil, fmt.Errorf("unset server group id") + } + + result := &DescribeRsMountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbRsMountUri(blbId)). + WithQueryParam("sgId", sgId). + WithResult(result). + Do() + + return result, err +} + +// DescribeRsUnMount - get all unmount backend server list +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - sgId: ServerGroup's ID +// +// RETURNS: +// - *DescribeRsMountResult: the unMount backend server list +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeRsUnMount(blbId, sgId string) (*DescribeRsMountResult, error) { + if len(sgId) == 0 { + return nil, fmt.Errorf("unset server group id") + } + + result := &DescribeRsMountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbRsUnMountUri(blbId)). + WithQueryParam("sgId", sgId). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/appblb/client.go b/bce-sdk-go/services/appblb/client.go new file mode 100644 index 0000000..e57d1d0 --- /dev/null +++ b/bce-sdk-go/services/appblb/client.go @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Application LoadBalance service + +// Package appblb defines the Application BLB services of BCE. The supported APIs are all defined in sub-package +package appblb + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + DEFAULT_SERVICE_DOMAIN = "blb." + bce.DEFAULT_REGION + ".baidubce.com" + URI_PREFIX = bce.URI_PREFIX + "v1" + REQUEST_APPBLB_URL = "/appblb" + + APP_SERVER_GROUP_URL = "/appservergroup" + APP_SERVER_GROUP_PORT_URL = "/appservergroupport" + BLB_RS_URL = "/blbrs" + BLB_RS_MOUNT_URL = "/blbrsmount" + BLB_RS_UNMOUNT_URL = "/blbrsunmount" + + APP_LISTENER_URL = "/listener" + APP_TCPLISTENER_URL = "/TCPlistener" + APP_UDPLISTENER_URL = "/UDPlistener" + APP_HTTPLISTENER_URL = "/HTTPlistener" + APP_HTTPSLISTENER_URL = "/HTTPSlistener" + APP_SSLLISTENER_URL = "/SSLlistener" + + POLICYS_URL = "/policys" + + APP_IP_GROUP_URL = "/ipgroup" + APP_IP_GROUP_BACKEND_POLICY_URL = "/ipgroup/backendpolicy" + APP_IP_GROUP_MEMBER_URL = "/ipgroup/member" + + SECURITY_GROUP_URL = "/securitygroup" + ENTERPRISE_SECURITY_GROUP_URL = "/enterprise/securitygroup" +) + +// Client of APPBLB service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getAppBlbUri() string { + return URI_PREFIX + REQUEST_APPBLB_URL +} + +func getAppBlbUriWithId(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id +} + +func getAppServerGroupUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_SERVER_GROUP_URL +} + +func getAppServerGroupPortUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_SERVER_GROUP_PORT_URL +} + +func getBlbRsUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + BLB_RS_URL +} + +func getBlbRsMountUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + BLB_RS_MOUNT_URL +} + +func getBlbRsUnMountUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + BLB_RS_UNMOUNT_URL +} + +func getAppListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_LISTENER_URL +} + +func getAppTCPListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_TCPLISTENER_URL +} + +func getAppUDPListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_UDPLISTENER_URL +} + +func getAppHTTPListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_HTTPLISTENER_URL +} + +func getAppHTTPSListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_HTTPSLISTENER_URL +} + +func getAppSSLListenerUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_SSLLISTENER_URL +} + +func getPolicysUrl(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + POLICYS_URL +} + +func getAppIpGroupUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_IP_GROUP_URL +} + +func getAppIpGroupBackendPolicyUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_IP_GROUP_BACKEND_POLICY_URL +} + +func getAppIpGroupMemberUri(id string) string { + return URI_PREFIX + REQUEST_APPBLB_URL + "/" + id + APP_IP_GROUP_MEMBER_URL +} + +func getSecurityGroupUri(id string) string { + return URI_PREFIX + "/blb" + "/" + id + SECURITY_GROUP_URL +} + +func getEnterpriseSecurityGroupUri(id string) string { + return URI_PREFIX + "/blb" + "/" + id + ENTERPRISE_SECURITY_GROUP_URL +} diff --git a/bce-sdk-go/services/appblb/client_test.go b/bce-sdk-go/services/appblb/client_test.go new file mode 100644 index 0000000..930209c --- /dev/null +++ b/bce-sdk-go/services/appblb/client_test.go @@ -0,0 +1,673 @@ +package appblb + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + APPBLB_CLIENT *Client + APPBLB_ID string + APPBLB_SERVERGROUP_ID string + APPBLB_SERVERGROUPPORT_ID string + APPBLB_POLICY_ID string + APPBLB_IPGROUP_ID string + IPGROUP_MEMBER_ID string + APPBLB_IPGROUPP_BACKENDPOLICY_ID string + + // set these values before start test + VPC_TEST_ID = "" + SUBNET_TEST_ID = "" + INSTANCE_ID = "" + CERT_ID = "" + IPGROUP_MEMBER_IP = "" + CLUSTER_PROPERTY_TEST = "" + TEST_APPBLB_ID = "" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + APPBLB_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateLoadBalancer(t *testing.T) { + createArgs := &CreateLoadBalancerArgs{ + ClientToken: getClientToken(), + Name: "sdkBlb", + VpcId: VPC_TEST_ID, + SubnetId: SUBNET_TEST_ID, + ClusterProperty: CLUSTER_PROPERTY_TEST, + } + + createResult, err := APPBLB_CLIENT.CreateLoadBalancer(createArgs) + ExpectEqual(t.Errorf, nil, err) + + APPBLB_ID = createResult.BlbId +} + +func TestClient_UpdateLoadBalancer(t *testing.T) { + updateArgs := &UpdateLoadBalancerArgs{ + Name: "testSdk", + Description: "test desc", + } + err := APPBLB_CLIENT.UpdateLoadBalancer(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeLoadBalancers(t *testing.T) { + describeArgs := &DescribeLoadBalancersArgs{} + res, err := APPBLB_CLIENT.DescribeLoadBalancers(describeArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeLoadBalancerDetail(t *testing.T) { + res, err := APPBLB_CLIENT.DescribeLoadBalancerDetail(APPBLB_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppServerGroup(t *testing.T) { + createArgs := &CreateAppServerGroupArgs{ + ClientToken: getClientToken(), + Name: "sdkTest", + } + createResult, err := APPBLB_CLIENT.CreateAppServerGroup(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) + + APPBLB_SERVERGROUP_ID = createResult.Id +} + +func TestClient_UpdateAppServerGroup(t *testing.T) { + updateArgs := &UpdateAppServerGroupArgs{ + SgId: APPBLB_SERVERGROUP_ID, + Name: "testSdk", + Description: "test desc", + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.UpdateAppServerGroup(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppServerGroup(t *testing.T) { + describeArgs := &DescribeAppServerGroupArgs{} + _, err := APPBLB_CLIENT.DescribeAppServerGroup(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppServerGroupPort(t *testing.T) { + createArgs := &CreateAppServerGroupPortArgs{ + ClientToken: getClientToken(), + SgId: APPBLB_SERVERGROUP_ID, + Port: 80, + Type: "TCP", + } + createResult, err := APPBLB_CLIENT.CreateAppServerGroupPort(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) + + APPBLB_SERVERGROUPPORT_ID = createResult.Id +} + +func TestClient_UpdateAppServerGroupPort(t *testing.T) { + updateArgs := &UpdateAppServerGroupPortArgs{ + ClientToken: getClientToken(), + SgId: APPBLB_SERVERGROUP_ID, + PortId: APPBLB_SERVERGROUPPORT_ID, + HealthCheck: "TCP", + HealthCheckPort: 30, + HealthCheckIntervalInSecond: 10, + HealthCheckTimeoutInSecond: 10, + } + err := APPBLB_CLIENT.UpdateAppServerGroupPort(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppServerGroupPort(t *testing.T) { + deleteArgs := &DeleteAppServerGroupPortArgs{ + SgId: APPBLB_SERVERGROUP_ID, + PortIdList: []string{APPBLB_SERVERGROUPPORT_ID}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppServerGroupPort(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateBlbRs(t *testing.T) { + createArgs := &CreateBlbRsArgs{ + BlbRsWriteOpArgs: BlbRsWriteOpArgs{ + ClientToken: getClientToken(), + SgId: APPBLB_SERVERGROUP_ID, + BackendServerList: []AppBackendServer{ + {InstanceId: INSTANCE_ID, Weight: 30}, + }, + }, + } + err := APPBLB_CLIENT.CreateBlbRs(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateBlbRs(t *testing.T) { + updateArgs := &UpdateBlbRsArgs{ + BlbRsWriteOpArgs: BlbRsWriteOpArgs{ + ClientToken: getClientToken(), + SgId: APPBLB_SERVERGROUP_ID, + BackendServerList: []AppBackendServer{ + {InstanceId: INSTANCE_ID, Weight: 50}, + }, + }, + } + err := APPBLB_CLIENT.UpdateBlbRs(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeBlbRs(t *testing.T) { + describeArgs := &DescribeBlbRsArgs{ + SgId: APPBLB_SERVERGROUP_ID, + } + _, err := APPBLB_CLIENT.DescribeBlbRs(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteBlbRs(t *testing.T) { + deleteArgs := &DeleteBlbRsArgs{ + SgId: APPBLB_SERVERGROUP_ID, + BackendServerIdList: []string{INSTANCE_ID}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteBlbRs(APPBLB_ID, deleteArgs) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeRsMount(t *testing.T) { + _, err := APPBLB_CLIENT.DescribeRsMount(APPBLB_ID, APPBLB_SERVERGROUP_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeRsUnMount(t *testing.T) { + _, err := APPBLB_CLIENT.DescribeRsUnMount(APPBLB_ID, APPBLB_SERVERGROUP_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppServerGroup(t *testing.T) { + deleteArgs := &DeleteAppServerGroupArgs{ + SgId: APPBLB_SERVERGROUP_ID, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppServerGroup(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppIpGroup(t *testing.T) { + createArgs := &CreateAppIpGroupArgs{ + ClientToken: getClientToken(), + Name: "sdkTest", + } + createResult, err := APPBLB_CLIENT.CreateAppIpGroup(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) + + APPBLB_IPGROUP_ID = createResult.Id +} + +func TestClient_UpdateAppIpGroup(t *testing.T) { + updateArgs := &UpdateAppIpGroupArgs{ + IpGroupId: APPBLB_IPGROUP_ID, + Name: "testSdk", + Desc: "test desc", + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.UpdateAppIpGroup(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppIpGroup(t *testing.T) { + describeArgs := &DescribeAppIpGroupArgs{} + res, err := APPBLB_CLIENT.DescribeAppIpGroup(APPBLB_ID, describeArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppIpGroupBackendPolicy(t *testing.T) { + createArgs := &CreateAppIpGroupBackendPolicyArgs{ + ClientToken: getClientToken(), + IpGroupId: APPBLB_IPGROUP_ID, + Type: "TCP", + } + err := APPBLB_CLIENT.CreateAppIpGroupBackendPolicy(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppIpGroupBackendPolicy(t *testing.T) { + updateArgs := &UpdateAppIpGroupBackendPolicyArgs{ + ClientToken: getClientToken(), + IpGroupId: APPBLB_IPGROUP_ID, + Id: APPBLB_IPGROUPP_BACKENDPOLICY_ID, + HealthCheckIntervalInSecond: 10, + HealthCheckTimeoutInSecond: 10, + } + err := APPBLB_CLIENT.UpdateAppIpGroupBackendPolicy(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppIpGroupBackendPolicy(t *testing.T) { + deleteArgs := &DeleteAppIpGroupBackendPolicyArgs{ + IpGroupId: APPBLB_IPGROUP_ID, + BackendPolicyIdList: []string{APPBLB_IPGROUPP_BACKENDPOLICY_ID}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppIpGroupBackendPolicy(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppIpGroupMember(t *testing.T) { + createArgs := &CreateAppIpGroupMemberArgs{ + AppIpGroupMemberWriteOpArgs: AppIpGroupMemberWriteOpArgs{ + ClientToken: getClientToken(), + IpGroupId: APPBLB_IPGROUP_ID, + MemberList: []AppIpGroupMember{ + {Ip: IPGROUP_MEMBER_IP, Port: 30}, + }, + }, + } + err := APPBLB_CLIENT.CreateAppIpGroupMember(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppIpGroupMember(t *testing.T) { + updateArgs := &UpdateAppIpGroupMemberArgs{ + AppIpGroupMemberWriteOpArgs: AppIpGroupMemberWriteOpArgs{ + ClientToken: getClientToken(), + IpGroupId: APPBLB_IPGROUP_ID, + MemberList: []AppIpGroupMember{ + {Ip: IPGROUP_MEMBER_IP, Port: 50, MemberId: IPGROUP_MEMBER_ID}, + }, + }, + } + err := APPBLB_CLIENT.UpdateAppIpGroupMember(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppIpGroupMember(t *testing.T) { + describeArgs := &DescribeAppIpGroupMemberArgs{ + IpGroupId: APPBLB_IPGROUP_ID, + } + res, err := APPBLB_CLIENT.DescribeAppIpGroupMember(APPBLB_ID, describeArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppIpGroupMember(t *testing.T) { + deleteArgs := &DeleteAppIpGroupMemberArgs{ + IpGroupId: APPBLB_IPGROUP_ID, + MemberIdList: []string{IPGROUP_MEMBER_ID}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppIpGroupMember(APPBLB_ID, deleteArgs) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppTCPListener(t *testing.T) { + createArgs := &CreateAppTCPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 90, + Scheduler: "RoundRobin", + } + err := APPBLB_CLIENT.CreateAppTCPListener(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppTCPListener(t *testing.T) { + updateArgs := &UpdateAppTCPListenerArgs{ + UpdateAppListenerArgs: UpdateAppListenerArgs{ + ListenerPort: 90, + Scheduler: "Hash", + }, + } + err := APPBLB_CLIENT.UpdateAppTCPListener(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppTCPListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{ + ListenerPort: 90, + } + _, err := APPBLB_CLIENT.DescribeAppTCPListeners(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppUDPListener(t *testing.T) { + createArgs := &CreateAppUDPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 91, + Scheduler: "RoundRobin", + } + err := APPBLB_CLIENT.CreateAppUDPListener(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppUDPListener(t *testing.T) { + updateArgs := &UpdateAppUDPListenerArgs{ + UpdateAppListenerArgs: UpdateAppListenerArgs{ + ListenerPort: 91, + Scheduler: "Hash", + }, + } + err := APPBLB_CLIENT.UpdateAppUDPListener(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppUDPListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{ + ListenerPort: 53, + } + result, err := APPBLB_CLIENT.DescribeAppUDPListeners(APPBLB_ID, describeArgs) + if err != nil { + fmt.Println("get udp listener failed:", err) + } else { + fmt.Println("get udp listener success: ", result) + } +} + +func TestClient_CreateAppHTTPListener(t *testing.T) { + createArgs := &CreateAppHTTPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 92, + Scheduler: "RoundRobin", + } + err := APPBLB_CLIENT.CreateAppHTTPListener(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppHTTPListener(t *testing.T) { + updateArgs := &UpdateAppHTTPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 92, + Scheduler: "LeastConnection", + KeepSession: true, + } + err := APPBLB_CLIENT.UpdateAppHTTPListener(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreatePolicys(t *testing.T) { + createArgs := &CreatePolicysArgs{ + ListenerPort: 92, + ClientToken: getClientToken(), + AppPolicyVos: []AppPolicy{ + { + Description: "test policy", + AppServerGroupId: "", + BackendPort: 92, + Priority: 300, + RuleList: []AppRule{ + { + Key: "*", + Value: "*", + }, + }, + }, + }, + } + err := APPBLB_CLIENT.CreatePolicys(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreatePolicysIpGroup(t *testing.T) { + createArgs := &CreatePolicysArgs{ + ListenerPort: 80, + ClientToken: getClientToken(), + AppPolicyVos: []AppPolicy{ + { + Description: "test policy", + AppIpGroupId: APPBLB_IPGROUP_ID, + Priority: 100, + RuleList: []AppRule{ + { + Key: "*", + Value: "*", + }, + }, + }, + }, + } + err := APPBLB_CLIENT.CreatePolicys(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribePolicys(t *testing.T) { + describeArgs := &DescribePolicysArgs{ + Port: 80, + } + res, err := APPBLB_CLIENT.DescribePolicys(APPBLB_ID, describeArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) + + APPBLB_POLICY_ID = res.PolicyList[0].Id +} + +func TestClient_DeletePolicys(t *testing.T) { + deleteArgs := &DeletePolicysArgs{ + Port: 80, + PolicyIdList: []string{APPBLB_POLICY_ID}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeletePolicys(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppHTTPListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{ + ListenerPort: 92, + } + _, err := APPBLB_CLIENT.DescribeAppHTTPListeners(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppHTTPSListener(t *testing.T) { + createArgs := &CreateAppHTTPSListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 93, + Scheduler: "RoundRobin", + CertIds: []string{CERT_ID}, + } + err := APPBLB_CLIENT.CreateAppHTTPSListener(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppHTTPSListener(t *testing.T) { + updateArgs := &UpdateAppHTTPSListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 93, + Scheduler: "LeastConnection", + KeepSession: true, + CertIds: []string{CERT_ID}, + } + err := APPBLB_CLIENT.UpdateAppHTTPSListener(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppHTTPSListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{ + ListenerPort: 93, + } + _, err := APPBLB_CLIENT.DescribeAppHTTPSListeners(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAppSSLListener(t *testing.T) { + createArgs := &CreateAppSSLListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 94, + Scheduler: "RoundRobin", + CertIds: []string{CERT_ID}, + } + err := APPBLB_CLIENT.CreateAppSSLListener(APPBLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAppSSLListener(t *testing.T) { + updateArgs := &UpdateAppSSLListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 94, + Scheduler: "LeastConnection", + CertIds: []string{CERT_ID}, + } + err := APPBLB_CLIENT.UpdateAppSSLListener(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppSSLListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{ + ListenerPort: 94, + } + _, err := APPBLB_CLIENT.DescribeAppSSLListeners(APPBLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAppAllListeners(t *testing.T) { + describeArgs := &DescribeAppListenerArgs{} + result, err := APPBLB_CLIENT.DescribeAppAllListeners(APPBLB_ID, describeArgs) + if err != nil { + fmt.Println("get all listener failed:", err) + } else { + fmt.Println("get all listener success: ", result) + } +} + +func TestClient_BindSecurityGroups(t *testing.T) { + updateArgs := &UpdateSecurityGroupsArgs{ + ClientToken: getClientToken(), + SecurityGroupIds: []string{"sg-id"}, + } + err := APPBLB_CLIENT.BindSecurityGroups(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindSecurityGroups(t *testing.T) { + updateArgs := &UpdateSecurityGroupsArgs{ + ClientToken: getClientToken(), + SecurityGroupIds: []string{"sg-id"}, + } + err := APPBLB_CLIENT.UnbindSecurityGroups(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeSecurityGroups(t *testing.T) { + res, err := APPBLB_CLIENT.DescribeSecurityGroups(APPBLB_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindEnterpriseSecurityGroups(t *testing.T) { + updateArgs := &UpdateEnterpriseSecurityGroupsArgs{ + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{"esg-id"}, + } + err := APPBLB_CLIENT.BindEnterpriseSecurityGroups(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindEnterpriseSecurityGroups(t *testing.T) { + updateArgs := &UpdateEnterpriseSecurityGroupsArgs{ + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{"esg-id"}, + } + err := APPBLB_CLIENT.UnbindEnterpriseSecurityGroups(APPBLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeEnterpriseSecurityGroups(t *testing.T) { + res, err := APPBLB_CLIENT.DescribeEnterpriseSecurityGroups(APPBLB_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppListeners(t *testing.T) { + deleteArgs := &DeleteAppListenersArgs{ + PortList: []uint16{90, 91, 92, 93, 94}, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppListeners(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAppPortTypeListeners(t *testing.T) { + deleteArgs := &DeleteAppListenersArgs{ + PortTypeList: []PortTypeModel{ + { + Port: 80, + Type: "UDP", + }, + { + Port: 80, + Type: "HTTP", + }, + }, + ClientToken: getClientToken(), + } + err := APPBLB_CLIENT.DeleteAppListeners(APPBLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteLoadBalancer(t *testing.T) { + err := APPBLB_CLIENT.DeleteLoadBalancer(APPBLB_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/appblb/config.json b/bce-sdk-go/services/appblb/config.json new file mode 100644 index 0000000..5a6634f --- /dev/null +++ b/bce-sdk-go/services/appblb/config.json @@ -0,0 +1,5 @@ +{ + "AK":"", + "SK":"", + "Endpoint":"" +} diff --git a/bce-sdk-go/services/appblb/enterprisesecuritygroups.go b/bce-sdk-go/services/appblb/enterprisesecuritygroups.go new file mode 100644 index 0000000..c630a66 --- /dev/null +++ b/bce-sdk-go/services/appblb/enterprisesecuritygroups.go @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// enterprisesecuritygroups.go - the enterprisesecuritygroup APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// BindEnterpriseSecurityGroups - bind the blb enterprise security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update enterprise security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) BindEnterpriseSecurityGroups(blbId string, args *UpdateEnterpriseSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.EnterpriseSecurityGroupIds) == 0 { + return fmt.Errorf("unset enterprise security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithQueryParam("bind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UnbindEnterpriseSecurityGroups - unbind the blb enterprise security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update enterprise security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UnbindEnterpriseSecurityGroups(blbId string, args *UpdateEnterpriseSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.EnterpriseSecurityGroupIds) == 0 { + return fmt.Errorf("unset enterprise security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithQueryParam("unbind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeEnterpriseSecurityGroups - describe all enterprise security groups of the specified LoadBalancer (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// +// RETURNS: +// - *DescribeEnterpriseSecurityGroupsResult: the result of describe all enterprise security groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeEnterpriseSecurityGroups(blbId string) (*DescribeEnterpriseSecurityGroupsResult, error) { + + result := &DescribeEnterpriseSecurityGroupsResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithResult(result) + + err := request.Do() + return result, err +} diff --git a/bce-sdk-go/services/appblb/listener.go b/bce-sdk-go/services/appblb/listener.go new file mode 100644 index 0000000..cffee76 --- /dev/null +++ b/bce-sdk-go/services/appblb/listener.go @@ -0,0 +1,636 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// listener.go - the Application BLB Listener APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateAppTCPListener - create a TCP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create TCP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppTCPListener(blbId string, args *CreateAppTCPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppTCPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateAppUDPListener - create a UDP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create UDP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppUDPListener(blbId string, args *CreateAppUDPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppUDPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateAppHTTPListener - create a HTTP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create HTTP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppHTTPListener(blbId string, args *CreateAppHTTPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppHTTPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateAppHTTPSListener - create a HTTPS Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create HTTPS Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppHTTPSListener(blbId string, args *CreateAppHTTPSListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppHTTPSListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateAppSSLListener - create a SSL Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create SSL Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppSSLListener(blbId string, args *CreateAppSSLListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAppSSLListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppTCPListener - update a TCP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update TCP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppTCPListener(blbId string, args *UpdateAppTCPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppTCPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppUDPListener - update a UDP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update UDP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppUDPListener(blbId string, args *UpdateAppUDPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppUDPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppHTTPListener - update a HTTP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update HTTP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppHTTPListener(blbId string, args *UpdateAppHTTPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppHTTPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppHTTPSListener - update a HTTPS Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update HTTPS Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppHTTPSListener(blbId string, args *UpdateAppHTTPSListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppHTTPSListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateAppSSLListener - update a SSL Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update SSL Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppSSLListener(blbId string, args *UpdateAppSSLListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppSSLListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeAppTCPListeners - describe all TCP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all TCP Listeners +// +// RETURNS: +// - *DescribeAppTCPListenersResult: the result of describe all TCP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppTCPListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppTCPListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppTCPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppTCPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAppUDPListeners - describe all UDP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all UDP Listeners +// +// RETURNS: +// - *DescribeAppUDPListenersResult: the result of describe all UDP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppUDPListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppUDPListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppUDPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppUDPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAppHTTPListeners - describe all HTTP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all HTTP Listeners +// +// RETURNS: +// - *DescribeAppHTTPListenersResult: the result of describe all HTTP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppHTTPListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppHTTPListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppHTTPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppHTTPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAppHTTPSListeners - describe all HTTPS Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all HTTPS Listeners +// +// RETURNS: +// - *DescribeAppHTTPSListenersResult: the result of describe all HTTPS Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppHTTPSListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppHTTPSListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppHTTPSListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppHTTPSListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAppSSLListeners - describe all SSL Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all SSL Listeners +// +// RETURNS: +// - *DescribeAppSSLListenersResult: the result of describe all SSL Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppSSLListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppSSLListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppSSLListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppSSLListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAppAllListeners - describe all Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all Listeners +// +// RETURNS: +// - *DescribeAppAllListenersResult: the result of describe all Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAppAllListeners(blbId string, args *DescribeAppListenerArgs) (*DescribeAppAllListenersResult, error) { + if args == nil { + args = &DescribeAppListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAppAllListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAppListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DeleteAppListeners - delete Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete Listeners, a listener port list +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppListeners(blbId string, args *DeleteAppListenersArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.PortList) == 0 && len(args.PortTypeList) == 0 { + return fmt.Errorf("unset port list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAppListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("batchdelete", ""). + WithBody(args). + Do() +} + +// CreatePolicys - create a policy bind with Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create a policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreatePolicys(blbId string, args *CreatePolicysArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unset listen port") + } + + if len(args.AppPolicyVos) == 0 { + return fmt.Errorf("unset App Policy") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getPolicysUrl(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribePolicys - descirbe a policy +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create a policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DescribePolicys(blbId string, args *DescribePolicysArgs) (*DescribePolicysResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Port == 0 { + return nil, fmt.Errorf("unset port") + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribePolicysResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getPolicysUrl(blbId)). + WithQueryParam("port", strconv.Itoa(int(args.Port))). + WithQueryParamFilter("type", args.Type). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// DeletePolicys - delete a policy +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete a policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeletePolicys(blbId string, args *DeletePolicysArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.Port == 0 { + return fmt.Errorf("unset port") + } + + if len(args.PolicyIdList) == 0 { + return fmt.Errorf("unset policy id list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getPolicysUrl(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("batchdelete", ""). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/appblb/model.go b/bce-sdk-go/services/appblb/model.go new file mode 100644 index 0000000..168946c --- /dev/null +++ b/bce-sdk-go/services/appblb/model.go @@ -0,0 +1,758 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package appblb + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type BLBStatus string + +const ( + BLBStatusCreating BLBStatus = "creating" + BLBStatusAvailable BLBStatus = "available" + BLBStatusUpdating BLBStatus = "updating" + BLBStatusPaused BLBStatus = "paused" + BLBStatusUnavailable BLBStatus = "unavailable" +) + +type AppRsPortModel struct { + ListenerPort int `json:"listenerPort"` + BackendPort string `json:"backendPort"` + PortType string `json:"portType"` + HealthCheckPortType string `json:"healthCheckPortType"` + Status string `json:"status"` + PortId string `json:"portId"` + PolicyId string `json:"policyId"` +} + +type AppBackendServer struct { + InstanceId string `json:"instanceId,omitempty"` + Weight int `json:"weight,omitempty"` + PrivateIp string `json:"privateIp,omitempty"` + PortList []AppRsPortModel `json:"portList,omitempty"` +} + +type DescribeResultMeta struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type CreateAppServerGroupArgs struct { + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + BackendServerList []AppBackendServer `json:"backendServerList,omitempty"` + ClientToken string `json:"-"` +} + +type CreateAppServerGroupResult struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"desc"` + Status BLBStatus `json:"status"` +} + +type UpdateAppServerGroupArgs struct { + SgId string `json:"sgId"` + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + ClientToken string `json:"-"` +} + +type DescribeAppServerGroupArgs struct { + Name string + ExactlyMatch bool + Marker string + MaxKeys int +} + +type AppServerGroupPort struct { + Id string `json:"id"` + Port int `json:"port"` + Type string `json:"type"` + Status BLBStatus `json:"status"` + HealthCheck string `json:"healthCheck"` + HealthCheckPort int `json:"healthCheckPort"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond"` + HealthCheckDownRetry int `json:"healthCheckDownRetry"` + HealthCheckUpRetry int `json:"healthCheckUpRetry"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + HealthCheckUrlPath string `json:"healthCheckUrlPath"` + UdpHealthCheckString string `json:"udpHealthCheckString"` +} + +type AppServerGroup struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"desc"` + Status BLBStatus `json:"status"` + PortList []AppServerGroupPort `json:"portList"` +} + +type DescribeAppServerGroupResult struct { + DescribeResultMeta + AppServerGroupList []AppServerGroup `json:"appServerGroupList"` +} + +type DeleteAppServerGroupArgs struct { + SgId string `json:"sgId"` + ClientToken string `json:"-"` +} + +type CreateAppServerGroupPortArgs struct { + ClientToken string `json:"-"` + SgId string `json:"sgId"` + Port uint16 `json:"port"` + Type string `json:"type"` + HealthCheck string `json:"healthCheck,omitempty"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} + +type CreateAppServerGroupPortResult struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"desc"` + Status BLBStatus `json:"status"` +} + +type UpdateAppServerGroupPortArgs struct { + ClientToken string `json:"-"` + SgId string `json:"sgId"` + PortId string `json:"portId"` + HealthCheck string `json:"healthCheck,omitempty"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} + +type DeleteAppServerGroupPortArgs struct { + SgId string `json:"sgId"` + PortIdList []string `json:"portIdList"` + ClientToken string `json:"-"` +} + +type BlbRsWriteOpArgs struct { + SgId string `json:"sgId"` + BackendServerList []AppBackendServer `json:"backendServerList"` + ClientToken string `json:"-"` +} + +type CreateBlbRsArgs struct { + BlbRsWriteOpArgs +} + +type UpdateBlbRsArgs struct { + BlbRsWriteOpArgs +} + +type DescribeBlbRsArgs struct { + Marker string + MaxKeys int + SgId string +} + +type DescribeBlbRsResult struct { + BackendServerList []AppBackendServer `json:"backendServerList"` + DescribeResultMeta +} + +type DeleteBlbRsArgs struct { + SgId string `json:"sgId"` + BackendServerIdList []string `json:"backendServerIdList"` + ClientToken string `json:"-"` +} + +type DescribeRsMountResult struct { + BackendServerList []AppBackendServer `json:"backendServerList"` +} + +type CreateLoadBalancerArgs struct { + ClientToken string `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + SubnetId string `json:"subnetId"` + VpcId string `json:"vpcId"` + ClusterProperty string `json:"clusterProperty"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +type CreateLoadBalanceResult struct { + Address string `json:"address"` + Name string `json:"name"` + Description string `json:"desc"` + BlbId string `json:"blbId"` +} + +type UpdateLoadBalancerArgs struct { + ClientToken string `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + AllowDelete *bool `json:"allowDelete,omitempty"` +} + +type DescribeLoadBalancersArgs struct { + Address string + Name string + BlbId string + BccId string + ExactlyMatch bool + Marker string + MaxKeys int +} + +type AppBLBModel struct { + BlbId string `json:"blbId"` + Name string `json:"name"` + Description string `json:"desc"` + Address string `json:"address"` + Status BLBStatus `json:"status"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + PublicIp string `json:"publicIp"` + Layer4ClusterId string `json:"layer4ClusterId"` + Layer7ClusterId string `json:"layer7ClusterId"` + Tags []model.TagModel `json:"tags"` + EipRouteType string `json:"eipRouteType"` + AllowDelete bool `json:"allowDelete"` +} + +type DescribeLoadBalancersResult struct { + BlbList []AppBLBModel `json:"blbList"` + DescribeResultMeta +} + +type ListenerModel struct { + Port string `json:"port"` + Type string `json:"type"` +} + +type PortTypeModel struct { + Port int `json:"port"` + Type string `json:"type"` +} + +type DescribeLoadBalancerDetailResult struct { + BlbId string `json:"blbId"` + Name string `json:"name"` + Status BLBStatus `json:"status"` + Description string `json:"desc"` + Address string `json:"address"` + PublicIp string `json:"publicIp"` + Cidr string `json:"cidr"` + VpcName string `json:"vpcName"` + SubnetCider string `json:"subnetCider"` + SubnetName string `json:"subnetName"` + CreateTime string `json:"createTime"` + ReleaseTime string `json:"releaseTime"` + Layer4ClusterId string `json:"layer4ClusterId"` + Layer7ClusterId string `json:"layer7ClusterId"` + Listener []ListenerModel `json:"listener"` + Tags []model.TagModel `json:"tags"` + EipRouteType string `json:"eipRouteType"` +} + +type CreateAppTCPListenerArgs struct { + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + ClientToken string `json:"-"` +} + +type CreateAppUDPListenerArgs struct { + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + ClientToken string `json:"-"` +} + +type CreateAppHTTPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionTimeout int `json:"keepSessionTimeout,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + RedirectPort uint16 `json:"redirectPort,omitempty"` +} + +type CreateAppHTTPSListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionTimeout int `json:"keepSessionTimeout,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type CreateAppSSLListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type UpdateAppListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + Scheduler string `json:"scheduler,omitempty"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` +} + +type UpdateAppTCPListenerArgs struct { + UpdateAppListenerArgs +} + +type UpdateAppUDPListenerArgs struct { + UpdateAppListenerArgs +} + +type UpdateAppHTTPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionTimeout int `json:"keepSessionTimeout,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + RedirectPort uint16 `json:"redirectPort,omitempty"` +} + +type UpdateAppHTTPSListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionTimeout int `json:"keepSessionTimeout,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type UpdateAppSSLListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + Scheduler string `json:"scheduler"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type AppListenerModel struct { + Port uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + TcpSessionTimeout int `json:"tcpSessionTimeout"` + UdpSessionTimeout int `json:"udpSessionTimeout"` +} + +type AppTCPListenerModel struct { + AppListenerModel +} + +type AppUDPListenerModel struct { + AppListenerModel +} + +type AppHTTPListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionTimeout int `json:"keepSessionTimeout"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + ServerTimeout int `json:"serverTimeout"` + RedirectPort int `json:"redirectPort"` +} + +type AppHTTPSListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionTimeout int `json:"keepSessionTimeout"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + ServerTimeout int `json:"serverTimeout"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` +} + +type AppSSLListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + Scheduler string `json:"scheduler"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` +} + +type AppAllListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + ListenerType string `json:"listenerType"` + Scheduler string `json:"scheduler"` + TcpSessionTimeout int `json:"tcpSessionTimeout"` + UdpSessionTimeout int `json:"udpSessionTimeout"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionTimeout int `json:"keepSessionTimeout"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + xForwardedProto bool `json:"xForwardedProto"` + ServerTimeout int `json:"serverTimeout"` + RedirectPort int `json:"redirectPort"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` +} + +type DescribeAppListenerArgs struct { + ListenerPort uint16 + Marker string + MaxKeys int +} + +type DescribeAppTCPListenersResult struct { + ListenerList []AppTCPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAppUDPListenersResult struct { + ListenerList []AppUDPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAppHTTPListenersResult struct { + ListenerList []AppHTTPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAppHTTPSListenersResult struct { + ListenerList []AppHTTPSListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAppSSLListenersResult struct { + ListenerList []AppSSLListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAppAllListenersResult struct { + ListenerList []AppAllListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DeleteAppListenersArgs struct { + ClientToken string `json:"-"` + PortList []uint16 `json:"portList"` + PortTypeList []PortTypeModel `json:"portTypeList"` +} + +type AppRule struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type AppPolicy struct { + Description string `json:"desc"` + AppServerGroupId string `json:"appServerGroupId,omitempty"` + AppIpGroupId string `json:"appIpGroupId,omitempty"` + AppIpGroupName string `json:"appIpGroupName,omitempty"` + GroupType string `json:"groupType,omitempty"` + + BackendPort uint16 `json:"backendPort,omitempty"` + Priority int `json:"priority"` + RuleList []AppRule `json:"ruleList"` + + Id string `json:"id"` + FrontendPort int `json:"frontendPort"` + AppServerGroupName string `json:"appServerGroupName"` + PortType string `json:"portType"` +} + +type CreatePolicysArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + AppPolicyVos []AppPolicy `json:"appPolicyVos"` + Type string `json:"type"` +} + +type DescribePolicysArgs struct { + Port uint16 + Type string + Marker string + MaxKeys int +} + +type DescribePolicysResult struct { + PolicyList []AppPolicy `json:"policyList"` + DescribeResultMeta +} + +type DeletePolicysArgs struct { + ClientToken string `json:"-"` + Port uint16 `json:"port"` + PolicyIdList []string `json:"policyIdList"` + Type string `json:"type"` +} + +type CreateAppIpGroupArgs struct { + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` + MemberList []AppIpGroupMember `json:"memberList,omitempty"` + ClientToken string `json:"-"` +} + +type AppIpGroupMember struct { + Ip string `json:"ip,omitempty"` + Port int `json:"port,omitempty"` + Weight int `json:"weight,omitempty"` + MemberId string `json:"memberId,omitempty"` + PortList []AppIpGroupMemberPortModel `json:"portList,omitempty"` +} + +type AppIpGroupMemberPortModel struct { + HealthCheckPortType string `json:"healthCheckPortType"` + Status string `json:"status"` +} + +type CreateAppIpGroupResult struct { + Id string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` +} + +type UpdateAppIpGroupArgs struct { + IpGroupId string `json:"ipGroupId"` + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` + ClientToken string `json:"-"` +} + +type DescribeAppIpGroupArgs struct { + Name string + ExactlyMatch bool + Marker string + MaxKeys int +} + +type DescribeAppIpGroupResult struct { + DescribeResultMeta + AppIpGroupList []AppIpGroup `json:"appIpGroupList"` +} + +type AppIpGroup struct { + Id string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` + BackendPolicyList []AppIpGroupBackendPolicy `json:"backendPolicyList"` +} + +type AppIpGroupBackendPolicy struct { + Id string `json:"id"` + Type string `json:"type"` + HealthCheck string `json:"healthCheck"` + HealthCheckPort int `json:"healthCheckPort"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond"` + HealthCheckDownRetry int `json:"healthCheckDownRetry"` + HealthCheckUpRetry int `json:"healthCheckUpRetry"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + HealthCheckUrlPath string `json:"healthCheckUrlPath"` + UdpHealthCheckString string `json:"udpHealthCheckString"` +} + +type DeleteAppIpGroupArgs struct { + IpGroupId string `json:"ipGroupId"` + ClientToken string `json:"-"` +} + +type CreateAppIpGroupBackendPolicyArgs struct { + ClientToken string `json:"-"` + IpGroupId string `json:"ipGroupId"` + Type string `json:"type"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} + +type UpdateAppIpGroupBackendPolicyArgs struct { + ClientToken string `json:"-"` + IpGroupId string `json:"ipGroupId"` + Id string `json:"id"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} + +type DeleteAppIpGroupBackendPolicyArgs struct { + IpGroupId string `json:"ipGroupId"` + BackendPolicyIdList []string `json:"backendPolicyIdList"` + ClientToken string `json:"-"` +} + +type AppIpGroupMemberWriteOpArgs struct { + IpGroupId string `json:"ipGroupId"` + MemberList []AppIpGroupMember `json:"memberList"` + ClientToken string `json:"-"` +} + +type CreateAppIpGroupMemberArgs struct { + AppIpGroupMemberWriteOpArgs +} + +type UpdateAppIpGroupMemberArgs struct { + AppIpGroupMemberWriteOpArgs +} + +type DescribeAppIpGroupMemberArgs struct { + Marker string + MaxKeys int + IpGroupId string +} + +type DescribeAppIpGroupMemberResult struct { + MemberList []AppIpGroupMember `json:"memberList"` + DescribeResultMeta +} + +type DeleteAppIpGroupMemberArgs struct { + IpGroupId string `json:"ipGroupId"` + MemberIdList []string `json:"memberIdList"` + ClientToken string `json:"-"` +} + +type UpdateSecurityGroupsArgs struct { + ClientToken string `json:"-"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type UpdateEnterpriseSecurityGroupsArgs struct { + ClientToken string `json:"-"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` +} + +type DescribeSecurityGroupsResult struct { + BlbSecurityGroups []BlbSecurityGroupModel `json:"blbSecurityGroups"` +} + +type DescribeEnterpriseSecurityGroupsResult struct { + BlbEnterpriseSecurityGroups []BlbEnterpriseSecurityGroupModel `json:"enterpriseSecurityGroups"` +} + +type BlbSecurityGroupModel struct { + SecurityGroupId string `json:"securityGroupId"` + SecurityGroupName string `json:"securityGroupName"` + SecurityGroupDesc string `json:"securityGroupDesc"` + VpcName string `json:"vpcName"` + SecurityGroupRules []BlbSecurityGroupRuleModel `json:"securityGroupRules"` +} + +type BlbEnterpriseSecurityGroupModel struct { + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId"` + EnterpriseSecurityGroupName string `json:"enterpriseSecurityGroupName"` + EnterpriseSecurityGroupDesc string `json:"enterpriseSecurityGroupDesc"` + EnterpriseSecurityGroupRules []BlbEnterpriseSecurityGroupRuleModel `json:"enterpriseSecurityGroupRules"` +} + +type BlbSecurityGroupRuleModel struct { + SecurityGroupRuleId string `json:"securityGroupRuleId"` + Direction string `json:"direction"` + Ethertype string `json:"ethertype,omitempty"` + PortRange string `json:"portRange,omitempty"` + Protocol string `json:"protocol,omitempty"` + SourceGroupId string `json:"sourceGroupId,omitempty"` + SourceIp string `json:"sourceIp,omitempty"` + DestGroupId string `json:"destGroupId,omitempty"` + DestIp string `json:"destIp,omitempty"` +} + +type BlbEnterpriseSecurityGroupRuleModel struct { + EnterpriseSecurityGroupRuleId string `json:"enterpriseSecurityGroupRuleId"` + Direction string `json:"direction"` + Action string `json:"action"` + Priority string `json:"priority"` + Remark string `json:"remark"` + Ethertype string `json:"ethertype,omitempty"` + PortRange string `json:"portRange,omitempty"` + Protocol string `json:"protocol,omitempty"` + SourceIp string `json:"sourceIp,omitempty"` + DestIp string `json:"destIp,omitempty"` +} diff --git a/bce-sdk-go/services/appblb/securitygroup.go b/bce-sdk-go/services/appblb/securitygroup.go new file mode 100644 index 0000000..2670743 --- /dev/null +++ b/bce-sdk-go/services/appblb/securitygroup.go @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// securitygroup.go - the securitygroup APIs definition supported by the APPBLB service + +package appblb + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// BindSecurityGroups - bind the blb security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) BindSecurityGroups(blbId string, args *UpdateSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.SecurityGroupIds) == 0 { + return fmt.Errorf("unset security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getSecurityGroupUri(blbId)). + WithQueryParam("bind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UnbindSecurityGroups - unbind the blb security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UnbindSecurityGroups(blbId string, args *UpdateSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.SecurityGroupIds) == 0 { + return fmt.Errorf("unset security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getSecurityGroupUri(blbId)). + WithQueryParam("unbind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeSecurityGroups - describe all security groups of the specified LoadBalancer (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// +// RETURNS: +// - *DescribeSecurityGroupsResult: the result of describe all security groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeSecurityGroups(blbId string) (*DescribeSecurityGroupsResult, error) { + + result := &DescribeSecurityGroupsResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupUri(blbId)). + WithResult(result) + + err := request.Do() + return result, err +} diff --git a/bce-sdk-go/services/as/as.go b/bce-sdk-go/services/as/as.go new file mode 100644 index 0000000..6c5ec46 --- /dev/null +++ b/bce-sdk-go/services/as/as.go @@ -0,0 +1,141 @@ +package as + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +const ( + GROUP_NAME = "groupName" + GROUP_ID = "groupId" + MARKER = "marker" + MAX_KEYS = "maxKeys" + MANNER = "manner" + AS_SCALING_DOWN = "scalingDown" + AS_SCALING_UP = "scalingUp" + AS_ADJUST_NODE = "adjustNode" +) + +// ListAsGroup 方法用于获取指定用户下的As组列表 +func (c *Client) ListAsGroup(req *ListAsGroupRequest) (*ListAsGroupResponse, error) { + if req == nil { + return nil, fmt.Errorf("ListAsGroupRequest is nil") + } + if req.MaxKeys <= 0 || req.MaxKeys > 1000 { + req.MaxKeys = 1000 + } + result := &ListAsGroupResponse{} + err := bce.NewRequestBuilder(c). + WithURL(getAsGroupListUri()). + WithMethod(http.GET). + WithQueryParamFilter(MANNER, MARKER). + WithQueryParamFilter(MARKER, req.Marker). + WithQueryParamFilter(MAX_KEYS, strconv.Itoa(req.MaxKeys)). + WithQueryParamFilter(GROUP_NAME, req.GroupName). + WithResult(result). + Do() + return result, err +} + +// GetAsGroup 根据groupId获取AsGroup信息 +func (c *Client) GetAsGroup(req *GetAsGroupRequest) (*GetAsGroupResponse, error) { + if req == nil { + return nil, fmt.Errorf("GetAsGroupRequest is nil") + } + result := &GetAsGroupResponse{} + err := bce.NewRequestBuilder(c). + WithURL(getAsGroupDetailUri(req.GroupId)). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} + +// IncreaseAsGroup 伸缩组扩容,用于在指定伸缩组下添加节点 +func (c *Client) IncreaseAsGroup(req *IncreaseAsGroupRequest) error { + if req == nil { + return fmt.Errorf("IncreaseAsGroupRequest is nil") + } + if len(req.GroupId) == 0 { + return fmt.Errorf("IncreaseAsGroupRequest GroupId is empty") + } + if req.NodeCount <= 0 { + return fmt.Errorf("IncreaseAsGroupRequest NodeCount is invalid") + } + if req.Zone == nil || len(req.Zone) == 0 { + return fmt.Errorf("IncreaseAsGroupRequest Zone is nil") + } + err := bce.NewRequestBuilder(c). + WithURL(getAsGroupUri(req.GroupId)). + WithQueryParam(AS_SCALING_UP, ""). + WithBody(req). + WithMethod(http.POST). + Do() + if err != nil { + return err + } + return nil +} + +// DecreaseAsGroup 伸缩组缩容,用于伸缩组下节点的缩容 +func (c *Client) DecreaseAsGroup(req *DecreaseAsGroupRequest) error { + if req == nil { + return fmt.Errorf("DecreaseAsGroupRequest is nil") + } + if len(req.GroupId) == 0 { + return fmt.Errorf("IncreaseAsGroupRequest GroupId is empty") + } + if req.Nodes == nil || len(req.Nodes) == 0 { + return fmt.Errorf("DecreaseAsGroupRequest Nodes is nil") + } + err := bce.NewRequestBuilder(c). + WithURL(getAsGroupUri(req.GroupId)). + WithQueryParam(AS_SCALING_DOWN, ""). + WithBody(req). + WithMethod(http.POST). + Do() + if err != nil { + return err + } + return nil +} + +// ListAsNode 方法用于获取节点列表 +func (c *Client) ListAsNode(req *ListAsNodeRequest) (*ListAsNodeResponse, error) { + if req == nil { + return nil, fmt.Errorf("ListAsNodeRequest is nil") + } + if req.MaxKeys <= 0 || req.MaxKeys > 1000 { + req.MaxKeys = 1000 + } + result := &ListAsNodeResponse{} + err := bce.NewRequestBuilder(c). + WithURL(getAsNodeUri()). + WithMethod(http.GET). + WithQueryParamFilter(MANNER, MARKER). + WithQueryParamFilter(MARKER, req.Marker). + WithQueryParamFilter(MAX_KEYS, strconv.Itoa(req.MaxKeys)). + WithQueryParamFilter(GROUP_ID, req.GroupId). + WithResult(result). + Do() + return result, err +} + +// AdjustAsGroup 将伸缩组节点调整到指定值。 +func (c *Client) AdjustAsGroup(req *AdjustAsGroupRequest) error { + if req == nil { + return fmt.Errorf("AdjustAsGroupByNodeIdRequest is nil") + } + if len(req.GroupId) == 0 { + return fmt.Errorf("AdjustAsGroupByNodeIdRequest GroupId is empty") + } + err := bce.NewRequestBuilder(c). + WithURL(getAsGroupUri(req.GroupId)). + WithQueryParam(AS_ADJUST_NODE, ""). + WithMethod(http.POST). + WithBody(req). + Do() + return err +} diff --git a/bce-sdk-go/services/as/client.go b/bce-sdk-go/services/as/client.go new file mode 100644 index 0000000..5168914 --- /dev/null +++ b/bce-sdk-go/services/as/client.go @@ -0,0 +1,69 @@ +package as + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strings" +) + +const ( + ProductName = "as" + DefaultBcmEndpoint = ProductName + "." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN + VERSION_1_URL = "v1" + VERSION_2_URL = "v2" + AS_GROUP_URL = "group" + AS_NODE_URL = "node" + AS_DETAIL_URL = "detail" +) + +// Client of BCM service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the as service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + + if len(endpoint) == 0 { + endpoint = DefaultBcmEndpoint + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: map[string]struct{}{ + strings.ToLower(http.HOST): {}, + strings.ToLower(http.BCE_DATE): {}, + }, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func getAsGroupListUri() string { + return "/" + VERSION_1_URL + "/" + AS_GROUP_URL +} + +func getAsGroupDetailUri(groupId string) string { + return "/" + VERSION_1_URL + "/" + AS_GROUP_URL + "/" + AS_DETAIL_URL + "/" + groupId +} + +func getAsGroupUri(groupId string) string { + return "/" + VERSION_2_URL + "/" + AS_GROUP_URL + "/" + groupId +} + +func getAsNodeUri() string { + return "/" + VERSION_1_URL + "/" + AS_NODE_URL +} diff --git a/bce-sdk-go/services/as/client_test.go b/bce-sdk-go/services/as/client_test.go new file mode 100644 index 0000000..11d44d0 --- /dev/null +++ b/bce-sdk-go/services/as/client_test.go @@ -0,0 +1,123 @@ +package as + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + asClient *Client + asConf *Conf +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` + InstanceId string `json:"InstanceId"` + UserId string `json:"UserId"` +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + asConf = &Conf{} + _ = decoder.Decode(asConf) + + asClient, _ = NewClient(asConf.AK, asConf.SK, asConf.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_GetAsGroupList(t *testing.T) { + req := &ListAsGroupRequest{ + GroupName: "test-name", + } + resp, err := asClient.ListAsGroup(req) + fmt.Println(resp) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAsGroupDetail(t *testing.T) { + req := &GetAsGroupRequest{ + GroupId: "asg-wqksXo95", + } + resp, err := asClient.GetAsGroup(req) + fmt.Println(resp) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAsNodeList(t *testing.T) { + req := &ListAsNodeRequest{ + GroupId: "asg-wqksXo95", + } + resp, err := asClient.ListAsNode(req) + fmt.Println(resp) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_IncreaseAs(t *testing.T) { + req := &IncreaseAsGroupRequest{ + GroupId: "asg-Hhm2ucIK", + Zone: []string{"zoneB"}, + NodeCount: 1, + } + err := asClient.IncreaseAsGroup(req) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DecreaseAs(t *testing.T) { + req := &DecreaseAsGroupRequest{ + GroupId: "asg-Hhm2ucIK", + Nodes: []string{"i-z0PXqFD3"}, + } + err := asClient.DecreaseAsGroup(req) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AdjustAs(t *testing.T) { + req := &AdjustAsGroupRequest{ + GroupId: "asg-Hhm2ucIK", + AdjustNum: 1, + } + err := asClient.AdjustAsGroup(req) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/as/model.go b/bce-sdk-go/services/as/model.go new file mode 100644 index 0000000..f64966c --- /dev/null +++ b/bce-sdk-go/services/as/model.go @@ -0,0 +1,155 @@ +package as + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type ListAsGroupRequest struct { + GroupName string `json:"groupName,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListAsGroupResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Result []GroupInfo `json:"result"` +} + +type AsGroupStatus string + +const ( + CREATING AsGroupStatus = "CREATING" + RUNNING AsGroupStatus = "RUNNING" + SCALING_UP AsGroupStatus = "SCALING_UP" + SCALING_DOWN AsGroupStatus = "SCALING_DOWN" + ATTACHING_NODE AsGroupStatus = "ATTACHING_NODE" + DETACHING_NODE AsGroupStatus = "DETACHING_NODE" + DELETING AsGroupStatus = "DELETING" + BINDING_BLB AsGroupStatus = "BINDING_BLB" + UNBINDING_BLB AsGroupStatus = "UNBINDING_BLB" + COOLDOWN AsGroupStatus = "COOLDOWN" + PAUSE AsGroupStatus = "PAUSE" + DELETED AsGroupStatus = "DELETED" +) + +type ZoneInfo struct { + Zone string `json:"zone,omitempty"` + SubnetID string `json:"subnetId,omitempty"` + SubnetUUID string `json:"subnetUuid,omitempty"` + SubnetName string `json:"subnetName,omitempty"` + SubnetType int16 `json:"subnetType,omitempty"` + NodeCount int `json:"nodeCount,omitempty"` +} + +type GroupConfig struct { + MinNodeNum int `json:"minNodeNum,omitempty"` + MaxNodeNum int `json:"maxNodeNum,omitempty"` + CooldownInSec int `json:"cooldownInSec,omitempty"` + ExpectNum int `json:"expectNum,omitempty"` +} + +type GroupInfo struct { + GroupId string `json:"groupId,omitempty"` + GroupName string `json:"groupName,omitempty"` + Region string `json:"region,omitempty"` + Status AsGroupStatus `json:"status,omitempty"` + VpcId string `json:"vpcId,omitempty"` + NodeNum int `json:"nodeNum,omitempty"` + CreateTime string `json:"createTime,omitempty"` + ZoneInfo []ZoneInfo `json:"zoneInfo,omitempty"` + Config GroupConfig `json:"config,omitempty"` + BlbId string `json:"blbId,omitempty"` +} + +type GetAsGroupRequest struct { + GroupId string `json:"groupId,omitempty"` +} + +type VpcInfo struct { + VpcId string `json:"vpcId,omitempty"` + VpcName string `json:"vpcName,omitempty"` + VpcUUID string `json:"vpcUuid,omitempty"` +} + +type GetAsGroupResponse struct { + GroupID string `json:"groupId,omitempty"` + GroupName string `json:"groupName,omitempty"` + Region string `json:"region,omitempty"` + Status string `json:"status,omitempty"` + VpcInfo VpcInfo `json:"vpcInfo,omitempty"` + ZoneInfo []ZoneInfo `json:"zoneInfo,omitempty"` + Config GroupConfig `json:"config,omitempty"` + BlbID string `json:"blbId,omitempty"` + NodeNum int `json:"nodeNum,omitempty"` + CreateTime string `json:"createTime,omitempty"` + RdsIDs string `json:"rdsIds,omitempty"` + ScsIDs string `json:"scsIds,omitempty"` + ExpansionStrategy string `json:"expansionStrategy,omitempty"` + ShrinkageStrategy string `json:"shrinkageStrategy,omitempty"` +} + +type IncreaseAsGroupRequest struct { + ClientToken string `json:"-"` + GroupId string `json:"groupId,omitempty"` + NodeCount int `json:"nodeCount,omitempty"` + Zone []string `json:"zone,omitempty"` + ExpansionStrategy string `json:"expansionStrategy,omitempty"` +} + +type DecreaseAsGroupRequest struct { + ClientToken string `json:"-"` + GroupId string `json:"groupId,omitempty"` + Nodes []string `json:"nodes,omitempty"` +} + +type AdjustAsGroupRequest struct { + ClientToken string `json:"-"` + GroupId string `json:"groupId,omitempty"` + AdjustNum int `json:"adjustNum,omitempty"` +} + +type ListAsNodeRequest struct { + GroupId string `json:"groupId,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type AsEipModel struct { + BandwidthInMbps int `json:"bandwidthInMbps,omitempty"` + EipId string `json:"eipId,omitempty"` + Address string `json:"address,omitempty"` + EipStatus string `json:"eipStatus,omitempty"` + EipAllocationId string `json:"eipAllocationId,omitempty"` +} + +type NodeModel struct { + InstanceId string `json:"instanceId,omitempty"` + InstanceUuid string `json:"instanceUuid,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + FloatingIp string `json:"floatingIp,omitempty"` + InternalIp string `json:"internalIp,omitempty"` + Status string `json:"status,omitempty"` + Payment string `json:"payment,omitempty"` + CpuCount int64 `json:"cpuCount,omitempty"` + MemoryCapacityInGB int64 `json:"memoryCapacityInGB,omitempty"` + InstanceType string `json:"instanceType,omitempty"` + SysDiskInGB int `json:"sysDiskInGB,omitempty"` + CreateTime string `json:"createTime,omitempty"` + Eip AsEipModel `json:"eip,omitempty"` + SubnetType string `json:"subnetType,omitempty"` + IsProtected bool `json:"isProtected,omitempty"` + NodeType string `json:"nodeType,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + GroupId string `json:"groupId,omitempty"` +} + +type ListAsNodeResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Result []NodeModel `json:"result,omitempty"` +} diff --git a/bce-sdk-go/services/bbc/cds.go b/bce-sdk-go/services/bbc/cds.go new file mode 100644 index 0000000..fe6e616 --- /dev/null +++ b/bce-sdk-go/services/bbc/cds.go @@ -0,0 +1,57 @@ +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +// ListCDSVolume - list all cds volumes with the given parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the optional arguments to list cds volumes +// +// RETURNS: +// - *ListCDSVolumeResult: the result of cds volume list +// - error: nil if success otherwise the specific error +func ListCDSVolume(cli bce.Client, queryArgs *ListCDSVolumeArgs) (*ListCDSVolumeResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.InstanceId) != 0 { + req.SetParam("instanceId", queryArgs.InstanceId) + } + if len(queryArgs.ZoneName) != 0 { + req.SetParam("zoneName", queryArgs.ZoneName) + } + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListCDSVolumeResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bbc/client.go b/bce-sdk-go/services/bbc/client.go new file mode 100644 index 0000000..fd8a1ce --- /dev/null +++ b/bce-sdk-go/services/bbc/client.go @@ -0,0 +1,1352 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BBC service + +// Package bbc defines the BBC services of BCE. The supported APIs are all defined in sub-package +package bbc + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bcc/api" +) + +const DEFAULT_SERVICE_DOMAIN = "bbc." + bce.DEFAULT_REGION + ".baidubce.com" + +// Client of BBC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the BBC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endPoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endPoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// CreateInstance - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *CreateInstanceArgs) (*CreateInstanceResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + if args.RootDiskSizeInGb <= 0 { + args.RootDiskSizeInGb = 20 + } + + if args.PurchaseCount < 1 { + args.PurchaseCount = 1 + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateInstance(c, args, body) +} + +// CreateInstance - create an instance with specific parameters and support the passing in of label +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific erro +func (c *Client) CreateInstanceByLabel(args *CreateSpecialInstanceArgs) (*CreateInstanceResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + if args.RootDiskSizeInGb <= 0 { + args.RootDiskSizeInGb = 20 + } + + if args.PurchaseCount < 1 { + args.PurchaseCount = 1 + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateInstanceByLabel(c, args, body) +} + +// CreateInstanceByLabelV2 - create an instance with specific parameters and support the passing in of label +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific erro +func (c *Client) CreateInstanceByLabelV2(argsV2 *CreateSpecialInstanceArgsV2) (*CreateInstanceResult, error) { + if len(argsV2.AdminPass) > 0 { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return nil, err + } + argsV2.AdminPass = cryptedPass + } + + if argsV2.RootDiskSizeInGb <= 0 { + argsV2.RootDiskSizeInGb = 20 + } + + if argsV2.PurchaseCount < 1 { + argsV2.PurchaseCount = 1 + } + + defaultTrue := true + defaultFalse := false + if argsV2.EnableNuma == nil { + argsV2.EnableNuma = &defaultFalse + } + if argsV2.EnableHt == nil { + argsV2.EnableHt = &defaultTrue + } + jsonBytes, err := json.Marshal(argsV2) + if err != nil { + return nil, err + } + args := &CreateSpecialInstanceArgs{} + err = json.Unmarshal(jsonBytes, args) + if err != nil { + return nil, err + } + args.ClientToken = argsV2.ClientToken + + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateInstanceByLabel(c, args, body) +} + +// ListInstances - list all instance with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance +// +// RETURNS: +// - *ListInstanceResult: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) ListInstances(args *ListInstancesArgs) (*ListInstancesResult, error) { + return ListInstances(c, args) +} + +// GetInstanceDetail - get a specific instance detail info +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - *GetInstanceDetailResult: the result of get instance detail info +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceDetail(instanceId string) (*InstanceModel, error) { + return GetInstanceDetail(c, instanceId) +} + +func (c *Client) GetInstanceDetailWithDeploySet(instanceId string, isDeploySet bool) (*InstanceModel, error) { + return GetInstanceDetailWithDeploySet(c, instanceId, isDeploySet) +} + +func (c *Client) GetInstanceDetailWithDeploySetAndFailed(instanceId string, + isDeploySet bool, containsFailed bool) (*InstanceModel, error) { + return GetInstanceDetailWithDeploySetAndFailed(c, instanceId, isDeploySet, containsFailed) +} + +// StartInstance - start an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StartInstance(instanceId string) error { + return StartInstance(c, instanceId) +} + +// StopInstance - stop an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - forceStop: choose to force stop an instance or not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StopInstance(instanceId string, forceStop bool) error { + args := &StopInstanceArgs{ + ForceStop: forceStop, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return StopInstance(c, instanceId, body) +} + +// RebootInstance - restart an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - forceStop: choose to force stop an instance or not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(instanceId string, forceStop bool) error { + args := &StopInstanceArgs{ + ForceStop: forceStop, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return RebootInstance(c, instanceId, body) +} + +// ModifyInstanceName - modify an instance's name +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstanceName(instanceId string, args *ModifyInstanceNameArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return ModifyInstanceName(c, instanceId, body) +} + +// ModifyInstanceDesc - modify an instance's description +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's description +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstanceDesc(instanceId string, args *ModifyInstanceDescArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + clientToken := args.ClientToken + + return ModifyInstanceDesc(c, instanceId, clientToken, body) +} + +// RebuildInstance - rebuild an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - isPreserveData: choose to preserve data or not +// - args: the arguments to rebuild an instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebuildInstance(instanceId string, isPreserveData bool, args *RebuildInstanceArgs) error { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return err + } + args.AdminPass = cryptedPass + + args.IsPreserveData = isPreserveData + + if !isPreserveData && args.SysRootSize <= 0 { + args.SysRootSize = 20 + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return RebuildInstance(c, instanceId, body) +} + +// GetInstanceVNC - get an instance's VNC url +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - *api.GetInstanceVNCResult: the result of get instance's VNC url +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceVNC(instanceId string) (*GetInstanceVNCResult, error) { + return GetInstanceVNC(c, instanceId) +} + +// RebuildBatchInstance - batch rebuild instances +// +// PARAMS: +// - args: the arguments to batch rebuild instances +// +// RETURNS: +// - *BatchRebuildResponse: the result of batch rebuild the instances +// - error: nil if success otherwise the specific error +func (c *Client) BatchRebuildInstances(args *RebuildBatchInstanceArgs) (*BatchRebuildResponse, error) { + if len(args.AdminPass) > 0 { + encryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + args.AdminPass = encryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return BatchRebuildInstances(c, body) +} + +// BatchRebuildInstancesV2 - batch rebuild instances +// +// PARAMS: +// - args: the arguments to batch rebuild instances +// +// RETURNS: +// - *BatchRebuildResponse: the result of batch rebuild the instances +// - error: nil if success otherwise the specific error +func (c *Client) BatchRebuildInstancesV2(argsV2 *RebuildBatchInstanceArgsV2) (*BatchRebuildResponse, error) { + if len(argsV2.AdminPass) > 0 { + encryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return nil, err + } + argsV2.AdminPass = encryptedPass + } + defaultTrue := true + if argsV2.IsPreserveData == nil { + argsV2.IsPreserveData = &defaultTrue + } + jsonBytes, jsonErr := json.Marshal(argsV2) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return BatchRebuildInstances(c, body) +} + +// InstancePurchaseReserved - purchase reserve an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to purchase reserved an instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstancePurchaseReserved(instanceId string, args *PurchaseReservedArgs) error { + // this api only support Prepaid instance + args.Billing.PaymentTiming = PaymentTimingPrePaid + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return InstancePurchaseReserved(c, instanceId, args.ClientToken, body) +} + +// DeleteInstance - delete an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstance(instanceId string) error { + return DeleteInstance(c, instanceId) +} + +// ListInstances - list all instance with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance +// +// RETURNS: +// - *ListInstanceResult: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycledInstances(args *ListRecycledInstancesArgs) (*ListRecycledInstancesResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return ListRecycledInstances(c, body) +} + +// ListInstances - list all instance with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance +// +// RETURNS: +// - *ListInstanceResult: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) RecoveryInstances(args *RecoveryInstancesArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return RecoveryInstances(c, body) +} + +// DeleteInstance - delete an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstances(args *DeleteInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return DeleteInstances(c, body) +} + +// ModifyInstancePassword - modify an instance's password +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstancePassword(instanceId string, args *ModifyInstancePasswordArgs) error { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return err + } + args.AdminPass = cryptedPass + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return ModifyInstancePassword(c, instanceId, body) +} + +// InstanceChangeSubnet - change an instance's subnet +// +// PARAMS: +// - args: the arguments to change an instance's subnet +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceChangeSubnet(args *InstanceChangeSubnetArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return InstanceChangeSubnet(c, body) +} + +// InstanceChangeVpc - change an instance's vpc +// +// PARAMS: +// - args: the arguments to change an instance's vpc +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceChangeVpc(args *InstanceChangeVpcArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return InstanceChangeVpc(c, body) +} + +// GetVpcSubnet - get multi instances vpc and subnet +// +// PARAMS: +// - args: the instanceId of bbc instances +// +// RETURNS: +// - *GetVpcSubnetResult: result of vpc and subnet +// - error: nil if success otherwise the specific error +func (c *Client) GetVpcSubnet(args *GetVpcSubnetArgs) (*GetVpcSubnetResult, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return GetVpcSubnet(c, body) +} + +// BatchAddIP - Add ips to instance +// +// PARAMS: +// - args: the arguments to add ips to bbc instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchAddIP(args *BatchAddIpArgs) (*BatchAddIpResponse, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return BatchAddIp(c, args, body) +} + +// BatchDelIP - Delete ips of instance +// +// PARAMS: +// - args: the arguments to add ips to bbc instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDelIP(args *BatchDelIpArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return BatchDelIp(c, args, body) +} + +// BatchAddIPCrossSubnet - Add ips to instance cross subnet +// +// PARAMS: +// - args: the arguments to add ips to bbc instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchAddIPCrossSubnet(args *BatchAddIpCrossSubnetArgs) (*BatchAddIpResponse, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return BatchAddIpCrossSubnet(c, args, body) +} + +// BindTags - bind an instance tags +// +// PARAMS: +// - instanceId: the id of the instance +// - args: tags of an instance to bind +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindTags(instanceId string, args *BindTagsArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return BindTags(c, instanceId, body) +} + +// UnbindTags - unbind an instance tags +// +// PARAMS: +// - instanceId: the id of the instance +// - args: tags of an instance to unbind +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnbindTags(instanceId string, args *UnbindTagsArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return UnbindTags(c, instanceId, body) +} + +// ListFlavors - list all available flavors +// +// RETURNS: +// - *ListFlavorsResult: the result of list all flavors +// - error: nil if success otherwise the specific error +func (c *Client) ListFlavors() (*ListFlavorsResult, error) { + return ListFlavors(c) +} + +// GetFlavorDetail - get details of the specified flavor +// +// PARAMS: +// - flavorId: the id of the flavor +// +// RETURNS: +// - *GetFlavorDetailResult: the detail of the specified flavor +// - error: nil if success otherwise the specific error +func (c *Client) GetFlavorDetail(flavorId string) (*GetFlavorDetailResult, error) { + return GetFlavorDetail(c, flavorId) +} + +// GetFlavorRaid - get the RAID detail and disk size of the specified flavor +// +// PARAMS: +// - flavorId: the id of the flavor +// +// RETURNS: +// - *GetFlavorRaidResult: the detail of the raid of the specified flavor +// - error: nil if success otherwise the specific error +func (c *Client) GetFlavorRaid(flavorId string) (*GetFlavorRaidResult, error) { + return GetFlavorRaid(c, flavorId) +} + +// CreateImageFromInstanceId - create image from specified instance +// +// PARAMS: +// - args: the arguments to create image +// +// RETURNS: +// - *CreateImageResult: the result of create Image +// - error: nil if success otherwise the specific error +func (c *Client) CreateImageFromInstanceId(args *CreateImageArgs) (*CreateImageResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateImageFromInstanceId(c, args.ClientToken, body) +} + +// ListImage - list all images +// +// PARAMS: +// - args: the arguments to list all images +// +// RETURNS: +// - *ListImageResult: the result of list all images +// - error: nil if success otherwise the specific error +func (c *Client) ListImage(args *ListImageArgs) (*ListImageResult, error) { + return ListImage(c, args) +} + +// GetImageDetail - get an image's detail info +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetImageDetail(imageId string) (*GetImageDetailResult, error) { + return GetImageDetail(c, imageId) +} + +// DeleteImage - delete an image +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteImage(imageId string) error { + return DeleteImage(c, imageId) +} + +// GetOperationLog - get operation log +// +// PARAMS: +// - args: the arguments to get operation log +// +// RETURNS: +// - *GetOperationLogResult: results of getting operation log +// - error: nil if success otherwise the specific error +func (c *Client) GetOperationLog(args *GetOperationLogArgs) (*GetOperationLogResult, error) { + return GetOperationLog(c, args) +} + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - args: the arguments to create a deploy set +// +// RETURNS: +// - *CreateDeploySetResult: results of creating a deploy set +// - error: nil if success otherwise the specific error +func (c *Client) CreateDeploySet(args *CreateDeploySetArgs) (*CreateDeploySetResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateDeploySet(c, args.ClientToken, body) +} + +// ListDeploySets - list all deploy sets +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func (c *Client) ListDeploySets() (*ListDeploySetsResult, error) { + return ListDeploySets(c) +} + +// ListDeploySets - list all deploy sets +// PARAMS: +// - args: the arguments to filter +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func (c *Client) ListDeploySetsPage(args *ListDeploySetsArgs) (*ListDeploySetsResult, error) { + return ListDeploySetsPage(c, args) +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *GetDeploySetResult: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) GetDeploySet(deploySetId string) (*DeploySetResult, error) { + return GetDeploySet(c, deploySetId) +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDeploySet(deploySetId string) error { + return DeleteDeploySet(c, deploySetId) +} + +// BindSecurityGroups - Bind Security Groups +// +// PARAMS: +// - args: the arguments of bind security groups +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindSecurityGroups(args *BindSecurityGroupsArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return BindSecurityGroups(c, body) +} + +// UnBindSecurityGroups - UnBind Security Groups +// +// PARAMS: +// - args: the arguments of bind security groups +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindSecurityGroups(args *UnBindSecurityGroupsArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return UnBindSecurityGroups(c, body) +} + +// ListFlavorZones - get the zone list of the specified flavor which can buy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorId: the id of the flavor +// +// RETURNS: +// - *ListZonesResult: the list of zone names +// - error: nil if success otherwise the specific error +func (c *Client) ListFlavorZones(args *ListFlavorZonesArgs) (*ListZonesResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return ListFlavorZones(c, body) +} + +// ListZoneFlavors - get the flavor detail of the specific zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: the zone name +// +// RETURNS: +// - *ListZoneResult: flavor detail of the specific zone +// - error: nil if success otherwise the specific error +func (c *Client) ListZoneFlavors(args *ListZoneFlavorsArgs) (*ListFlavorInfosResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return ListZoneFlavors(c, body) +} + +// GetCommonImage - get common flavor image list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorIds: the specific flavorIds, can be nil +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetCommonImage(args *GetFlavorImageArgs) (*GetImagesResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return GetCommonImage(c, body) +} + +// GetCustomImage - get user onwer flavor image list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorIds: the specific flavorIds, can be nil +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetCustomImage(args *GetFlavorImageArgs) (*GetImagesResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return GetCustomImage(c, body) +} + +// ShareImage - share an image +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to share an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ShareImage(imageId string, args *SharedUser) error { + return ShareImage(c, imageId, args) +} + +// UnShareImage - cancel share an image +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to cancel share an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnShareImage(imageId string, args *SharedUser) error { + return UnShareImage(c, imageId, args) +} + +// GetImageSharedUser - get user list use this image +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - *api.GetImageSharedUserResult: the result of user list +// - error: nil if success otherwise the specific error +func (c *Client) GetImageSharedUser(imageId string) (*GetImageSharedUserResult, error) { + return GetImageSharedUser(c, imageId) +} + +// RemoteCopyImage - copy a bbc image from other region +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to remote copy an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RemoteCopyImage(imageId string, args *RemoteCopyImageArgs) error { + return RemoteCopyImage(c, imageId, args) +} + +// RemoteCopyImageReturnImageIds - copy an image from other region +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to remote copy an image +// +// RETURNS: +// - imageIds of destination region if success otherwise the specific error +func (c *Client) RemoteCopyImageReturnImageIds(imageId string, args *RemoteCopyImageArgs) (*RemoteCopyImageResult, error) { + return RemoteCopyImageReturnImageIds(c, imageId, args) +} + +// CancelRemoteCopyImage - cancel a copy image from other region operation +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelRemoteCopyImage(imageId string) error { + return CancelRemoteCopyImage(c, imageId) +} + +// GetInstanceEni - get the eni of the bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the bbc instance id +// RETURNS: +// - error: nil if success otherwise the specific error + +func (c *Client) GetInstanceEni(instanceId string) (*GetInstanceEniResult, error) { + return GetInstanceEni(c, instanceId) +} + +func (c *Client) GetInstanceCreateStock(args *CreateInstanceStockArgs) (*InstanceStockResult, error) { + return GetInstanceCreateStock(c, args) +} + +func (c *Client) GetSimpleFlavor(args *GetSimpleFlavorArgs) (*SimpleFlavorResult, error) { + return GetSimpleFlavor(c, args) +} + +func (c *Client) GetInstancePirce(args *InstancePirceArgs) (*InstancePirceResult, error) { + return GetInstancePirce(c, args) +} + +func (c *Client) ListRepairTasks(args *ListRepairTaskArgs) (*ListRepairTaskResult, error) { + return ListRepairTasks(c, args) +} + +func (c *Client) ListClosedRepairTasks(args *ListClosedRepairTaskArgs) (*ListClosedRepairTaskResult, error) { + return ListClosedRepairTasks(c, args) +} + +func (c *Client) GetRepairTaskDetail(taskId string) (*GetRepairTaskResult, error) { + return GetTaskDetail(c, taskId) +} + +func (c *Client) AuthorizeRepairTask(args *TaskIdArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return AuthorizeRepairTask(c, body) +} + +func (c *Client) UnAuthorizeRepairTask(args *TaskIdArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return UnAuthorizeRepairTask(c, body) +} + +func (c *Client) ConfirmRepairTask(args *TaskIdArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return ConfirmRepairTask(c, body) +} + +func (c *Client) DisConfirmRepairTask(args *DisconfirmTaskArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return DisConfirmRepairTask(c, body) +} + +func (c *Client) GetRepairTaskRecord(args *TaskIdArgs) (*GetRepairRecords, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return GetRepairTaskReocrd(c, body) +} + +// ListRule - list the repair plat rules +// +// PARAMS: +// - args: the arguments of listing the repair plat rules +// +// RETURNS: +// - *ListRuleResult: results of listing the repair plat rules +// - error: nil if success otherwise the specific error +func (c *Client) ListRule(args *ListRuleArgs) (*ListRuleResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return ListRule(c, body) +} + +// GetRuleDetail - list the repair plat rules +// +// PARAMS: +// - ruleId: the specified rule id +// +// RETURNS: +// - *Rule: results of the specified repair plat rule +// - error: nil if success otherwise the specific error +func (c *Client) GetRuleDetail(ruleId string) (*Rule, error) { + return GetRuleDetail(c, ruleId) +} + +// CreateRule - create the repair plat rule +// +// PARAMS: +// - args: the arguments of creating the repair plat rule +// +// RETURNS: +// - *CreateRuleResult: results of the id of the repair plat rule which is created +// - error: nil if success otherwise the specific error +func (c *Client) CreateRule(args *CreateRuleArgs) (*CreateRuleResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return CreateRule(c, body) +} + +// DeleteRule - delete the repair plat rule +// +// PARAMS: +// - args: the arguments of deleting the repair plat rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRule(args *DeleteRuleArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return DeleteRule(c, body) +} + +// DisableRule - disable the repair plat rule +// +// PARAMS: +// - args: the arguments of disabling the repair plat rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DisableRule(args *DisableRuleArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return DisableRule(c, body) +} + +// EnableRule - enable the repair plat rule +// +// PARAMS: +// - args: the arguments of enabling the repair plat rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EnableRule(args *EnableRuleArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return EnableRule(c, body) +} + +// BatchCreateAutoRenewRules - Batch Create AutoRenew Rules +// +// PARAMS: +// - args: the arguments to batch create autorenew rules +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchCreateAutoRenewRules(args *BbcCreateAutoRenewArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return BatchCreateAutoRenewRules(c, body) +} + +// BatchDeleteAutoRenewRules - Batch Delete AutoRenew Rules +// +// PARAMS: +// - args: the arguments to batch delete autorenew rules +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDeleteAutoRenewRules(args *BbcDeleteAutoRenewArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return BatchDeleteAutoRenewRules(c, body) +} + +func (c *Client) DeleteInstanceIngorePayment(args *DeleteInstanceIngorePaymentArgs) (*DeleteInstanceResult, error) { + return DeleteBbcIngorePayment(c, args) +} + +// DeleteRecycledInstance - delete an recycled instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecycledInstance(instanceId string) error { + return DeleteRecycledInstance(c, instanceId) +} + +// ListCDSVolume - list all cds volume with the specific parameters +// +// PARAMS: +// - args: the arguments to list all cds +// +// RETURNS: +// - *api.ListCDSVolumeResult: the result of list all CDS volume +// - error: nil if success otherwise the specific error +func (c *Client) ListCDSVolume(queryArgs *ListCDSVolumeArgs) (*ListCDSVolumeResult, error) { + return ListCDSVolume(c, queryArgs) +} + +// GetBbcStockWithDeploySet - get the bbc's stock with deploySet +// +// RETURNS: +// - *GetBbcStocksResult: the result of the bbc's stock +// - error: nil if success otherwise the specific error +func (c *Client) GetBbcStockWithDeploySet(args *GetBbcStockArgs) (*GetBbcStocksResult, error) { + return GetStockWithDeploySet(c, args) +} + +// ListInstanceByInstanceIds - list bbc detail info by instanceId +// +// RETURNS: +// - *ListInstancesResult: the result of list bbc detail info +// - error: nil if success otherwise the specific error +func (c *Client) ListInstanceByInstanceIds(args *ListInstanceByInstanceIdArgs) (*ListInstancesResult, error) { + return ListInstanceByInstanceIds(c, args) +} diff --git a/bce-sdk-go/services/bbc/client_test.go b/bce-sdk-go/services/bbc/client_test.go new file mode 100644 index 0000000..5f66808 --- /dev/null +++ b/bce-sdk-go/services/bbc/client_test.go @@ -0,0 +1,923 @@ +package bbc + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/model" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BBC_CLIENT *Client + BBC_TestBbcId string + BBC_TestImageId string + BBC_TestFlavorId string + BBC_TestRaidId string + BBC_TestZoneName string + BBC_TestSubnetId string + BBC_TestName string + BBC_TestAdminPass string + BBC_TestDeploySetId string + BBC_TestClientToken string + BBC_TestSecurityGroupId string + BBC_TestTaskId string + BBC_TestErrResult string + BBC_TestRuleId string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 6; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fmt.Println(conf) + fp, err := os.Open(conf) + if err != nil { + fmt.Println("config json file of ak/sk not given: ", conf) + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + BBC_TestFlavorId = "flavor-id" + BBC_TestImageId = "image-id" + BBC_TestRaidId = "raid-id" + BBC_TestZoneName = "zone-name" + BBC_TestSubnetId = "subnet-id" + BBC_TestName = "sdkTest" + BBC_TestAdminPass = "123@adminPass" + BBC_TestDeploySetId = "deployset-id" + BBC_TestBbcId = "bbc_id" + BBC_TestSecurityGroupId = "bbc-security-group-id" + BBC_TestTaskId = "task-id" + BBC_TestErrResult = "err-result" + BBC_TestRuleId = "rule-id" + BBC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) + //log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestCreateInstance(t *testing.T) { + InternalIps := []string{"ip"} + createInstanceArgs := &CreateInstanceArgs{ + FlavorId: BBC_TestFlavorId, + ImageId: BBC_TestImageId, + RaidId: BBC_TestRaidId, + RootDiskSizeInGb: 40, + PurchaseCount: 1, + AdminPass: "AdminPass", + ZoneName: BBC_TestZoneName, + SubnetId: BBC_TestSubnetId, + SecurityGroupId: BBC_TestSecurityGroupId, + ClientToken: BBC_TestClientToken, + Billing: Billing{ + PaymentTiming: PaymentTimingPostPaid, + }, + DeploySetId: BBC_TestDeploySetId, + Name: BBC_TestName, + EnableNuma: false, + InternalIps: InternalIps, + Tags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, + } + res, err := BBC_CLIENT.CreateInstance(createInstanceArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateSpecialInstance(t *testing.T) { + InternalIps := []string{"ip"} + createSpecialInstanceArgs := &CreateSpecialInstanceArgs{ + FlavorId: BBC_TestFlavorId, + ImageId: BBC_TestImageId, + RaidId: BBC_TestRaidId, + RootDiskSizeInGb: 40, + PurchaseCount: 1, + AdminPass: "AdminPass", + ZoneName: BBC_TestZoneName, + SubnetId: BBC_TestSubnetId, + SecurityGroupId: BBC_TestSecurityGroupId, + ClientToken: BBC_TestClientToken, + Billing: Billing{ + PaymentTiming: PaymentTimingPostPaid, + }, + DeploySetId: BBC_TestDeploySetId, + Name: BBC_TestName, + EnableNuma: false, + InternalIps: InternalIps, + Tags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, + LabelConstraints: []LabelConstraint{{ + Key: "feaA", + Operator: LabelOperatorExist, + }, { + Key: "feaB", + Value: "typeB", + Operator: LabelOperatorNotEqual, + }}, + } + // 将使用『没有 feaC 这个 label』且『feaD 这个 label 的值为 typeD』的测试机创建实例 + res, err := BBC_CLIENT.CreateInstanceByLabel(createSpecialInstanceArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateSpecialInstanceV2(t *testing.T) { + InternalIps := []string{"ip"} + createSpecialInstanceArgs := &CreateSpecialInstanceArgsV2{ + FlavorId: BBC_TestFlavorId, + ImageId: BBC_TestImageId, + RaidId: BBC_TestRaidId, + RootDiskSizeInGb: 40, + PurchaseCount: 1, + AdminPass: "AdminPass", + ZoneName: BBC_TestZoneName, + SubnetId: BBC_TestSubnetId, + SecurityGroupId: BBC_TestSecurityGroupId, + ClientToken: BBC_TestClientToken, + Billing: Billing{ + PaymentTiming: PaymentTimingPostPaid, + }, + DeploySetId: BBC_TestDeploySetId, + Name: BBC_TestName, + InternalIps: InternalIps, + Tags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, + LabelConstraints: []LabelConstraint{{ + Key: "feaA", + Operator: LabelOperatorExist, + }, { + Key: "feaB", + Value: "typeB", + Operator: LabelOperatorNotEqual, + }}, + } + // 将使用『没有 feaC 这个 label』且『feaD 这个 label 的值为 typeD』的测试机创建实例 + res, err := BBC_CLIENT.CreateInstanceByLabelV2(createSpecialInstanceArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListInstances(t *testing.T) { + listArgs := &ListInstancesArgs{ + MaxKeys: 500, + } + res, err := BBC_CLIENT.ListInstances(listArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetInstanceDetail(t *testing.T) { + res, err := BBC_CLIENT.GetInstanceDetail("i-4PvLVv37") + fmt.Println(res.Status) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetInstanceDetailWithDeploySetAndFailed(t *testing.T) { + res, err := BBC_CLIENT.GetInstanceDetailWithDeploySetAndFailed(BBC_TestBbcId, false, true) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestStopInstance(t *testing.T) { + err := BBC_CLIENT.StopInstance(BBC_TestBbcId, false) + ExpectEqual(t.Errorf, err, nil) +} + +func TestStartInstance(t *testing.T) { + err := BBC_CLIENT.StartInstance(BBC_TestBbcId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRebootInstance(t *testing.T) { + err := BBC_CLIENT.RebootInstance(BBC_TestBbcId, true) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstanceName(t *testing.T) { + modifyInstanceNameArgs := &ModifyInstanceNameArgs{ + Name: "new_bbc_name", + } + err := BBC_CLIENT.ModifyInstanceName(BBC_TestBbcId, modifyInstanceNameArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstanceDesc(t *testing.T) { + modifyInstanceDescArgs := &ModifyInstanceDescArgs{ + Description: "new_bbc_description_02", + ClientToken: "be31b98c-5e42-4838-9230-9be700de5a20", + } + err := BBC_CLIENT.ModifyInstanceDesc(BBC_TestBbcId, modifyInstanceDescArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRebuildInstance(t *testing.T) { + rebuildArgs := &RebuildInstanceArgs{ + ImageId: BBC_TestImageId, + AdminPass: BBC_TestAdminPass, + IsPreserveData: true, + RaidId: BBC_TestRaidId, + SysRootSize: 20, + RootPartitionType: "xfs", + DataPartitionType: "xfs", + } + err := BBC_CLIENT.RebuildInstance(BBC_TestBbcId, true, rebuildArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchRebuildInstances(t *testing.T) { + rebuildArgs := &RebuildBatchInstanceArgs{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + InstanceIds: []string{"BBC_TestBbcId"}, + IsPreserveData: true, + RaidId: BBC_TestRaidId, + SysRootSize: 20, + RootPartitionType: "xfs", + DataPartitionType: "xfs", + } + result, err := BBC_CLIENT.BatchRebuildInstances(rebuildArgs) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchRebuildInstancesV2(t *testing.T) { + argsV2 := &RebuildBatchInstanceArgsV2{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + InstanceIds: []string{"BBC_TestBbcId"}, + RaidId: BBC_TestRaidId, + SysRootSize: 20, + RootPartitionType: "xfs", + DataPartitionType: "xfs", + } + result, err := BBC_CLIENT.BatchRebuildInstancesV2(argsV2) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestReleaseInstance(t *testing.T) { + err := BBC_CLIENT.DeleteInstance(BBC_TestBbcId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstancePassword(t *testing.T) { + modifyInstancePasswordArgs := &ModifyInstancePasswordArgs{ + AdminPass: BBC_TestAdminPass, + } + err := BBC_CLIENT.ModifyInstancePassword(BBC_TestBbcId, modifyInstancePasswordArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetVpcSubnet(t *testing.T) { + getVpcSubnetArgs := &GetVpcSubnetArgs{ + BbcIds: []string{BBC_TestBbcId}, + } + result, err := BBC_CLIENT.GetVpcSubnet(getVpcSubnetArgs) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchAddIp(t *testing.T) { + privateIps := []string{"192.168.200.25"} + batchAddIpArgs := &BatchAddIpArgs{ + InstanceId: BBC_TestBbcId, + PrivateIps: privateIps, + ClientToken: "be31b98c-5e41-4838-9230-9be700de5a20", + } + result, err := BBC_CLIENT.BatchAddIP(batchAddIpArgs) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchAddIpCrossSubnet(t *testing.T) { + batchAddIpCrossSubnetArgs := &BatchAddIpCrossSubnetArgs{ + InstanceId: BBC_TestBbcId, + SingleEniAndSubentIps: []SingleEniAndSubentIp{ + { + EniId: "eni-cc31j8i1nq5f", + IpAndSubnets: []IpAndSubnet{ + { + PrivateIp: "192.168.0.6", + SubnetId: "sbn-af5iegk24se1", + }, + }, + }, + }, + ClientToken: "be31b98c-5e41-4838-9230-9be700de5a20", + } + result, err := BBC_CLIENT.BatchAddIPCrossSubnet(batchAddIpCrossSubnetArgs) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchDelIp(t *testing.T) { + privateIps := []string{"192.168.1.25"} + batchDelIpArgs := &BatchDelIpArgs{ + InstanceId: BBC_TestBbcId, + PrivateIps: privateIps, + ClientToken: "be31b98c-5e41-4e38-9230-9be700de5120", + } + err := BBC_CLIENT.BatchDelIP(batchDelIpArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBindTags(t *testing.T) { + bindTagsArgs := &BindTagsArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "BBCTestKey", + TagValue: "BBCTestValue", + }, + }, + } + err := BBC_CLIENT.BindTags(BBC_TestBbcId, bindTagsArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnbindTags(t *testing.T) { + unbindTagsArgs := &UnbindTagsArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "BCC", + TagValue: "aaa", + }, + }, + } + err := BBC_CLIENT.UnbindTags(BBC_TestBbcId, unbindTagsArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListFlavors(t *testing.T) { + res, err := BBC_CLIENT.ListFlavors() + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetFlavorDetail(t *testing.T) { + testFlavorId := BBC_TestFlavorId + rep, err := BBC_CLIENT.GetFlavorDetail(testFlavorId) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetFlavorRaid(t *testing.T) { + testFlavorId := "BBC-G4-01S" + rep, err := BBC_CLIENT.GetFlavorRaid(testFlavorId) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateImageFromInstanceId(t *testing.T) { + testInstanceId := BBC_TestBbcId + testImageName := "testCreateImage" + queryArgs := &CreateImageArgs{ + ImageName: testImageName, + InstanceId: testInstanceId, + } + rep, err := BBC_CLIENT.CreateImageFromInstanceId(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListImage(t *testing.T) { + queryArgs := &ListImageArgs{} + rep, err := BBC_CLIENT.ListImage(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetImageDetail(t *testing.T) { + testImageId := "" + rep, err := BBC_CLIENT.GetImageDetail(testImageId) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteImage(t *testing.T) { + testImageId := BBC_TestImageId + err := BBC_CLIENT.DeleteImage(testImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetOperationLog(t *testing.T) { + queryArgs := &GetOperationLogArgs{ + StartTime: "2021-03-28T15:00:27Z", + EndTime: "2021-03-30T15:00:27Z", + } + rep, err := BBC_CLIENT.GetOperationLog(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateDeploySet(t *testing.T) { + testDeploySetName := "testName" + testDeployDesc := "testDesc" + testConcurrency := 1 + testStrategy := "tor_ha" + queryArgs := &CreateDeploySetArgs{ + Strategy: testStrategy, + Concurrency: testConcurrency, + Name: testDeploySetName, + Desc: testDeployDesc, + } + rep, err := BBC_CLIENT.CreateDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListDeploySets(t *testing.T) { + rep, err := BBC_CLIENT.ListDeploySets() + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListDeploySetsPage(t *testing.T) { + queryArgs := &ListDeploySetsArgs{ + Strategy: "TOR_HA", // RACK_HA or TOR_HA + MaxKeys: 100, + Marker: "your-marker", + } + rep, err := BBC_CLIENT.ListDeploySetsPage(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetDeploySet(t *testing.T) { + testDeploySetID := BBC_TestDeploySetId + rep, err := BBC_CLIENT.GetDeploySet(testDeploySetID) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteDeploySet(t *testing.T) { + testDeleteDeploySetId := BBC_TestDeploySetId + err := BBC_CLIENT.DeleteDeploySet(testDeleteDeploySetId) + fmt.Println(err) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBindSecurityGroups(t *testing.T) { + instanceIds := []string{""} + sg := []string{""} + args := &BindSecurityGroupsArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: sg, + } + err := BBC_CLIENT.BindSecurityGroups(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnBindSecurityGroups(t *testing.T) { + args := &UnBindSecurityGroupsArgs{ + InstanceId: "", + SecurityGroupId: "", + } + err := BBC_CLIENT.UnBindSecurityGroups(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetFlavorZone(t *testing.T) { + flavorId := "BBC-G3-01" + queryArgs := &ListFlavorZonesArgs{ + FlavorId: flavorId, + } + if res, err := BBC_CLIENT.ListFlavorZones(queryArgs); err != nil { + fmt.Println("Get flavor zoneName failed: ", err) + } else { + fmt.Println("Get flavor zoneName success, result: ", res) + } +} + +func TestListZoneFlavors(t *testing.T) { + zoneName := "cn-bj-b" + queryArgs := &ListZoneFlavorsArgs{ + ZoneName: zoneName, + } + if res, err := BBC_CLIENT.ListZoneFlavors(queryArgs); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) + } else { + fmt.Println("Get the specific zone flavor success, result: ", res) + } +} + +func TestGetCommonImage(t *testing.T) { + flavorIds := []string{"BBC-S3-02"} + queryArgs := &GetFlavorImageArgs{ + FlavorIds: flavorIds, + } + if res, err := BBC_CLIENT.GetCommonImage(queryArgs); err != nil { + fmt.Println("Get specific flavor common image failed: ", err) + } else { + fmt.Println("Get specific flavor common image success, result: ", res) + } +} + +func TestGetCustomImage(t *testing.T) { + flavorIds := []string{"flavorId"} + queryArgs := &GetFlavorImageArgs{ + FlavorIds: flavorIds, + } + if res, err := BBC_CLIENT.GetCustomImage(queryArgs); err != nil { + fmt.Println("Get specific flavor common image failed: ", err) + } else { + fmt.Println("Get specific flavor common image success, result: ", res) + } +} + +func TestShareImage(t *testing.T) { + args := &SharedUser{ + AccountId: "id", + } + err := BBC_CLIENT.ShareImage(BBC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnShareImage(t *testing.T) { + args := &SharedUser{ + AccountId: "id", + } + err := BBC_CLIENT.UnShareImage(BBC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetImageSharedUser(t *testing.T) { + users, err := BBC_CLIENT.GetImageSharedUser(BBC_TestImageId) + if err != nil { + fmt.Println("error: ", err) + } else { + fmt.Println(users) + } +} + +func TestRemoteCopyImage(t *testing.T) { + args := &RemoteCopyImageArgs{ + Name: "testRemoteCopy", + DestRegion: []string{"hkg"}, + } + err := BBC_CLIENT.RemoteCopyImage(BBC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCancelRemoteCopyImage(t *testing.T) { + err := BBC_CLIENT.CancelRemoteCopyImage(BBC_TestImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRemoteCopyImageReturnImageIds(t *testing.T) { + args := &RemoteCopyImageArgs{ + Name: "Copy", + DestRegion: []string{"hkg"}, + } + result, err := BBC_CLIENT.RemoteCopyImageReturnImageIds(BBC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestGetInstanceEni(t *testing.T) { + instanceId := "instanceId" + if res, err := BBC_CLIENT.GetInstanceEni(instanceId); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestGetInstanceStock(t *testing.T) { + args := &CreateInstanceStockArgs{ + FlavorId: "BBC-G4-PDDAS", + ZoneName: "cn-su-a", + } + if res, err := BBC_CLIENT.GetInstanceCreateStock(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestListRepairTasks(t *testing.T) { + listArgs := &ListRepairTaskArgs{ + MaxKeys: 100, + } + res, err := BBC_CLIENT.ListRepairTasks(listArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListClosedRepairTasks(t *testing.T) { + listArgs := &ListClosedRepairTaskArgs{ + MaxKeys: 100, + } + res, err := BBC_CLIENT.ListClosedRepairTasks(listArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetTaskDetail(t *testing.T) { + res, err := BBC_CLIENT.GetRepairTaskDetail(BBC_TestTaskId) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAuthorizeTask(t *testing.T) { + taskIdArgs := &TaskIdArgs{ + TaskId: BBC_TestTaskId, + } + err := BBC_CLIENT.AuthorizeRepairTask(taskIdArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnAuthorizeTask(t *testing.T) { + taskIdArgs := &TaskIdArgs{ + TaskId: BBC_TestTaskId, + } + err := BBC_CLIENT.UnAuthorizeRepairTask(taskIdArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestConfirmTask(t *testing.T) { + taskIdArgs := &TaskIdArgs{ + TaskId: BBC_TestTaskId, + } + err := BBC_CLIENT.ConfirmRepairTask(taskIdArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDisConfirmTask(t *testing.T) { + disconfirmTaskArgs := &DisconfirmTaskArgs{ + TaskId: BBC_TestTaskId, + NewErrResult: BBC_TestErrResult, + } + err := BBC_CLIENT.DisConfirmRepairTask(disconfirmTaskArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetRepairRecord(t *testing.T) { + taskIdArgs := &TaskIdArgs{ + TaskId: BBC_TestTaskId, + } + res, err := BBC_CLIENT.GetRepairTaskRecord(taskIdArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListRule(t *testing.T) { + args := &ListRuleArgs{ + Marker: "your-marker", + MaxKeys: 100, + RuleName: "your-choose-rule-name", + RuleId: "your-choose-rule-id", + } + res, err := BBC_CLIENT.ListRule(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestGetRuleDetail(t *testing.T) { + ruleId := BBC_TestRuleId + res, err := BBC_CLIENT.GetRuleDetail(ruleId) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestCreateRule(t *testing.T) { + args := &CreateRuleArgs{ + RuleName: "goSdkRule", + Limit: 2, + Enabled: 1, + TagStr: "msinstancekey:msinstancevalue", + Extra: "extra", + } + res, err := BBC_CLIENT.CreateRule(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestDeleteRule(t *testing.T) { + args := &DeleteRuleArgs{ + RuleId: BBC_TestRuleId, + } + err := BBC_CLIENT.DeleteRule(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDisableRule(t *testing.T) { + args := &DisableRuleArgs{ + RuleId: BBC_TestRuleId, + } + err := BBC_CLIENT.DisableRule(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestEnableRule(t *testing.T) { + args := &EnableRuleArgs{ + RuleId: BBC_TestRuleId, + } + err := BBC_CLIENT.EnableRule(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchCreateAutoRenewRules(t *testing.T) { + bccAutoRenewArgs := &BbcCreateAutoRenewArgs{ + InstanceId: BBC_TestBbcId, + RenewTimeUnit: "month", + RenewTime: 1, + } + err := BBC_CLIENT.BatchCreateAutoRenewRules(bccAutoRenewArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchDeleteAutoRenewRules(t *testing.T) { + bccAutoRenewArgs := &BbcDeleteAutoRenewArgs{ + InstanceId: BBC_TestBbcId, + } + err := BBC_CLIENT.BatchDeleteAutoRenewRules(bccAutoRenewArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteInstanceIngorePayment(t *testing.T) { + args := &DeleteInstanceIngorePaymentArgs{ + InstanceId: "InstanceId", + RelatedReleaseFlag: true, + } + if res, err := BBC_CLIENT.DeleteInstanceIngorePayment(args); err != nil { + fmt.Println("delete instance failed: ", err) + } else { + fmt.Println("delelte instance success, result: ", res) + } +} + +func TestListCDSVolume(t *testing.T) { + queryArgs := &ListCDSVolumeArgs{ + MaxKeys: 100, + InstanceId: "InstanceId", + Marker: "VolumeId", + ZoneName: "zoneName", + } + if res, err := BBC_CLIENT.ListCDSVolume(queryArgs); err != nil { + fmt.Println("list volume failed: ", err) + } else { + fmt.Println("list volume success, result: ", res) + } +} + +func TestDeleteInstanceV2(t *testing.T) { + instanceIds := []string{"instanceId"} + queryArgs := &DeleteInstanceArgs{ + BbcRecycleFlag: true, + InstanceIds: instanceIds, + } + if err := BBC_CLIENT.DeleteInstances(queryArgs); err != nil { + fmt.Println("delete instance failed: ", err) + } else { + fmt.Println("delete instance success") + } +} + +func TestListRecycledInstances(t *testing.T) { + queryArgs := &ListRecycledInstancesArgs{ + Marker: "your marker", + PaymentTiming: "your paymentTiming", + RecycleBegin: "RecycleBegin", // recycled begin time ,eg: 2020-11-23T17:18:24Z + RecycleEnd: "RecycleEnd", + MaxKeys: 10, + InstanceId: "InstanceId", + Name: "InstanceName", + } + if res, err := BBC_CLIENT.ListRecycledInstances(queryArgs); err != nil { + fmt.Println("list recycled bbc failed: ", err) + } else { + fmt.Println("list recycled bbc success, result: ", res) + } +} + +func TestInstanceChangeSubnet(t *testing.T) { + args := &InstanceChangeSubnetArgs{ + InstanceId: "i-DFlNGqLf", + SubnetId: "sbn-z1y9tcedqnh6", + InternalIp: "10.10.10.1", + Reboot: true, + } + + err := BBC_CLIENT.InstanceChangeSubnet(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestInstanceChangeVpc(t *testing.T) { + args := &InstanceChangeVpcArgs{ + InstanceId: "i-xxxxx", + SubnetId: "sbn-zyyyyyyy", + Reboot: true, + } + + err := BBC_CLIENT.InstanceChangeVpc(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRecoveryInstances(t *testing.T) { + instanceIds := []string{"instanceId"} + queryArgs := &RecoveryInstancesArgs{ + InstanceIds: instanceIds, + } + if err := BBC_CLIENT.RecoveryInstances(queryArgs); err != nil { + fmt.Println("recovery instance failed: ", err) + } else { + fmt.Println("recovery instance success") + } +} + +func TestGetInstanceVnc(t *testing.T) { + res, err := BBC_CLIENT.GetInstanceVNC(BBC_TestBbcId) + ExpectEqual(t.Errorf, err, nil) + fmt.Println("get instance vnc success: ", res.VNCUrl) +} + +func TestGetBbcStockWithDeploySet(t *testing.T) { + queryArgs := &GetBbcStockArgs{ + Flavor: "BBC-S3-02", + DeploySetIds: []string{"dset-0RHZYUfF"}, + } + if res, err := BBC_CLIENT.GetBbcStockWithDeploySet(queryArgs); err != nil { + fmt.Println("get bbc stock failed: ", err) + } else { + data, e := json.Marshal(res) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("get bbc stock, result : %s", data) + } +} + +func TestListInstanceByInstanceIds(t *testing.T) { + args := &ListInstanceByInstanceIdArgs{ + InstanceIds: []string{"i-3s8MUcfl"}, + } + result, err := BBC_CLIENT.ListInstanceByInstanceIds(args) + if err != nil { + fmt.Println("list instance failed: ", err) + } else { + fmt.Println("list instance success") + data, e := json.Marshal(result) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("list instance : %s", data) + } +} diff --git a/bce-sdk-go/services/bbc/deploySet.go b/bce-sdk-go/services/bbc/deploySet.go new file mode 100644 index 0000000..0254110 --- /dev/null +++ b/bce-sdk-go/services/bbc/deploySet.go @@ -0,0 +1,207 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// deploySet.go - the deploy set APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - *CreateDeploySetResult: results of creating a deploy set +// - error: nil if success otherwise the specific error +func CreateDeploySet(cli bce.Client, clientToken string, reqBody *bce.Body) (*CreateDeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateDeploySetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListDeploySets - list all deploy sets +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func ListDeploySets(cli bce.Client) (*ListDeploySetsResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUri()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListDeploySetsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListDeploySets - list all deploy sets +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the filter of deployset +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func ListDeploySetsPage(cli bce.Client, args *ListDeploySetsArgs) (*ListDeploySetsResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.Strategy) != 0 { + req.SetParam("strategy", args.Strategy) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "500") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListDeploySetsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *GetDeploySetResult: the detail of the deploy set +// - error: nil if success otherwise the specific error +func GetDeploySet(cli bce.Client, deploySetId string) (*DeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(deploySetId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeploySetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteDeploySet(cli bce.Client, deploySetId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(deploySetId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + + return nil +} + +func getDeploySetUri() string { + return URI_PREFIX_V1 + REQUEST_DEPLOY_SET_URI +} + +func getDeploySetUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_DEPLOY_SET_URI + "/" + id +} diff --git a/bce-sdk-go/services/bbc/flavor.go b/bce-sdk-go/services/bbc/flavor.go new file mode 100644 index 0000000..1ab028d --- /dev/null +++ b/bce-sdk-go/services/bbc/flavor.go @@ -0,0 +1,207 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// flavor.go - the flavor APIs definition supported by the BBC service + +// Package defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListFlavors - list all available flavors +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListFlavorsResult: the result of list all flavors +// - error: nil if success otherwise the specific error +func ListFlavors(cli bce.Client) (*ListFlavorsResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavorUri()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListFlavorsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetFlavorDetail - get details of the specified flavor +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorId: the id of the flavor +// +// RETURNS: +// - *GetFlavorDetailResult: the detail of the specified flavor +// - error: nil if success otherwise the specific error +func GetFlavorDetail(cli bce.Client, flavorId string) (*GetFlavorDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavorUriWithId(flavorId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetFlavorDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetFlavorRaid - get the RAID detail and disk size of the specified flavor +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorId: the id of the flavor +// +// RETURNS: +// - *GetFlavorRaidResult: the detail of the raid of the specified flavor +// - error: nil if success otherwise the specific error +func GetFlavorRaid(cli bce.Client, flavorId string) (*GetFlavorRaidResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavorRaidUriWithId(flavorId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetFlavorRaidResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListFlavorZones - get the zone list of the specified flavor which can buy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorId: the id of the flavor +// +// RETURNS: +// - *ListZonesResult: the list of zone names +// - error: nil if success otherwise the specific error +func ListFlavorZones(cli bce.Client, reqBody *bce.Body) (*ListZonesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavorZoneUrl()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + fmt.Println(getFlavorZoneUrl()) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListZonesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListZoneFlavors - get the flavor detail of the specific zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: the zone name +// +// RETURNS: +// - *ListZoneResult: flavor detail of the specific zone +// - error: nil if success otherwise the specific error +func ListZoneFlavors(cli bce.Client, reqBody *bce.Body) (*ListFlavorInfosResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavors()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListFlavorInfosResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func getFlavorUri() string { + return URI_PREFIX_V1 + REQUEST_FLAVOR_URI +} + +func getFlavorUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_FLAVOR_URI + "/" + id +} + +func getFlavorRaidUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_FLAVOR_RAID_URI + "/" + id +} + +func getFlavorZoneUrl() string { + return URI_PREFIX_V1 + REQUEST_FLAVOR_ZONE_URI +} + +func getFlavors() string { + return URI_PREFIX_V1 + REQUEST_FLAVORS_URI +} diff --git a/bce-sdk-go/services/bbc/image.go b/bce-sdk-go/services/bbc/image.go new file mode 100644 index 0000000..daf813d --- /dev/null +++ b/bce-sdk-go/services/bbc/image.go @@ -0,0 +1,478 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// image.go - the image APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateImageFromInstanceId - create image from specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - *CreateImageResult: the result of create Image +// - error: nil if success otherwise the specific error +func CreateImageFromInstanceId(cli bce.Client, clientToken string, reqBody *bce.Body) (*CreateImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListImage - list all images +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list all images +// +// RETURNS: +// - *ListImageResult: the result of list all images +// - error: nil if success otherwise the specific error +func ListImage(cli bce.Client, queryArgs *ListImageArgs) (*ListImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.ImageType) != 0 { + req.SetParam("imageType", queryArgs.ImageType) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetImageDetail - get an image's detail info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: the specific image ID +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func GetImageDetail(cli bce.Client, imageId string) (*GetImageDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImageDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DeleteImage - delete an image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: the specific image ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteImage(cli bce.Client, imageId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetCommonImage - get common flavor image list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorIds: the specific flavorIds, can be nil +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func GetCommonImage(cli bce.Client, reqBody *bce.Body) (*GetImagesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCommonImageUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImagesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetCustomImage - get user onwer flavor image list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - flavorIds: the specific flavorIds, can be nil +// +// RETURNS: +// - *GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func GetCustomImage(cli bce.Client, reqBody *bce.Body) (*GetImagesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCustomImageUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImagesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ShareImage - share a specified custom image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be shared +// - args: the arguments to share image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ShareImage(cli bce.Client, imageId string, args *SharedUser) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("share", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// UnShareImage - unshare a specified image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be unshared +// - args: the arguments to unshare image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnShareImage(cli bce.Client, imageId string, args *SharedUser) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("unshare", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetImageSharedUser - get the list of users that the image has been shared with +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image +// +// RETURNS: +// - *GetImageSharedUserResult: result of the shared users +// - error: nil if success otherwise the specific error +func GetImageSharedUser(cli bce.Client, imageId string) (*GetImageSharedUserResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageSharedUserUri(imageId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImageSharedUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// RemoteCopyImage - copy bbc custom images across regions, only custom images supported, the system \ +// and service integration images cannot be copied. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be copied +// - args: the arguments to copy image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RemoteCopyImage(cli bce.Client, imageId string, args *RemoteCopyImageArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("remoteCopy", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// CancelRemoteCopyImage - cancel the image copy across regions +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CancelRemoteCopyImage(cli bce.Client, imageId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("cancelRemoteCopy", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RemoteCopyImageReturnImageIds - copy custom images across regions, only custom images supported, the system \ +// and service integration images cannot be copied. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be copied +// - args: the arguments to copy image +// +// RETURNS: +// - imageIds of destination region if success otherwise the specific error +func RemoteCopyImageReturnImageIds(cli bce.Client, imageId string, args *RemoteCopyImageArgs) (*RemoteCopyImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("remoteCopy", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &RemoteCopyImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func getImageUri() string { + return URI_PREFIX_V1 + REQUEST_IMAGE_URI +} + +func getImageUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_IMAGE_URI + "/" + id +} + +func getCommonImageUri() string { + return URI_PREFIX_V1 + REQUEST_COMMON_IMAGE_URI +} + +func getCustomImageUri() string { + return URI_PREFIX_V1 + REQUEST_CUSTOM_IMAGE_URI +} + +func getImageSharedUserUri(id string) string { + return URI_PREFIX_V1 + REQUEST_IMAGE_URI + "/" + id + REQUEST_IMAGE_SHAREDUSER_URI +} diff --git a/bce-sdk-go/services/bbc/instance.go b/bce-sdk-go/services/bbc/instance.go new file mode 100644 index 0000000..8fa9128 --- /dev/null +++ b/bce-sdk-go/services/bbc/instance.go @@ -0,0 +1,1295 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// instance.go - the instance APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateInstance - create a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - *CreateInstanceResult: results of creating a bbc instance +// - error: nil if success otherwise the specific error +func CreateInstance(cli bce.Client, args *CreateInstanceArgs, reqBody *bce.Body) (*CreateInstanceResult, + error) { + clientToken := args.ClientToken + requestToken := args.RequestToken + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CreateInstance - create a bbc instance and support the passing in of label +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - *CreateInstanceResult: results of creating a bbc instance +// - error: nil if success otherwise the specific error +func CreateInstanceByLabel(cli bce.Client, args *CreateSpecialInstanceArgs, reqBody *bce.Body) (*CreateInstanceResult, + error) { + clientToken := args.ClientToken + requestToken := args.RequestToken + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceByLabelUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListInstances - list all bbc instances +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list bbc instances +// +// RETURNS: +// - *ListInstanceResult: results of list bbc instances +// - error: nil if success otherwise the specific error +func ListInstances(cli bce.Client, args *ListInstancesArgs) (*ListInstancesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.InternalIp) != 0 { + req.SetParam("internalIp", args.InternalIp) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstancesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetInstanceDetail - get a bbc instance detail msg +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - *InstanceModel: instance detail msg +// - error: nil if success otherwise the specific error +func GetInstanceDetailWithDeploySet(cli bce.Client, instanceId string, isDeploySet bool) (*InstanceModel, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + if isDeploySet == true { + req.SetParam("isDeploySet", "true") + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceModel{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetInstanceDetail - get a bbc instance detail msg +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - *InstanceModel: instance detail msg +// - error: nil if success otherwise the specific error +func GetInstanceDetailWithDeploySetAndFailed(cli bce.Client, instanceId string, + isDeploySet bool, containsFailed bool) (*InstanceModel, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + if isDeploySet == true { + req.SetParam("isDeploySet", "true") + } + if containsFailed == true { + req.SetParam("containsFailed", "true") + } else { + req.SetParam("containsFailed", "false") + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceModel{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func GetInstanceDetail(cli bce.Client, instanceId string) (*InstanceModel, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + req.SetParam("isDeploySet", "false") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceModel{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// StartInstance - start a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func StartInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("start", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// StopInstance - stop a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func StopInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("stop", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ListInstances - list all bbc instances +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list bbc instances +// +// RETURNS: +// - *ListInstanceResult: results of list bbc instances +// - error: nil if success otherwise the specific error +func ListRecycledInstances(cli bce.Client, reqBody *bce.Body) (*ListRecycledInstancesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRecycledInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRecycledInstancesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func RecoveryInstances(cli bce.Client, reqBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getRecoveryInstancesUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteInstance - delete a specified instance,contains prepay or postpay instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// RETURNS: +// - error: nil if success otherwise the specific error + +func DeleteBbcIngorePayment(cli bce.Client, args *DeleteInstanceIngorePaymentArgs) (*DeleteInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeleteBbcDeleteIngorePaymentUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeleteInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func getDeleteBbcDeleteIngorePaymentUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/delete" +} + +// RebootInstance - reboot a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RebootInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("reboot", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyInstanceName - modify a bbc instance name +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstanceName(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("rename", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyInstanceDesc - modify a bbc instance desc +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstanceDesc(cli bce.Client, instanceId string, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("updateDesc", "") + req.SetBody(reqBody) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RebuildInstance - rebuild a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RebuildInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("rebuild", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchRebuildInstances - batch rebuild instances +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to rebuild instance +// +// RETURNS: +// - *BatchRebuildResponse: result of batch rebuild instances +// - error: nil if success otherwise the specific error +func BatchRebuildInstances(cli bce.Client, reqBody *bce.Body) (*BatchRebuildResponse, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRebuildBatchInstanceUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + fmt.Println(resp) + jsonBody := &BatchRebuildResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// InstancePurchaseReserved - renew a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be renewed +// - reqBody: the request body to renew instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func InstancePurchaseReserved(cli bce.Client, instanceId string, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("purchaseReserved", "") + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteInstance - delete a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithIdV2(instanceId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteInstance - delete a bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteInstances(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchDeleteInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteRecycledInstance - delete a recycled bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteRecycledInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeleteRecycledInstanceUri(instanceId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetVpcSubnet - get multi instances vpc and subnet +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - *GetVpcSubnetResult: result of vpc and subnet +// - error: nil if success otherwise the specific error +func GetVpcSubnet(cli bce.Client, reqBody *bce.Body) (*GetVpcSubnetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSubnetUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetVpcSubnetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ModifyInstancePassword - modify a bbc instance password +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstancePassword(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("changePass", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchAddIp - Add ips to instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchAddIp(cli bce.Client, args *BatchAddIpArgs, reqBody *bce.Body) (*BatchAddIpResponse, error) { + // Build the request + clientToken := args.ClientToken + req := &bce.BceRequest{} + req.SetUri(getBatchAddIpUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchAddIpResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// BatchAddIp - Add ips to instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchAddIpCrossSubnet(cli bce.Client, args *BatchAddIpCrossSubnetArgs, reqBody *bce.Body) (*BatchAddIpResponse, + error) { + // Build the request + clientToken := args.ClientToken + req := &bce.BceRequest{} + req.SetUri(getBatchAddIpCrossSubnetUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchAddIpResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// BatchDelIp - Delete ips of instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchDelIp(cli bce.Client, args *BatchDelIpArgs, reqBody *bce.Body) error { + // Build the request + clientToken := args.ClientToken + req := &bce.BceRequest{} + req.SetUri(getBatchDelIpUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func GetInstanceCreateStock(cli bce.Client, args *CreateInstanceStockArgs) (*InstanceStockResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCreateInstanceStock()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceStockResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetSimpleFlavor(cli bce.Client, args *GetSimpleFlavorArgs) (*SimpleFlavorResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getsimpleFlavor()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &SimpleFlavorResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetInstancePirce(cli bce.Client, args *InstancePirceArgs) (*InstancePirceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstancePirce()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstancePirceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetInstanceEni - get the eni of the bbc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the bbc instance id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func GetInstanceEni(cli bce.Client, instanceId string) (*GetInstanceEniResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceEniUri(instanceId)) + req.SetMethod(http.GET) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetInstanceEniResult{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetInstanceVNC - get VNC address of the specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetInstanceVNCResult: result of the VNC address of the instance +// - error: nil if success otherwise the specific error +func GetInstanceVNC(cli bce.Client, instanceId string) (*GetInstanceVNCResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceVNCUri(instanceId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetInstanceVNCResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// BatchCreateAutoRenewRules - Batch Create AutoRenew Rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchCreateAutoRenewRules(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchCreateAutoRenewRulesUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchDeleteAutoRenewRules - Batch Delete AutoRenew Rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchDeleteAutoRenewRules(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchDeleteAutoRenewRulesUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// InstanceChangeVpc - change the subnet to which the instance belongs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to change subnet of instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func InstanceChangeSubnet(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getChangeSubnetUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// InstanceChangeVpc - change the vpc to which the instance belongs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to change vpc of instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func InstanceChangeVpc(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getChangeVpcUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func getInstanceVNCUri(instanceId string) string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/" + instanceId + "/vnc" +} + +func getInstanceEniUri(instanceId string) string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_PORT_URI + "/" + instanceId +} + +func getInstanceUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI +} + +func getInstanceByLabelUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_LABEL_URI +} + +func getRecycledInstanceUri() string { + return URI_PREFIX_V1 + REQUEST_RECYCLE_URI + REQUEST_INSTANCE_URI +} + +func getRecoveryInstancesUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_RECOVERY_URI +} + +func getInstanceUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/" + id +} + +func getBatchAddIpUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCHADDIP_URI +} + +func getBatchAddIpCrossSubnetUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCHADDIPCROSSSUBNET_URI +} + +func getBatchDelIpUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCHDELIP_URI +} + +func getBatchDeleteInstanceUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCH_DELETE_URI +} + +func getInstanceUriWithIdV2(id string) string { + return URI_PREFIX_V2 + REQUEST_INSTANCE_URI + "/" + id +} + +func getDeleteRecycledInstanceUri(id string) string { + return URI_PREFIX_V1 + "/recycle" + REQUEST_INSTANCE_URI + "/" + id +} + +func getSubnetUri() string { + return URI_PREFIX_V1 + REQUEST_SUBNET_URI +} + +func getCreateInstanceStock() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/stock/createInstance" +} + +func getsimpleFlavor() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/simpleFlavor" +} + +func getInstancePirce() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/price" +} + +func getBatchCreateAutoRenewRulesUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCH_CREATE_AUTORENEW_RULES_URI +} + +func getBatchDeleteAutoRenewRulesUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCH_Delete_AUTORENEW_RULES_URI +} + +func getRebuildBatchInstanceUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + REQUEST_BATCH_REBUILD_INSTANCE_URI +} + +func getChangeSubnetUri() string { + return URI_PREFIX_V1 + "/subnet" + "/changeSubnet" +} + +func getChangeVpcUri() string { + return URI_PREFIX_V1 + REQUEST_VPC_URI + "/changeVpc" +} + +// GetStockWithDeploySet - get the bbc's stock with deploySet +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get the bbc's stock with deploySet +// +// RETURNS: +// - *GetBbcStocksResult: the result of the bbc's stock +// - error: nil if success otherwise the specific error +func GetStockWithDeploySet(cli bce.Client, args *GetBbcStockArgs) (*GetBbcStocksResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(geBbcStockWithDeploySetUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetBbcStocksResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListInstanceByInstanceIds - list instance by instanceId +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListInstancesResult: result of the instance list +// - error: nil if success otherwise the specific error +func ListInstanceByInstanceIds(cli bce.Client, args *ListInstanceByInstanceIdArgs) (*ListInstancesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListInstancesByIdsUrl()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstancesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bbc/model.go b/bce-sdk-go/services/bbc/model.go new file mode 100644 index 0000000..21ec6f8 --- /dev/null +++ b/bce-sdk-go/services/bbc/model.go @@ -0,0 +1,1049 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type PaymentTimingType string + +const ( + PaymentTimingPrePaid PaymentTimingType = "Prepaid" + PaymentTimingPostPaid PaymentTimingType = "Postpaid" +) + +type InstanceStatus string + +const ( + InstanceStatusRunning InstanceStatus = "Running" + InstanceStatusStarting InstanceStatus = "Starting" + InstanceStatusStopping InstanceStatus = "Stopping" + InstanceStatusStopped InstanceStatus = "Stopped" + InstanceStatusDeleted InstanceStatus = "Deleted" + InstanceStatusExpired InstanceStatus = "Expired" + InstanceStatusError InstanceStatus = "Error" + InstanceStatusImageProcessing InstanceStatus = "ImageProcessing" + InstanceStatusChangeVpcProcessing InstanceStatus = "ChangeVpc" + InstanceStatusRecycled InstanceStatus = "Recycled" + InstanceStatusRecharging InstanceStatus = "Recharging" +) + +type ImageType string + +const ( + ImageTypeIntegration ImageType = "Integration" + ImageTypeSystem ImageType = "System" + ImageTypeCustom ImageType = "Custom" +) + +type ImageStatus string + +const ( + ImageStatusCreating ImageStatus = "Creating" + ImageStatusCreateFailed ImageStatus = "CreateFailed" + ImageStatusAvailable ImageStatus = "Available" + ImageStatusNotAvailable ImageStatus = "NotAvailable" + ImageStatusError ImageStatus = "Error" +) + +type VolumeType string + +const ( + VolumeTypeSYSTEM VolumeType = "System" + VolumeTypeEPHEMERAL VolumeType = "Ephemeral" + VolumeTypeCDS VolumeType = "Cds" +) + +type StorageType string + +const ( + StorageTypeStd1 StorageType = "std1" + StorageTypeHP1 StorageType = "hp1" + StorageTypeCloudHP1 StorageType = "cloud_hp1" + StorageTypeLocal StorageType = "local" + StorageTypeSATA StorageType = "sata" + StorageTypeSSD StorageType = "ssd" + StorageTypeHDDThroughput StorageType = "HDD_Throughput" + StorageTypeHdd StorageType = "hdd" +) + +type VolumeStatus string + +const ( + VolumeStatusAVAILABLE VolumeStatus = "Available" + VolumeStatusINUSE VolumeStatus = "InUse" + VolumeStatusSNAPSHOTPROCESSING VolumeStatus = "SnapshotProcessing" + VolumeStatusRECHARGING VolumeStatus = "Recharging" + VolumeStatusDETACHING VolumeStatus = "Detaching" + VolumeStatusDELETING VolumeStatus = "Deleting" + VolumeStatusEXPIRED VolumeStatus = "Expired" + VolumeStatusNOTAVAILABLE VolumeStatus = "NotAvailable" + VolumeStatusDELETED VolumeStatus = "Deleted" + VolumeStatusSCALING VolumeStatus = "Scaling" + VolumeStatusIMAGEPROCESSING VolumeStatus = "ImageProcessing" + VolumeStatusCREATING VolumeStatus = "Creating" + VolumeStatusATTACHING VolumeStatus = "Attaching" + VolumeStatusERROR VolumeStatus = "Error" +) + +type CreateInstanceArgs struct { + FlavorId string `json:"flavorId"` + ImageId string `json:"imageId"` + RaidId string `json:"raidId"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb"` + PurchaseCount int `json:"purchaseCount"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Billing Billing `json:"billing"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + DeploySetId string `json:"deploySetId,omitempty"` + ClientToken string `json:"-"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + RequestToken string `json:"requestToken"` + EnableNuma bool `json:"enableNuma"` + EnableHt bool `json:"enableHt"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type CreateInstanceArgsV2 struct { + FlavorId string `json:"flavorId"` + ImageId string `json:"imageId"` + RaidId string `json:"raidId"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb"` + PurchaseCount int `json:"purchaseCount"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Billing Billing `json:"billing"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + DeploySetId string `json:"deploySetId,omitempty"` + ClientToken string `json:"-"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + RequestToken string `json:"requestToken"` + EnableNuma *bool `json:"enableNuma"` + EnableHt *bool `json:"enableHt"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +const ( + LabelOperatorEqual LabelOperator = "equal" + LabelOperatorNotEqual LabelOperator = "not_equal" + LabelOperatorExist LabelOperator = "exist" + LabelOperatorNotExist LabelOperator = "not_exist" +) + +type LabelOperator string + +type LabelConstraint struct { + Key string `json:"labelKey,omitempty"` + Value string `json:"labelValue,omitempty"` + Operator LabelOperator `json:"operatorName,omitempty"` +} + +type CreateSpecialInstanceArgs struct { + FlavorId string `json:"flavorId"` + ImageId string `json:"imageId"` + RaidId string `json:"raidId"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb"` + PurchaseCount int `json:"purchaseCount"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Billing Billing `json:"billing"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + DeploySetId string `json:"deploySetId,omitempty"` + ClientToken string `json:"-"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + RequestToken string `json:"requestToken"` + EnableNuma bool `json:"enableNuma"` + EnableHt bool `json:"enableHt"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` + // CreateInstanceArgs 的基础上增加的参数 + LabelConstraints []LabelConstraint `json:"labelConstraints,omitempty"` +} + +type CreateSpecialInstanceArgsV2 struct { + FlavorId string `json:"flavorId"` + ImageId string `json:"imageId"` + RaidId string `json:"raidId"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb"` + PurchaseCount int `json:"purchaseCount"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Billing Billing `json:"billing"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + DeploySetId string `json:"deploySetId,omitempty"` + ClientToken string `json:"-"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + RequestToken string `json:"requestToken"` + EnableNuma *bool `json:"enableNuma"` + EnableHt *bool `json:"enableHt"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` + // CreateInstanceArgs 的基础上增加的参数 + LabelConstraints []LabelConstraint `json:"labelConstraints,omitempty"` +} + +type CreateSpecialInstanceResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation Reservation `json:"reservation,omitempty"` +} + +type Reservation struct { + Length int `json:"reservationLength"` + TimeUnit string `json:"reservationTimeUnit"` +} + +type CreateInstanceResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type ListInstancesArgs struct { + Marker string + MaxKeys int + InternalIp string + VpcId string `json:"vpcId"` +} + +type RecoveryInstancesArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type ListRecycledInstancesArgs struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + InstanceId string `json:"instanceId,omitempty"` + Name string `json:"name,omitempty"` + PaymentTiming string `json:"paymentTiming,omitempty"` + RecycleBegin string `json:"recycleBegin,omitempty"` + RecycleEnd string `json:"recycleEnd,omitempty"` +} + +type ListRecycledInstancesResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RecycledInstances []RecycledInstancesModel `json:"instances"` +} + +type RecycledInstancesModel struct { + ServiceType string `json:"serviceType"` + ServiceName string `json:"serviceName"` + Name string `json:"name"` + Id string `json:"id"` + SerialNumber string `json:"serialNumber"` + RecycleTime string `json:"recycleTime"` + DeleteTime string `json:"deleteTime"` + PaymentTiming string `json:"paymentTiming"` + ConfigItems []string `json:"configItems"` + ConfigItem RecycleInstanceModelConfigItem `json:"configItem"` +} + +type RecycleInstanceModelConfigItem struct { + Cpu int `json:"cpu"` + Memory int `json:"memory"` + Type string `json:"type"` + ZoneName string `json:"zoneName"` +} + +type ListInstancesResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []InstanceModel `json:"instances"` +} + +type InstanceModel struct { + Id string `json:"id"` + Name string `json:"name"` + Hostname string `json:"hostname"` + Uuid string `json:"uuid"` + Desc string `json:"desc"` + Status InstanceStatus `json:"status"` + PaymentTiming string `json:"paymentTiming"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + PublicIp string `json:"publicIp"` + InternalIp string `json:"internalIp"` + RdmaIp string `json:"rdmaIp"` + ImageId string `json:"imageId"` + FlavorId string `json:"flavorId"` + Zone string `json:"zone"` + Region string `json:"region"` + HasAlive int `json:"hasAlive"` + Tags []model.TagModel `json:"tags"` + SwitchId string `json:"switchId"` + HostId string `json:"hostId"` + DeploysetId string `json:"deploysetId"` + NetworkCapacityInMbps int `json:"networkCapacityInMbps"` + RackId string `json:"rackId"` +} + +type StopInstanceArgs struct { + ForceStop bool `json:"forceStop,omitempty"` +} + +type ModifyInstanceNameArgs struct { + Name string `json:"name"` +} + +type InstanceChangeSubnetArgs struct { + InstanceId string `json:"instanceId"` + SubnetId string `json:"subnetId"` + InternalIp string `json:"internalIp"` + Reboot bool `json:"reboot"` +} + +type InstanceChangeVpcArgs struct { + InstanceId string `json:"instanceId"` + SubnetId string `json:"subnetId"` + InternalIp string `json:"internalIp"` + Reboot bool `json:"reboot"` +} + +type ModifyInstanceDescArgs struct { + Description string `json:"desc"` + ClientToken string `json:"clientToken"` +} + +type ModifyInstanceHostnameArgs struct { + Hostname string `json:"hostname"` + Reboot bool `json:"reboot"` +} + +type RebuildInstanceArgs struct { + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + IsPreserveData bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type RebuildBatchInstanceArgs struct { + InstanceIds []string `json:"instanceIds"` + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + IsPreserveData bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type RebuildBatchInstanceArgsV2 struct { + InstanceIds []string `json:"instanceIds"` + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + IsPreserveData *bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type BatchRebuildResponse struct { + Result []BatchRebuild `json:"result"` +} + +type BatchRebuild struct { + InstanceIds []string `json:"instanceIds"` + ErrMsp string `json:"errMsg"` + Code string `json:"code"` +} + +type GetVpcSubnetArgs struct { + BbcIds []string `json:"bbcIds"` +} + +type GetVpcSubnetResult struct { + NetworkInfo []BbcNetworkModel `json:"networkInfo"` +} + +type BbcNetworkModel struct { + BbcId string `json:"bbcId"` + Vpc VpcModel `json:"vpc"` + Subnet SubnetModel `json:"subnet"` +} + +type VpcModel struct { + VpcId string `json:"vpcId"` + Cidr string `json:"cidr"` + Name string `json:"name"` + IsDefault bool `json:"isDefault"` + Description string `json:"description"` +} + +type SubnetModel struct { + VpcId string `json:"vpcId"` + Name string `json:"name"` + SubnetType string `json:"subnetType"` + SubnetId string `json:"subnetId"` + Cidr string `json:"cidr"` + ZoneName string `json:"zoneName"` +} + +type ModifyInstancePasswordArgs struct { + AdminPass string `json:"adminPass"` +} + +type BatchAddIpArgs struct { + InstanceId string `json:"instanceId"` + PrivateIps []string `json:"privateIps"` + SecondaryPrivateIpAddressCount int `json:"secondaryPrivateIpAddressCount"` + AllocateMultiIpv6Addr bool `json:"allocateMultiIpv6Addr"` + ClientToken string `json:"-"` +} + +type BatchAddIpCrossSubnetArgs struct { + InstanceId string `json:"instanceId"` + SingleEniAndSubentIps []SingleEniAndSubentIp `json:"singleEniAndSubentIps"` + ClientToken string `json:"-"` +} + +type SingleEniAndSubentIp struct { + EniId string `json:"eniId"` + SubnetId string `json:"subnetId"` + SecondaryPrivateIpAddressCount int `json:"secondaryPrivateIpAddressCount"` + IpAndSubnets []IpAndSubnet `json:"ipAndSubnets"` +} + +type IpAndSubnet struct { + PrivateIp string `json:"privateIp"` + SubnetId string `json:"subnetId"` +} + +type BatchAddIpResponse struct { + PrivateIps []string `json:"privateIps"` +} + +type BatchDelIpArgs struct { + InstanceId string `json:"instanceId"` + PrivateIps []string `json:"privateIps"` + ClientToken string `json:"-"` +} + +type BindTagsArgs struct { + ChangeTags []model.TagModel `json:"changeTags"` +} + +type UnbindTagsArgs struct { + ChangeTags []model.TagModel `json:"changeTags"` +} + +type ListImageArgs struct { + Marker string + MaxKeys int + ImageType string +} + +type ListImageResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Images []ImageModel `json:"images"` +} + +type ImageModel struct { + OsVersion string `json:"osVersion"` + OsArch string `json:"osArch"` + Status ImageStatus `json:"status"` + Desc string `json:"desc"` + Id string `json:"id"` + Name string `json:"name"` + OsName string `json:"osName"` + OsBuild string `json:"osBuild"` + CreateTime string `json:"createTime"` + Type ImageType `json:"type"` + OsType string `json:"osType"` + SpecialVersion string `json:"specialVersion"` +} + +type FlavorImageModel struct { + FlavorId string `json:"flavorId"` + Images []ImageModel `json:"images"` +} + +type GetImageDetailResult struct { + Result *ImageModel `json:"image"` +} + +type GetImagesResult struct { + Result []FlavorImageModel `json:"result"` +} + +type ListFlavorsResult struct { + Flavors []FlavorModel `json:"flavors"` +} + +type FlavorModel struct { + FlavorId string `json:"flavorID"` + CpuCount int `json:"cpuCount"` + CpuType string `json:"cpuType"` + MemoryCapacityInGB int `json:"memoryCapacityInGb"` + Disk string `json:"disk"` + NetworkCard string `json:"networkCard"` + Others string `json:"others"` +} + +type GetFlavorDetailResult struct { + FlavorModel +} + +type GetFlavorRaidResult struct { + FlavorId string `json:"flavorId"` + Raids []RaidModel `json:"raids"` +} + +type RaidModel struct { + RaidId string `json:"raidId"` + Raid string `json:"raid"` + SysSwapSize int `json:"sysSwapSize"` + SysRootSize int `json:"sysRootSize"` + SysHomeSize int `json:"sysHomeSize"` + SysDiskSize int `json:"sysDiskSize"` + DataDiskSize float64 `json:"dataDiskSize"` +} + +type CreateImageArgs struct { + ImageName string `json:"imageName"` + InstanceId string `json:"instanceId"` + ClientToken string `json:"-"` +} + +type CreateImageResult struct { + ImageId string `json:"imageId"` +} + +type GetOperationLogArgs struct { + Marker string + MaxKeys int + StartTime string + EndTime string +} + +type GetOperationLogResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + OperationLogs []OperationLogModel `json:"operationLogs"` +} + +type GetInstanceVNCResult struct { + VNCUrl string `json:"vncUrl"` +} + +type OperationLogModel struct { + OperationStatus bool `json:"operationStatus"` + OperationTime string `json:"operationTime"` + OperationDesc string `json:"operationDesc"` + OperationIp string `json:"operationIp"` +} + +type CreateDeploySetArgs struct { + Strategy string `json:"strategy"` + Concurrency int `json:"concurrency"` + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` + ClientToken string `json:"-"` +} + +type GetFlavorImageArgs struct { + FlavorIds []string `json:"flavorIds"` + ClientToken string `json:"-"` +} + +type CreateDeploySetResult struct { + DeploySetId string `json:"deploySetId"` +} + +type ListDeploySetsArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"MaxKeys"` + Strategy string `json:"strategy"` +} + +type ListDeploySetsResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + DeploySetList []DeploySetModel `json:"deploySetList"` +} + +type AzIntstanceStatis struct { + ZoneName string `json:"zoneName"` + Count int `json:"instanceCount"` + BbcCount int `json:"bbcInstanceCnt"` + BccCount int `json:"bccInstanceCnt"` + Total int `json:"instanceTotal"` +} + +type DeploySetModel struct { + Strategy string `json:"strategy"` + AzIntstanceStatisList []AzIntstanceStatis `json:"azIntstanceStatisList"` + Name string `json:"name"` + Desc string `json:"desc"` + DeploySetId string `json:"deploysetId"` + Concurrency int `json:"concurrency"` +} + +type GetDeploySetResult struct { + DeploySetModel +} + +type BindSecurityGroupsArgs struct { + InstanceIds []string `json:"instanceIds"` + SecurityGroupIds []string `json:"securityGroups"` +} + +type UnBindSecurityGroupsArgs struct { + InstanceId string `json:"instanceId"` + SecurityGroupId string `json:"securityGroupId"` +} +type ListZonesResult struct { + ZoneNames []string `json:"zoneNames"` +} + +type DiskInfo struct { + Raid string `json:"raid"` + Description string `json:"description"` + DataDiskName string `json:"dataDiskName"` + RaidDisplay string `json:"raidDisplay"` + SysAndHomeSize float64 `json:"sysAndHomeSize"` + DataDiskSize float64 `json:"dataDiskSize"` + RaidId string `json:"raidId"` +} + +type BbcFlavorInfo struct { + Count int `json:"count"` + SataInfo string `json:"sataInfo"` + Cpu int `json:"cpu"` + CpuGhz string `json:"cpuGhz"` + Memory int `json:"memory"` + StorageType string `json:"type"` + FlavorId string `json:"id"` + DiskInfos map[string]DiskInfo `json:"diskInfos"` +} + +type ListFlavorInfosResult struct { + BbcFlavorInfoList []BbcFlavorInfo `json:"bbcFlavorInfoList"` +} + +type ListFlavorZonesArgs struct { + FlavorId string `json:"flavorId"` + ProductType PaymentTimingType `json:"productType"` +} + +type ListZoneFlavorsArgs struct { + ZoneName string `json:"zoneName"` + ProductType PaymentTimingType `json:"productType"` +} + +type PurchaseReservedArgs struct { + Billing Billing `json:"billing"` + ClientToken string `json:"-"` +} + +type PrivateIP struct { + PublicIpAddress string `json:"publicIpAddress"` + Primary bool `json:"primary"` + PrivateIpAddress string `json:"privateIpAddress"` + Ipv6Address string `json:"ipv6Address"` + SubnetId string `json:"subnetId"` +} + +type GetInstanceEniResult struct { + Id string `json:"eniId"` + Name string `json:"name"` + ZoneName string `json:"zoneName"` + Description string `json:"description"` + InstanceId string `json:"instanceId"` + MacAddress string `json:"macAddress"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Status string `json:"status"` + PrivateIpSet []PrivateIP `json:"privateIpSet"` +} + +type CreateInstanceStockArgs struct { + FlavorId string `json:"flavorId"` + ZoneName string `json:"zoneName,omitempty"` +} + +type InstanceStockResult struct { + FlaovrId string `json:"flavorId"` + Count int `json:"Count"` +} + +type GetSimpleFlavorArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type SimpleFlavorResult struct { + SimpleFlavorModel []SimpleFlavorModel `json:"flavorInfo"` +} + +type SimpleFlavorModel struct { + GpuCard string `json:"gpuCard"` + DiskDescription string `json:"diskDescription"` + InstanceId string `json:"instanceId"` + MemDescription string `json:"memDescription"` + NicDescription string `json:"nicDescription"` + RamType string `json:"ramType"` + RamRate string `json:"ramRate"` + CpuDescription string `json:"cpuDescription"` + RaidDescription string `json:"raidDescription"` +} + +type InstancePirceArgs struct { + FlaovrId string `json:"flavorId"` + PurchaseCount int `json:"purchaseCount"` + Billing Billing `json:"billing"` +} + +type InstancePirceResult struct { + Pirce string `json:"price"` +} + +type ListRepairTaskArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"MaxKeys"` + ErrResult string `json:"errResult"` + InstanceId string `json:"instanceId"` +} + +type RepairTask struct { + TaskId string `json:"taskId"` + InstanceId string `json:"instanceId"` + ErrResult string `json:"errResult"` + Status string `json:"status"` +} + +type ListRepairTaskResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RepairTasks []RepairTask `json:"RepairTask"` +} + +type ListClosedRepairTaskArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"MaxKeys"` + ErrResult string `json:"errResult"` + InstanceId string `json:"instanceId"` + TaskId string `json:"taskId"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type ClosedRepairTask struct { + TaskId string `json:"taskId"` + InstanceId string `json:"instanceId"` + ErrResult string `json:"errResult"` + CreateTime string `json:"createTime"` + EndTime string `json:"endTime"` +} + +type ListClosedRepairTaskResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RepairTasks []ClosedRepairTask `json:"RepairTask"` +} + +type GetRepairTaskResult struct { + TaskId string `json:"taskId"` + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + ErrResult string `json:"errResult"` + Status string `json:"status"` + ServerStatus string `json:"serverStatus"` + Region string `json:"region"` + InternalIp string `json:"internalIp"` + FloatingIp string `json:"floatingIp"` +} + +type TaskIdArgs struct { + TaskId string `json:"taskId"` + RetainDataDisk int `json:"retainDataDisk,omitempty"` +} + +type DisconfirmTaskArgs struct { + TaskId string `json:"taskId"` + NewErrResult string `json:"newErrResult"` +} + +type RepairRecord struct { + Name string `json:"name"` + Operator string `json:"operator"` + OperateTime string `json:"operateTime"` +} + +type GetRepairRecords struct { + RepairRecords []RepairRecord `json:"RepairRecord"` +} + +type ListRuleArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + RuleName string `json:"ruleName"` + RuleId string `json:"ruleId"` +} + +type ListRuleResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RuleList []Rule `json:"RuleList"` +} + +type Rule struct { + RuleId string `json:"ruleId"` + RuleName string `json:"ruleName"` + TagCount int `json:"tagCount"` + AssociateBbcNum int `json:"associateBbcNum"` + ErrorBbcNum int `json:"errorBbcNum"` + ErrResult string `json:"errResult"` + Limit int `json:"limit"` + Status string `json:"status"` + AssociateBbcList []string `json:"associateBbcList"` + Tags []model.TagModel `json:"tags"` +} + +type CreateRuleArgs struct { + RuleName string `json:"ruleName"` + Limit int `json:"limit"` + Enabled int `json:"enabled"` + TagStr string `json:"tagStr"` + Extra string `json:"extra"` +} + +type CreateRuleResult struct { + RuleId string `json:"ruleId"` +} + +type DeleteRuleArgs struct { + RuleId string `json:"ruleId"` +} + +type DisableRuleArgs struct { + RuleId string `json:"ruleId"` +} + +type EnableRuleArgs struct { + RuleId string `json:"ruleId"` +} + +type DeploySetResult struct { + Strategy string `json:"strategy"` + Name string `json:"name"` + Desc string `json:"desc"` + DeploySetId string `json:"deploySetId"` + InstanceList []AzIntstanceStatisDetail `json:"azIntstanceStatisList"` + Concurrency int `json:"concurrency"` +} + +type AzIntstanceStatisDetail struct { + ZoneName string `json:"zoneName"` + Count int `json:"instanceCount"` + BccCount int `json:"bccInstanceCnt"` + BbcCount int `json:"bbcInstanceCnt"` + Total int `json:"instanceTotal"` + InstanceIds []string `json:"instanceIds"` + BccInstanceIds []string `json:"bccInstanceIds"` + BbcInstanceIds []string `json:"bbcInstanceIds"` +} + +type BbcCreateAutoRenewArgs struct { + InstanceId string `json:"instanceId"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` +} + +type BbcDeleteAutoRenewArgs struct { + InstanceId string `json:"instanceId"` +} + +type DeleteInstanceIngorePaymentArgs struct { + InstanceId string `json:"instanceId"` + RelatedReleaseFlag bool `json:"relatedReleaseFlag"` + DeleteCdsSnapshotFlag bool `json:"deleteCdsSnapshotFlag"` + DeleteRelatedEnisFlag bool `json:"deleteRelatedEnisFlag"` + DeleteImmediate bool `json:"deleteImmediate"` +} + +type DeleteInstanceModel struct { + InstanceId string `json:"instanceId"` + Eip string `json:"eip"` +} + +type DeleteInstanceResult struct { + SuccessResources *DeleteInstanceModel `json:"successResources"` + FailResources *DeleteInstanceModel `json:"failResources"` +} + +type SharedUser struct { + AccountId string `json:"accountId,omitempty"` + Account string `json:"account,omitempty"` +} + +type GetImageSharedUserResult struct { + Users []SharedUser `json:"users"` +} + +type RemoteCopyImageModel struct { + Region string `json:"region"` + ImageId string `json:"imageId"` + ErrMsg string `json:"errMsg"` + Code string `json:"code"` +} + +type RemoteCopyImageArgs struct { + Name string `json:"name,omitempty"` + DestRegion []string `json:"destRegion"` +} + +type RemoteCopyImageResult struct { + RemoteCopyImages []RemoteCopyImageModel `json:"result"` +} + +type ListCDSVolumeArgs struct { + MaxKeys int + InstanceId string + ZoneName string + Marker string +} + +type ListCDSVolumeResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Volumes []VolumeModel `json:"volumes"` +} + +type VolumeModel struct { + Type VolumeType `json:"type"` + StorageType StorageType `json:"storageType"` + Id string `json:"id"` + Name string `json:"name"` + DiskSizeInGB int `json:"diskSizeInGB"` + PaymentTiming string `json:"paymentTiming"` + ExpireTime string `json:"expireTime"` + Status VolumeStatus `json:"status"` + Desc string `json:"desc"` + Attachments []VolumeAttachmentModel `json:"attachments"` + ZoneName string `json:"zoneName"` + AutoSnapshotPolicy *AutoSnapshotPolicyModel `json:"autoSnapshotPolicy"` + CreateTime string `json:"createTime"` + IsSystemVolume bool `json:"isSystemVolume"` + RegionId string `json:"regionId"` + SourceSnapshotId string `json:"sourceSnapshotId"` + SnapshotNum string `json:"snapshotNum"` + Tags []model.TagModel `json:"tags"` + Encrypted bool `json:"encrypted"` +} + +type VolumeAttachmentModel struct { + VolumeId string `json:"volumeId"` + InstanceId string `json:"instanceId"` + Device string `json:"device"` + Serial string `json:"serial"` +} + +type AutoSnapshotPolicyModel struct { + CreatedTime string `json:"createdTime"` + Id string `json:"id"` + Status string `json:"status"` + RetentionDays int `json:"retentionDays"` + UpdatedTime string `json:"updatedTime"` + DeletedTime string `json:"deletedTime"` + LastExecuteTime string `json:"lastExecuteTime"` + VolumeCount int `json:"volumeCount"` + Name string `json:"name"` + TimePoints []int `json:"timePoints"` + RepeatWeekdays []int `json:"repeatWeekdays"` +} + +type DeleteInstanceArgs struct { + BbcRecycleFlag bool `json:"bbcRecycleFlag"` + InstanceIds []string `json:"instanceIds"` +} + +type GetBbcStockArgs struct { + Flavor string `json:"flavor"` + DeploySetIds []string `json:"deploySetIds"` +} + +type GetBbcStocksResult struct { + BbcStocks []BbcStock `json:"bbcStocks"` +} + +type BbcStock struct { + FlavorId string `json:"flavorId"` + InventoryQuantity int `json:"inventoryQuantity"` + UpdatedTime string `json:"updatedTime"` + CollectionTime string `json:"collectionTime"` + ZoneName string `json:"logicalZone"` +} + +type ListInstanceByInstanceIdArgs struct { + InstanceIds []string `json:"instanceIdList"` +} diff --git a/bce-sdk-go/services/bbc/operationLog.go b/bce-sdk-go/services/bbc/operationLog.go new file mode 100644 index 0000000..c89250c --- /dev/null +++ b/bce-sdk-go/services/bbc/operationLog.go @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// operationLog.go - the operationLog APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// GetOperationLog - get operation log +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get operation log +// +// RETURNS: +// - *GetOperationLogResult: results of getting operation log +// - error: nil if success otherwise the specific error +func GetOperationLog(cli bce.Client, args *GetOperationLogArgs) (*GetOperationLogResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getOperationLogUri()) + req.SetMethod(http.GET) + + if args.Marker != "" { + req.SetParam("marker", args.Marker) + } + + if args.MaxKeys != 0 { + req.SetParam("maxKeys", fmt.Sprintf("%d", args.MaxKeys)) + } + + if args.StartTime != "" { + req.SetParam("startTime", args.StartTime) + } + + if args.EndTime != "" { + req.SetParam("endTime", args.EndTime) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetOperationLogResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func getOperationLogUri() string { + return URI_PREFIX_V1 + REQUEST_OPERATION_LOG_URI +} diff --git a/bce-sdk-go/services/bbc/repairPlat.go b/bce-sdk-go/services/bbc/repairPlat.go new file mode 100644 index 0000000..55f028f --- /dev/null +++ b/bce-sdk-go/services/bbc/repairPlat.go @@ -0,0 +1,480 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// repairPlat.go - the repair plat APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +func ListRepairTasks(cli bce.Client, args *ListRepairTaskArgs) (*ListRepairTaskResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRepairTaskUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.InstanceId) != 0 { + req.SetParam("instanceId", args.InstanceId) + } + if len(args.ErrResult) != 0 { + req.SetParam("errResult", args.ErrResult) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRepairTaskResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func ListClosedRepairTasks(cli bce.Client, args *ListClosedRepairTaskArgs) (*ListClosedRepairTaskResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getClosedRepairTaskUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.InstanceId) != 0 { + req.SetParam("instanceId", args.InstanceId) + } + if len(args.TaskId) != 0 { + req.SetParam("taskId", args.TaskId) + } + if len(args.ErrResult) != 0 { + req.SetParam("errResult", args.ErrResult) + } + if args.StartTime != "" { + req.SetParam("startTime", args.StartTime) + } + if args.EndTime != "" { + req.SetParam("endTime", args.EndTime) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListClosedRepairTaskResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func GetTaskDetail(cli bce.Client, instanceId string) (*GetRepairTaskResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getTaskUriWithId(instanceId)) + req.SetMethod(http.GET) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetRepairTaskResult{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func AuthorizeRepairTask(cli bce.Client, reqBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getAuthorizeTaskUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func UnAuthorizeRepairTask(cli bce.Client, reqBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getUnAuthorizeTaskUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func ConfirmRepairTask(cli bce.Client, reqBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getConfirmTaskUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func DisConfirmRepairTask(cli bce.Client, reqBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getDisConfirmTaskUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func GetRepairTaskReocrd(cli bce.Client, reqBody *bce.Body) (*GetRepairRecords, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getTaskRecordUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetRepairRecords{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListRule - list the repair plat rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - *ListRuleResult: results of listing the repair plat rules +// - error: nil if success otherwise the specific error +func ListRule(cli bce.Client, reqBody *bce.Body) (*ListRuleResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListRuleUri()) + req.SetMethod(http.GET) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRuleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetRuleDetail - get the repair plat rule detail +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - ruleId: the specified rule id +// +// RETURNS: +// - *Rule: results of listing the repair plat rules +// - error: nil if success otherwise the specific error +func GetRuleDetail(cli bce.Client, ruleId string) (*Rule, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRuleDetailUri(ruleId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &Rule{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CreateRule - create the repair plat rule +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - *CreateRuleResult: results of the id of the repair plat rule which is created +// - error: nil if success otherwise the specific error +func CreateRule(cli bce.Client, reqBody *bce.Body) (*CreateRuleResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCreateRuleUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateRuleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteRule - delete the repair plat rule +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteRule(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeleteRuleUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DisableRule - disable the repair plat rule +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DisableRule(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDisableRuleUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// EnableRule - enable the repair plat rule +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func EnableRule(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getEnableRuleUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func getRepairTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI +} + +func getClosedRepairTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_CLOSED_TASK_URI +} + +func getTaskUriWithId(id string) string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/" + id +} + +func getAuthorizeTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/authorize" +} + +func getUnAuthorizeTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/unauthorize" +} + +func getConfirmTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/confirm" +} + +func getDisConfirmTaskUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/disconfirm" +} + +func getTaskRecordUri() string { + return URI_PREFIX_V2 + REQUEST_REPAIR_TASK_URI + "/record" +} + +func getListRuleUri() string { + return URI_PREFIX_V2 + REQUEST_RULE_URI +} + +func getRuleDetailUri(ruleId string) string { + return URI_PREFIX_V2 + REQUEST_RULE_URI + "/" + ruleId +} + +func getCreateRuleUri() string { + return URI_PREFIX_V2 + REQUEST_RULE_URI + REQUEST_CREATE_URI +} + +func getDeleteRuleUri() string { + return URI_PREFIX_V2 + REQUEST_RULE_URI + REQUEST_DELETE_URI +} + +func getEnableRuleUri() string { + return URI_PREFIX_V2 + REQUEST_RULE_URI + REQUEST_ENABLE_URI +} + +func getDisableRuleUri() string { + return URI_PREFIX_V2 + REQUEST_RULE_URI + REQUEST_DISABLE_URI +} diff --git a/bce-sdk-go/services/bbc/securityGroup.go b/bce-sdk-go/services/bbc/securityGroup.go new file mode 100644 index 0000000..543d3f3 --- /dev/null +++ b/bce-sdk-go/services/bbc/securityGroup.go @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Baidu, Inc. All Rights Reserved. + */ +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// BindSecurityGroups - Bind Security Groups +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BindSecurityGroups(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBindSecurityGroupsUri()) + req.SetMethod(http.POST) + req.SetParam("bind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// UnBindSecurityGroups - UnBind Security Groups +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnBindSecurityGroups(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getUnBindSecurityGroupsUri()) + req.SetMethod(http.POST) + req.SetParam("unbind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func getBindSecurityGroupsUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + SECURITY_GROUP_URI +} + +func getUnBindSecurityGroupsUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + SECURITY_GROUP_URI +} diff --git a/bce-sdk-go/services/bbc/tag.go b/bce-sdk-go/services/bbc/tag.go new file mode 100644 index 0000000..b83bcb7 --- /dev/null +++ b/bce-sdk-go/services/bbc/tag.go @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// tag.go - the tag APIs definition supported by the BBC service + +// Package bbc defines all APIs supported by the BBC service of BCE. +package bbc + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// bindTags - bind a bbc instance tags +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BindTags(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBindTagsUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("bind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// UnbindTags - unbind a bbc instance tags +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnbindTags(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getUnbindTagsUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("unbind", "") + req.SetBody(reqBody) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func getBindTagsUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/" + id + "/tag" +} + +func getUnbindTagsUriWithId(id string) string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/" + id + "/tag" +} diff --git a/bce-sdk-go/services/bbc/util.go b/bce-sdk-go/services/bbc/util.go new file mode 100644 index 0000000..863870c --- /dev/null +++ b/bce-sdk-go/services/bbc/util.go @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BBC service +package bbc + +import ( + "encoding/hex" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +const ( + URI_PREFIX_V1 = bce.URI_PREFIX + "v1" + URI_PREFIX_V2 = bce.URI_PREFIX + "v2" + + REQUEST_INSTANCE_URI = "/instance" + REQUEST_INSTANCE_LABEL_URI = "/instanceByLabel" + REQUEST_BATCH_DELETE_URI = "/batchDelete" + REQUEST_RECYCLE_URI = "/recycle" + REQUEST_RECOVERY_URI = "/recovery" + REQUEST_SUBNET_URI = "/vpcSubnet" + REQUEST_VPC_URI = "/vpc" + SECURITY_GROUP_URI = "/securitygroup" + + REQUEST_IMAGE_URI = "/image" + REQUEST_BATCHADDIP_URI = "/batchAddIp" + REQUEST_BATCHADDIPCROSSSUBNET_URI = "/batchAddIpCrossSubnet" + REQUEST_BATCHDELIP_URI = "/batchDelIp" + REQUEST_BATCH_CREATE_AUTORENEW_RULES_URI = "/batchCreateAutoRenewRules" + REQUEST_BATCH_Delete_AUTORENEW_RULES_URI = "/batchDeleteAutoRenewRules" + REQUEST_BATCH_REBUILD_INSTANCE_URI = "/batchRebuild" + + REQUEST_FLAVOR_URI = "/flavor" + REQUEST_FLAVOR_RAID_URI = "/flavorRaid" + REQUEST_COMMON_IMAGE_URI = "/flavor/image" + REQUEST_CUSTOM_IMAGE_URI = "/customFlavor/image" + REQUEST_IMAGE_SHAREDUSER_URI = "/sharedUsers" + + REQUEST_FLAVOR_ZONE_URI = "/order/flavorZone" + REQUEST_FLAVORS_URI = "/order/flavor" + + REQUEST_OPERATION_LOG_URI = "/operationLog" + + REQUEST_DEPLOY_SET_URI = "/deployset" + REQUEST_INSTANCE_PORT_URI = "/vpcPort" + + REQUEST_REPAIR_TASK_URI = "/task" + REQUEST_REPAIR_CLOSED_TASK_URI = "/closedTask" + + REQUEST_RULE_URI = "/rule" + REQUEST_CREATE_URI = "/create" + REQUEST_DELETE_URI = "/delete" + REQUEST_DISABLE_URI = "/disable" + REQUEST_ENABLE_URI = "/enable" + REQUEST_VOLUME_URI = "/volume" +) + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} + +func getVolumeUri() string { + return URI_PREFIX_V1 + REQUEST_VOLUME_URI +} + +func geBbcStockWithDeploySetUri() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/getStockWithDeploySet" +} + +func getListInstancesByIdsUrl() string { + return URI_PREFIX_V1 + REQUEST_INSTANCE_URI + "/listByInstanceId" +} diff --git a/bce-sdk-go/services/bcc/api/autoSnapshotPolicy.go b/bce-sdk-go/services/bcc/api/autoSnapshotPolicy.go new file mode 100644 index 0000000..afa5a27 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/autoSnapshotPolicy.go @@ -0,0 +1,298 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// autoSnapshotPolicy.go - the autoSnapshotPolicy APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateAutoSnapshotPolicy - create an automatic snapshot policy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create automatic snapshot policy +// +// RETURNS: +// - *CreateASPResult: the ID of the automatic snapshot policy newly created +// - error: nil if success otherwise the specific error +func CreateAutoSnapshotPolicy(cli bce.Client, args *CreateASPArgs) (*CreateASPResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUri()) + req.SetMethod(http.POST) + + if args != nil && len(args.ClientToken) != 0 { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateASPResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// AttachAutoSnapshotPolicy - attach an automatic snapshot policy to specified volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - aspId: the id of the automatic snapshot policy +// - args: the arguments to attach automatic snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func AttachAutoSnapshotPolicy(cli bce.Client, aspId string, args *AttachASPArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUriWithId(aspId)) + req.SetMethod(http.PUT) + + req.SetParam("attach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DetachAutoSnapshotPolicy - detach an automatic snapshot policy for specified volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - aspId: the id of the automatic snapshot policy +// - args: the arguments to detach automatic snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DetachAutoSnapshotPolicy(cli bce.Client, aspId string, args *DetachASPArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUriWithId(aspId)) + req.SetMethod(http.PUT) + + req.SetParam("detach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteAutoSnapshotPolicy - delete an automatic snapshot policy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - aspId: the id of the automatic snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteAutoSnapshotPolicy(cli bce.Client, aspId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUriWithId(aspId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ListAutoSnapshotPolicy - list all automatic snapshot policies with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the arguments to list automatic snapshot policies +// - : +// +// RETURNS: +// - *ListASPResult: the result of the automatic snapshot policies +// - error: nil if success otherwise the specific error +func ListAutoSnapshotPolicy(cli bce.Client, queryArgs *ListASPArgs) (*ListASPResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.AspName) != 0 { + req.SetParam("aspName", queryArgs.AspName) + } + if len(queryArgs.VolumeName) != 0 { + req.SetParam("volumeName", queryArgs.VolumeName) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListASPResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetAutoSnapshotPolicyDetail - get details of the specified automatic snapshot policy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - aspId: the id of the automatic snapshot policy +// +// RETURNS: +// - *GetASPDetailResult: the result of the given automatic snapshot policy +// - error: nil if success otherwise the specific error +func GetAutoSnapshotPolicyDetail(cli bce.Client, aspId string) (*GetASPDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUriWithId(aspId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetASPDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// UpdateAutoSnapshotPolicy - update an automatic snapshot policy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to update automatic snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateAutoSnapshotPolicy(cli bce.Client, args *UpdateASPArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getASPUri() + "/update") + req.SetMethod(http.PUT) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} diff --git a/bce-sdk-go/services/bcc/api/cds.go b/bce-sdk-go/services/bcc/api/cds.go new file mode 100644 index 0000000..95cf9f9 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/cds.go @@ -0,0 +1,900 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// cds.go - the cds APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateCDSVolume - create a specified count of cds volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create cds volumes +// +// RETURNS: +// - *CreateCDSVolumeResult: the result of volume ids newly created +// - error: nil if success otherwise the specific error +func CreateCDSVolume(cli bce.Client, args *CreateCDSVolumeArgs) (*CreateCDSVolumeResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUri()) + req.SetMethod(http.POST) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateCDSVolumeResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// CreateCDSVolumeV3 - create a specified count of cds volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create cds volumes +// +// RETURNS: +// - *CreateCDSVolumeResult: the result of volume ids newly created +// - error: nil if success otherwise the specific error +func CreateCDSVolumeV3(cli bce.Client, args *CreateCDSVolumeV3Args) (*CreateCDSVolumeResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeV3Uri()) + req.SetMethod(http.POST) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateCDSVolumeResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListCDSVolume - list all cds volumes with the given parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the optional arguments to list cds volumes +// +// RETURNS: +// - *ListCDSVolumeResult: the result of cds volume list +// - error: nil if success otherwise the specific error +func ListCDSVolume(cli bce.Client, queryArgs *ListCDSVolumeArgs) (*ListCDSVolumeResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.InstanceId) != 0 { + req.SetParam("instanceId", queryArgs.InstanceId) + } + if len(queryArgs.ZoneName) != 0 { + req.SetParam("zoneName", queryArgs.ZoneName) + } + if len(queryArgs.ClusterId) != 0 { + req.SetParam("clusterId", queryArgs.ClusterId) + } + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.ChargeFilter) != 0 { + req.SetParam("chargeFilter", queryArgs.ChargeFilter) + } + if len(queryArgs.UsageFilter) != 0 { + req.SetParam("usageFilter", queryArgs.UsageFilter) + } + if len(queryArgs.Name) != 0 { + req.SetParam("name", queryArgs.Name) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListCDSVolumeResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListCDSVolumeV3 - list all cds volumes with the given parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the optional arguments to list cds volumes +// +// RETURNS: +// - *ListCDSVolumeResultV3: the result of cds volume list +// - error: nil if success otherwise the specific error +func ListCDSVolumeV3(cli bce.Client, queryArgs *ListCDSVolumeArgs) (*ListCDSVolumeResultV3, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeV3Uri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.InstanceId) != 0 { + req.SetParam("instanceId", queryArgs.InstanceId) + } + if len(queryArgs.ZoneName) != 0 { + req.SetParam("zoneName", queryArgs.ZoneName) + } + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.ChargeFilter) != 0 { + req.SetParam("chargeFilter", queryArgs.ChargeFilter) + } + if len(queryArgs.UsageFilter) != 0 { + req.SetParam("usageFilter", queryArgs.UsageFilter) + } + if len(queryArgs.Name) != 0 { + req.SetParam("name", queryArgs.Name) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListCDSVolumeResultV3{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetCDSVolumeDetail - get details of the specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume +// +// RETURNS: +// - *GetVolumeDetailResult: the result of the specified cds volume details +// - error: nil if success otherwise the specific error +func GetCDSVolumeDetail(cli bce.Client, volumeId string) (*GetVolumeDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetVolumeDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetCDSVolumeDetail - get details of the specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume +// +// RETURNS: +// - *GetVolumeDetailResultV3: the result of the specified cds volume details +// - error: nil if success otherwise the specific error +func GetCDSVolumeDetailV3(cli bce.Client, volumeId string) (*GetVolumeDetailResultV3, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeV3UriWithId(volumeId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetVolumeDetailResultV3{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// AttachCDSVolume - attach an cds volume to a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume +// - args: the arguments of instance id +// +// RETURNS: +// - *AttachVolumeResult: the result of the attachment +// - error: nil if success otherwise the specific error +func AttachCDSVolume(cli bce.Client, volumeId string, args *AttachVolumeArgs) (*AttachVolumeResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("attach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &AttachVolumeResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DetachCDSVolume - detach an cds volume for a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume +// - args: the arguments of instance id detached from +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DetachCDSVolume(cli bce.Client, volumeId string, args *DetachVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("detach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteCDSVolume - delete a specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume to be deleted +// - : +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteCDSVolume(cli bce.Client, volumeId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteCDSVolumeNew - delete a specified cds volume, the difference from the above api is that \ +// can control whether to delete the snapshot associated with the volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume to be deleted +// - args: the arguments to delete cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteCDSVolumeNew(cli bce.Client, volumeId string, args *DeleteCDSVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ResizeCDSVolume - resize a specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume to be resized +// - args: the arguments to resize cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ResizeCDSVolume(cli bce.Client, volumeId string, args *ResizeCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + req.SetParam("resize", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RollbackCDSVolume - roll back a specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the cds volume to be rolled back +// - args: the arguments to roll back the cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RollbackCDSVolume(cli bce.Client, volumeId string, args *RollbackCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("rollback", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// PurchaseReservedCDSVolume - renew a specified volume to extend expiration time. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the volume to be renewed +// - args: the arguments to renew cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PurchaseReservedCDSVolume(cli bce.Client, volumeId string, args *PurchaseReservedCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + req.SetParam("purchaseReserved", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RenameCDSVolume - rename a specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the volume to be renamed +// - args: the arguments to rename volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RenameCDSVolume(cli bce.Client, volumeId string, args *RenameCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("rename", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyCDSVolume - modify attributes of the specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the volume to be modified +// - args: arguments to modify volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyCDSVolume(cli bce.Client, volumeId string, args *ModifyCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("modify", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyChargeTypeCDSVolume - modify the volume billing method, only support Postpaid to Prepaid and Prepaid to Postpaid +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - volumeId: id of the volume to be modified +// - args: the arguments to modify volume billing method +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyChargeTypeCDSVolume(cli bce.Client, volumeId string, args *ModifyChargeTypeCSDVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeUriWithId(volumeId)) + req.SetMethod(http.PUT) + + req.SetParam("modifyChargeType", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// AutoRenewCDSVolume - auto renew the specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to auto renew the cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func AutoRenewCDSVolume(cli bce.Client, args *AutoRenewCDSVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getAutoRenewVolumeUri()) + req.SetMethod(http.POST) + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + + return nil +} + +// CancelAutoRenewCDSVolume - cancel auto renew the specified cds volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to cancel auto renew the cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CancelAutoRenewCDSVolume(cli bce.Client, args *CancelAutoRenewCDSVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCancelAutoRenewVolumeUri()) + req.SetMethod(http.POST) + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + + return nil +} + +// GetAvailableDiskInfo - get available diskInfos of the specified zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: the zone name eg:cn-bj-a +// +// RETURNS: +// - *GetAvailableDiskInfoResult: the result of the specified zone diskInfos +// - error: nil if success otherwise the specific error +func GetAvailableDiskInfo(cli bce.Client, zoneName string) (*GetAvailableDiskInfoResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getAvailableDiskInfo()) + req.SetMethod(http.GET) + req.SetParam("zoneName", zoneName) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetAvailableDiskInfoResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DeletePrepayVolume - delete the volumes for prepay +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments of method +// +// RETURNS: +// - *VolumeDeleteResultResponse: the result of deleting volumes +// - error: nil if success otherwise the specific error +func DeletePrepayVolume(cli bce.Client, args *VolumePrepayDeleteRequestArgs) (*VolumeDeleteResultResponse, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeletePrepayVolumeUri()) + req.SetMethod(http.POST) + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &VolumeDeleteResultResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func TagVolume(cli bce.Client, volumeId string, args *TagVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getTagVolumeUri(volumeId)) + req.SetMethod(http.PUT) + req.SetParam("bind", "") + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func UntagVolume(cli bce.Client, volumeId string, args *TagVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getUntagVolumeUri(volumeId)) + req.SetMethod(http.PUT) + req.SetParam("unbind", "") + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/bcc/api/deploySet.go b/bce-sdk-go/services/bcc/api/deploySet.go new file mode 100644 index 0000000..92974b6 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/deploySet.go @@ -0,0 +1,264 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// deploySet.go - the deploy set APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - *CreateDeploySetResult: results of creating a deploy set +// - error: nil if success otherwise the specific error +func CreateDeploySet(cli bce.Client, clientToken string, reqBody *bce.Body) (*CreateDeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetCreateUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateDeploySetResp{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + if jsonBody != nil && len(jsonBody.DeploySetIds) > 0 { + jsonResp := &CreateDeploySetResult{ + DeploySetId: jsonBody.DeploySetIds[0], + } + return jsonResp, nil + } + return nil, nil +} + +// ListDeploySets - list all deploy sets +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func ListDeploySets(cli bce.Client) (*ListDeploySetsResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetListUri()) + req.SetMethod(http.GET) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListDeploySetsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ModifyDeploySet - modify the deploy set atrribute +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyDeploySet(cli bce.Client, deploySetId string, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(deploySetId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + req.SetParam("modifyAttribute", "") + + // Send request and get response + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteDeploySet(cli bce.Client, deploySetId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(deploySetId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + + return nil +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *GetDeploySetResult: the detail of the deploy set +// - error: nil if success otherwise the specific error +func GetDeploySet(cli bce.Client, deploySetId string) (*DeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUrl(deploySetId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeploySetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func UpdateInstanceDeploy(cli bce.Client, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getUpdateInstanceDeployUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func DelInstanceDeploy(cli bce.Client, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDelInstanceDeployUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func getDeploySetCreateUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_DEPLOYSET_URI + REQUEST_CREATE_URI +} + +func getDeploySetListUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_DEPLOYSET_URI + REQUEST_LIST_URI +} + +func getDeploySetUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_DEPLOYSET_URI + "/" + id +} + +func getDeploySetUrl(id string) string { + return URI_PREFIXV2 + REQUEST_DEPLOYSET_URI + "/" + id +} + +func getUpdateInstanceDeployUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_DEPLOYSET_URI + REQUEST_UPDATE_URI +} + +func getDelInstanceDeployUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_DEPLOYSET_URI + REQUEST_DEL_URI +} diff --git a/bce-sdk-go/services/bcc/api/image.go b/bce-sdk-go/services/bcc/api/image.go new file mode 100644 index 0000000..1560bd4 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/image.go @@ -0,0 +1,557 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// image.go - the image APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" + "strings" +) + +// CreateImage - create an image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create image +// +// RETURNS: +// - *CreateImageResult: the result of the image newly created +// - error: nil if success otherwise the specific error +func CreateImage(cli bce.Client, args *CreateImageArgs) (*CreateImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUri()) + req.SetMethod(http.POST) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListImage - list all images with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the arguments to list images +// +// RETURNS: +// - *ListImageResult: result of the image list +// - error: nil if success otherwise the specific error +func ListImage(cli bce.Client, queryArgs *ListImageArgs) (*ListImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.ImageName) != 0 { + if len(queryArgs.ImageType) != 0 && strings.EqualFold("custom", queryArgs.ImageType) { + req.SetParam("imageName", queryArgs.ImageName) + } else { + return nil, errors.New("only the custom image type could filter by name") + } + } + if len(queryArgs.ImageType) != 0 { + req.SetParam("imageType", queryArgs.ImageType) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetImageDetail - get details of the specified image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image +// +// RETURNS: +// - *GetImageDetailResult: result of image details +// - error: nil if success otherwise the specific error +func GetImageDetail(cli bce.Client, imageId string) (*GetImageDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImageDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DeleteImage - delete a specified image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of image to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteImage(cli bce.Client, imageId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RemoteCopyImage - copy custom images across regions, only custom images supported, the system \ +// and service integration images cannot be copied. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be copied +// - args: the arguments to copy image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RemoteCopyImage(cli bce.Client, imageId string, args *RemoteCopyImageArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("remoteCopy", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RemoteCopyImageReturnImageIds - copy custom images across regions, only custom images supported, the system \ +// and service integration images cannot be copied. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be copied +// - args: the arguments to copy image +// +// RETURNS: +// - imageIds of destination region if success otherwise the specific error +func RemoteCopyImageReturnImageIds(cli bce.Client, imageId string, args *RemoteCopyImageArgs) (*RemoteCopyImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("remoteCopy", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &RemoteCopyImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// CancelRemoteCopyImage - cancel the image copy across regions +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CancelRemoteCopyImage(cli bce.Client, imageId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("cancelRemoteCopy", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ShareImage - share a specified custom image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be shared +// - args: the arguments to share image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ShareImage(cli bce.Client, imageId string, args *SharedUser) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("share", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// UnShareImage - unshare a specified image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image to be unshared +// - args: the arguments to unshare image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnShareImage(cli bce.Client, imageId string, args *SharedUser) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageUriWithId(imageId)) + req.SetMethod(http.POST) + + req.SetParam("unshare", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetImageSharedUser - get the list of users that the image has been shared with +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - imageId: id of the image +// +// RETURNS: +// - *GetImageSharedUserResult: result of the shared users +// - error: nil if success otherwise the specific error +func GetImageSharedUser(cli bce.Client, imageId string) (*GetImageSharedUserResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageSharedUserUri(imageId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImageSharedUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetImageOS - get the operating system information of the instance in batches according to the instance ids +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments of instance ids +// +// RETURNS: +// - *GetImageOsResult: result of the operating system information +// - error: nil if success otherwise the specific error +func GetImageOS(cli bce.Client, args *GetImageOsArgs) (*GetImageOsResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageOsUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetImageOsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func BindImageToTags(cli bce.Client, imageId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageToTagsUri(imageId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + req.SetParam("bind", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func UnBindImageToTags(cli bce.Client, imageId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImageToTagsUri(imageId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + req.SetParam("unbind", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func ImportCustomImage(cli bce.Client, args *ImportCustomImageArgs) (*ImportCustomImageResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getImportCustomImageUri()) + req.SetMethod(http.POST) + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ImportCustomImageResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetAvailableImagesBySpec(cli bce.Client, args *GetAvailableImagesBySpecArg) (*GetAvailableImagesBySpecResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getAvailableImagesBySpecUri()) + req.SetMethod(http.GET) + + if len(args.Spec) > 0 { + req.SetParam("spec", args.Spec) + } + if len(args.OsName) > 0 { + req.SetParam("osName", args.OsName) + } + if len(args.Marker) > 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys > 0 { + req.SetParam("maxKeys", fmt.Sprint(args.MaxKeys)) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetAvailableImagesBySpecResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bcc/api/instance.go b/bce-sdk-go/services/bcc/api/instance.go new file mode 100644 index 0000000..a3e33bb --- /dev/null +++ b/bce-sdk-go/services/bcc/api/instance.go @@ -0,0 +1,2238 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// instance.go - the instance APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateInstance - create an instance with specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceResult: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func CreateInstance(cli bce.Client, args *CreateInstanceArgs, reqBody *bce.Body) (*CreateInstanceResult, + error) { + // Build the request + clientToken := args.ClientToken + requestToken := args.RequestToken + req := &bce.BceRequest{} + req.SetUri(getInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CreateInstance - create an instance with specified parameters and support the passing in of label +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceResult: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func CreateInstanceByLabel(cli bce.Client, args *CreateSpecialInstanceBySpecArgs, reqBody *bce.Body) (*CreateInstanceResult, + error) { + // Build the request + clientToken := args.ClientToken + requestToken := args.RequestToken + req := &bce.BceRequest{} + req.SetUri(getInstanceByLabelUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CreateInstanceBySpec - create an instance with specified spec. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceBySpecResult: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func CreateInstanceBySpec(cli bce.Client, args *CreateInstanceBySpecArgs, reqBody *bce.Body) ( + *CreateInstanceBySpecResult, error) { + // Build the request + clientToken := args.ClientToken + requestToken := args.RequestToken + req := &bce.BceRequest{} + req.SetUri(getInstanceBySpecUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceBySpecResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CreateInstanceV3 - create an instance with specified spec. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceV3Result: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func CreateInstanceV3(cli bce.Client, args *CreateInstanceV3Args, reqBody *bce.Body) ( + *CreateInstanceV3Result, error) { + // Build the request + clientToken := args.ClientToken + requestToken := args.RequestToken + req := &bce.BceRequest{} + req.SetUri(getInstanceUriV3()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetHeader("x-request-token", requestToken) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceV3Result{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListInstances - list all instances with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances +// +// RETURNS: +// - *ListInstanceResult: result of the instance list +// - error: nil if success otherwise the specific error +func ListInstances(cli bce.Client, args *ListInstanceArgs) (*ListInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.DedicatedHostId) != 0 { + req.SetParam("dedicateHostId", args.DedicatedHostId) + } + if len(args.InternalIp) != 0 { + req.SetParam("internalIp", args.InternalIp) + } + if len(args.ZoneName) != 0 { + req.SetParam("zoneName", args.ZoneName) + } + if len(args.KeypairId) != 0 { + req.SetParam("keypairId", args.KeypairId) + } + if args.AutoRenew { + req.SetParam("autoRenew", fmt.Sprint(args.AutoRenew)) + } + if len(args.InstanceIds) != 0 { + req.SetParam("instanceIds", args.InstanceIds) + } + if len(args.InstanceNames) != 0 { + req.SetParam("instanceNames", args.InstanceNames) + } + if len(args.CdsIds) != 0 { + req.SetParam("cdsIds", args.CdsIds) + } + if len(args.DeploySetIds) != 0 { + req.SetParam("deploySetIds", args.DeploySetIds) + } + if len(args.SecurityGroupIds) != 0 { + req.SetParam("securityGroupIds", args.SecurityGroupIds) + } + if len(args.PaymentTiming) != 0 { + req.SetParam("paymentTiming", args.PaymentTiming) + } + if len(args.Status) != 0 { + req.SetParam("status", args.Status) + } + if len(args.Tags) != 0 { + req.SetParam("tags", args.Tags) + } + if len(args.PrivateIps) != 0 { + req.SetParam("privateIps", args.PrivateIps) + } + if len(args.VpcId) != 0 { + req.SetParam("vpcId", args.VpcId) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListRecycleInstances - list all instances in the recycle bin with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances in the recycle bin +// +// RETURNS: +// - *ListRecycleInstanceResult: result of the instance in the recycle bin list +// - error: nil if success otherwise the specific error +func ListRecycleInstances(cli bce.Client, args *ListRecycleInstanceArgs) (*ListRecycleInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRecycleInstanceListUri()) + req.SetMethod(http.POST) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRecycleInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// listServersByMarkerV3 - list all instances with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances +// +// RETURNS: +// - *LogicMarkerResultResponseV3: result of the instance +// - error: nil if success otherwise the specific error +func ListServersByMarkerV3(cli bce.Client, args *ListServerRequestV3Args) (*LogicMarkerResultResponseV3, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getServersByMarkerV3Uri()) + req.SetMethod(http.POST) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &LogicMarkerResultResponseV3{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetInstanceDetail - get details of the specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetInstanceDetailResult: result of the instance details +// - error: nil if success otherwise the specific error +func GetInstanceDetail(cli bce.Client, instanceId string) (*GetInstanceDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + req.SetParam("isDeploySet", "false") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetInstanceDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func GetInstanceDetailWithDeploySet(cli bce.Client, instanceId string, isDeploySet bool) (*GetInstanceDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + if isDeploySet == true { + req.SetParam("isDeploySet", "true") + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetInstanceDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func GetInstanceDetailWithDeploySetAndFailed(cli bce.Client, instanceId string, + isDeploySet bool, containsFailed bool) (*GetInstanceDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.GET) + if isDeploySet == true { + req.SetParam("isDeploySet", "true") + } + if containsFailed == true { + req.SetParam("containsFailed", "true") + } else { + req.SetParam("containsFailed", "false") + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetInstanceDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteInstance - delete a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteInstance - delete a specified instance,contains prepay or postpay instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// RETURNS: +// - error: nil if success otherwise the specific error + +func DeleteInstanceIngorePayment(cli bce.Client, args *DeleteInstanceIngorePaymentArgs) (*DeleteInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeleteInstanceDeleteIngorePaymentUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeleteInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DeleteRecycledInstance - delete a recycled bcc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteRecycledInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeleteRecycledInstanceUri(instanceId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// AutoReleaseInstance - set releaseTime of a postpay instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the specific instance ID +// - args: the arguments to auto release instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func AutoReleaseInstance(cli bce.Client, instanceId string, args *AutoReleaseArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("autorelease", "") + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ResizeInstance - resize a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be resized +// - reqBody: the request body to resize instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ResizeInstance(cli bce.Client, instanceId, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("resize", "") + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RebuildInstance - rebuild a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebuilded +// - reqBody: the request body to rebuild instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RebuildInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("rebuild", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// StartInstance - start a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be started +// +// RETURNS: +// - error: nil if success otherwise the specific error +func StartInstance(cli bce.Client, instanceId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("start", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// StopInstance - stop a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be stoped +// - reqBody: the request body to stop instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func StopInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("stop", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// - reqBody: the request body to reboot instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RebootInstance(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("reboot", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func RecoveryInstance(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRecoveryInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ChangeInstancePass - change password of specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - reqBody: the request body to change paasword of instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ChangeInstancePass(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("changePass", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyDeletionProtection - Modify deletion protection of specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - reqBody: the request body to set an instance, default 0 for deletable and 1 for deletion protection +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyDeletionProtection(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceDeletionProtectionUri(instanceId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyInstanceAttribute - modify attribute of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be modified +// - reqBody: the request body to modify instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstanceAttribute(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("modifyAttribute", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyInstanceDesc - modify desc of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be modified +// - reqBody: the request body to modify instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstanceDesc(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("modifyDesc", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ModifyInstanceHostname - modify hostname of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be modified +// - reqBody: the request body to modify instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ModifyInstanceHostname(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("changeHostname", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BindSecurityGroup - bind security group for a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - reqBody: the request body to bind security group associate to the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BindSecurityGroup(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("bind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// UnBindSecurityGroup - unbind security group for a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - reqBody: the request body to unbind security group associate to the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnBindSecurityGroup(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("unbind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetInstanceVNC - get VNC address of the specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetInstanceVNCResult: result of the VNC address of the instance +// - error: nil if success otherwise the specific error +func GetInstanceVNC(cli bce.Client, instanceId string) (*GetInstanceVNCResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceVNCUri(instanceId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetInstanceVNCResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// InstancePurchaseReserved - renew a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be renewed +// - reqBody: the request body to renew instance +// +// RETURNS: +// - *api.InstancePurchaseReservedResult: the result of renew a specified instance +// - error: nil if success otherwise the specific error +func InstancePurchaseReserved(cli bce.Client, instanceId, relatedRenewFlag, clientToken string, reqBody *bce.Body) (*InstancePurchaseReservedResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("purchaseReserved", "") + req.SetParam("relatedRenewFlag", relatedRenewFlag) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstancePurchaseReservedResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// DeleteInstanceWithRelatedResource - delete an instance with related resources +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// - reqBody: request body to delete instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteInstanceWithRelatedResource(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceUriWithId(instanceId)) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// InstanceChangeSubnet - change the subnet to which the instance belongs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to change subnet of instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func InstanceChangeSubnet(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getChangeSubnetUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// InstanceChangeVpc - change the vpc to which the instance belongs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to change vpc of instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func InstanceChangeVpc(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getChangeVpcUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchAddIp - Add ips to instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchAddIp(cli bce.Client, args *BatchAddIpArgs, reqBody *bce.Body) (*BatchAddIpResponse, error) { + // Build the request + clientToken := args.ClientToken + req := &bce.BceRequest{} + req.SetUri(getBatchAddIpUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchAddIpResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// BatchDelIp - Delete ips of instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchDelIp(cli bce.Client, args *BatchDelIpArgs, reqBody *bce.Body) error { + // Build the request + clientToken := args.ClientToken + req := &bce.BceRequest{} + req.SetUri(getBatchDelIpUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ResizeInstanceBySpec - resize a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be resized +// - reqBody: the request body to resize instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ResizeInstanceBySpec(cli bce.Client, instanceId, clientToken string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getResizeInstanceBySpec(instanceId)) + req.SetMethod(http.PUT) + req.SetParam("resize", "") + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchRebuildInstances - batch rebuild instances +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to rebuild instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchRebuildInstances(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRebuildBatchInstanceUri()) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ChangeToPrepaid - to prepaid +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to ChangeToPrepaid +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ChangeToPrepaid(cli bce.Client, instanceId string, reqBody *bce.Body) (*ChangeToPrepaidResponse, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getChangeToPrepaidUri(instanceId)) + req.SetMethod(http.POST) + req.SetBody(reqBody) + req.SetParam("toPrepay", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ChangeToPrepaidResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// bindInstanceToTags - bind instance to tags +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to bindInstanceToTags +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BindInstanceToTags(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getbindInstanceToTagsUri(instanceId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + req.SetParam("bind", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// UnBindInstanceToTags - unbind instance to tags +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to unbindInstanceToTags +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UnBindInstanceToTags(cli bce.Client, instanceId string, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getbindInstanceToTagsUri(instanceId)) + req.SetMethod(http.PUT) + req.SetBody(reqBody) + req.SetParam("unbind", "") + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// GetInstanceNoChargeList - get instance with nocharge list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances +// +// RETURNS: +// - *ListInstanceResult: result of the instance list +// - error: nil if success otherwise the specific error +func GetInstanceNoChargeList(cli bce.Client, args *ListInstanceArgs) (*ListInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(GetInstanceNoChargeListUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.InternalIp) != 0 { + req.SetParam("internalIp", args.InternalIp) + } + if len(args.ZoneName) != 0 { + req.SetParam("zoneName", args.ZoneName) + } + if len(args.KeypairId) != 0 { + req.SetParam("keypairId", args.KeypairId) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// createBidInstance - create an instance with specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceResult: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func CreateBidInstance(cli bce.Client, clientToken string, reqBody *bce.Body) (*CreateInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(GetCreateBidInstanceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// CancelBidOrder - Cancel the bidding instance order. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to cancel bid order +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CancelBidOrder(cli bce.Client, clientToken string, reqBody *bce.Body) (*CreateBidInstanceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(GetCancelBidOrderUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateBidInstanceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetBidInstancePrice - get the market price of the specified bidding instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - *GetBidInstancePriceResult: result of the market price of the specified bidding instance +// - error: nil if success otherwise the specific error +func GetBidInstancePrice(cli bce.Client, clientToken string, reqBody *bce.Body) (*GetBidInstancePriceResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBidInstancePriceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + if clientToken != "" { + req.SetParam("clientToken", clientToken) + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetBidInstancePriceResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListBidFlavor - list all flavors of the bidding instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListBidFlavorResult: result of the flavor list +// - error: nil if success otherwise the specific error +func ListBidFlavor(cli bce.Client) (*ListBidFlavorResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(listBidFlavorUri()) + req.SetMethod(http.POST) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListBidFlavorResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetInstanceResizeStock(cli bce.Client, args *ResizeInstanceStockArgs) (*InstanceStockResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getResizeInstanceStock()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceStockResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetAllStocks - get the bcc and bbc's stock +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *GetAllStocksResult: the result of the bcc and bbc's stock +// - error: nil if success otherwise the specific error +func GetAllStocks(cli bce.Client) (*GetAllStocksResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getAllStocks()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetAllStocksResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetStockWithDeploySet - get the bcc's stock with deploySet +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get the bcc's stock with deploySet +// +// RETURNS: +// - *GetStockWithDeploySetResults: the result of the bcc's stock +// - error: nil if success otherwise the specific error +func GetStockWithDeploySet(cli bce.Client, args *GetStockWithDeploySetArgs) (*GetStockWithDeploySetResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getStockWithDeploySet()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetStockWithDeploySetResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetStockWithSpec - get the bcc's stock with spec +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get the bcc's stock with spec +// +// RETURNS: +// - *GetStockWithSpecResults: the result of the bcc's stock +// - error: nil if success otherwise the specific error +func GetStockWithSpec(cli bce.Client, args *GetStockWithSpecArgs) (*GetStockWithSpecResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getStockWithSpec()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetStockWithSpecResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetInstanceCreateStock(cli bce.Client, args *CreateInstanceStockArgs) (*InstanceStockResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getCreateInstanceStock()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &InstanceStockResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// BatchCreateAutoRenewRules - Batch Create AutoRenew Rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchCreateAutoRenewRules(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchCreateAutoRenewRulesUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + //print(resp) + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchDeleteAutoRenewRules - Batch Delete AutoRenew Rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchDeleteAutoRenewRules(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchDeleteAutoRenewRulesUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + //print(resp) + + defer func() { resp.Body().Close() }() + return nil +} + +// ListInstanceByInstanceIds - list instance by instanceId +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListInstancesResult: result of the instance list +// - error: nil if success otherwise the specific error +func ListInstanceByInstanceIds(cli bce.Client, args *ListInstanceByInstanceIdArgs) (*ListInstancesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListInstancesByIdsUrl()) + req.SetMethod(http.POST) + + if args != nil { + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstancesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// BatchDeleteInstanceWithRelatedResource - delete instance with related resources +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to delete instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchDeleteInstanceWithRelatedResource(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchDeleteInstanceWithRelatedResourceUri()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// BatchStartInstance - batch start specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: request body to batch start instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchStartInstance(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchStartInstanceUri()) + req.SetMethod(http.PUT) + req.SetParam("start", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { _ = resp.Body().Close() }() + return nil +} + +// BatchStopInstance - batch stop specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to batch stop instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchStopInstance(cli bce.Client, reqBody *bce.Body) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchStopInstanceUri()) + req.SetMethod(http.PUT) + req.SetParam("stop", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// ListInstanceTypes - list all instances type with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances type +// +// RETURNS: +// - *ListInstanceTypeResults: result of the instance type list +// - error: nil if success otherwise the specific error +func ListInstanceTypes(cli bce.Client, args *ListInstanceTypeArgs) (*ListInstanceTypeResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListInstanceTypesUri()) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.ZoneName) != 0 { + req.SetParam("zoneName", args.ZoneName) + } + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstanceTypeResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListIdMappings - Long and short ID conversion parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to Long and short ID conversion +// +// RETURNS: +// - *ListIdMappingResults: result of the Long short mapping +// - error: nil if success otherwise the specific error +func ListIdMappings(cli bce.Client, args *ListIdMappingArgs) (*ListIdMappingResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListIdMappingsUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListIdMappingResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// BatchResizeInstance - batch resize a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to resize instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BatchResizeInstance(cli bce.Client, reqBody *bce.Body) (*BatchResizeInstanceResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchResizeInstanceUri()) + req.SetMethod(http.PUT) + req.SetParam("resize", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchResizeInstanceResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func GetInstanceDeleteProgress(cli bce.Client, reqBody *bce.Body) (map[string]interface{}, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceDeleteProgress()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + var jsonBody map[string]interface{} + if err := resp.ParseJsonBody(&jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func ListAvailableResizeSpecs(cli bce.Client, reqBody *bce.Body) ( + *ListAvailableResizeSpecResults, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getListAvailableResizeSpecsUri()) + req.SetParam("resizeList", "") + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListAvailableResizeSpecResults{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func BatchChangeInstanceToPrepay(cli bce.Client, reqBody *bce.Body) (*BatchChangeInstanceBillingMethodResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchChangeInstanceToPrepay()) + req.SetParam("toPrepay", "") + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchChangeInstanceBillingMethodResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func BatchChangeInstanceToPostpay(cli bce.Client, reqBody *bce.Body) (*BatchChangeInstanceBillingMethodResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getBatchChangeInstanceToPostpay()) + req.SetParam("toPostpay", "") + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BatchChangeInstanceBillingMethodResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func ListInstanceRoles(cli bce.Client) (*ListInstanceRolesResult, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(listInstanceRoles()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListInstanceRolesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func BindInstanceRole(cli bce.Client, reqBody *bce.Body) (*BindInstanceRoleResult, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(postInstanceRole()) + req.SetMethod(http.POST) + req.SetParam("bind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &BindInstanceRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func UnBindInstanceRole(cli bce.Client, reqBody *bce.Body) (*UnBindInstanceRoleResult, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(postInstanceRole()) + req.SetMethod(http.POST) + req.SetParam("unbind", "") + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &UnBindInstanceRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +func DeleteIpv6(cli bce.Client, reqBody *bce.Body) error { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(deleteIpv6()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func AddIpv6(cli bce.Client, reqBody *bce.Body) (*AddIpv6Result, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(addIpv6()) + req.SetMethod(http.POST) + req.SetBody(reqBody) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &AddIpv6Result{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bcc/api/keypair.go b/bce-sdk-go/services/bcc/api/keypair.go new file mode 100644 index 0000000..1d908c1 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/keypair.go @@ -0,0 +1,298 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// keypair.go - the keypair APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateKeypair(cli bce.Client, args *CreateKeypairArgs) (*KeypairResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairUri()) + req.SetMethod(http.POST) + //req.SetParam("create", "") + + if len(args.ClientToken) != 0 { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &KeypairResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ImportKeypair(cli bce.Client, args *ImportKeypairArgs) (*KeypairResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairUri()) + req.SetMethod(http.PUT) + req.SetParam("import", "") + + if len(args.ClientToken) != 0 { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &KeypairResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func AttachKeypair(cli bce.Client, args *AttackKeypairArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(args.KeypairId)) + req.SetMethod(http.PUT) + req.SetParam("attach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func DetachKeypair(cli bce.Client, args *DetachKeypairArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(args.KeypairId)) + req.SetMethod(http.PUT) + req.SetParam("detach", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func DeleteKeypair(cli bce.Client, args *DeleteKeypairArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(args.KeypairId)) + req.SetMethod(http.DELETE) + req.SetParam("delete", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func GetKeypairDetail(cli bce.Client, keypairId string) (*KeypairResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(keypairId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &KeypairResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ListKeypairs(cli bce.Client, queryArgs *ListKeypairArgs) (*ListKeypairResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + if len(queryArgs.Name) != 0 { + req.SetParam("name", queryArgs.Name) + } + } + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListKeypairResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func RenameKeypair(cli bce.Client, args *RenameKeypairArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(args.KeypairId)) + req.SetMethod(http.PUT) + req.SetParam("rename", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func UpdateKeypairDescription(cli bce.Client, args *KeypairUpdateDescArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getKeypairWithId(args.KeypairId)) + req.SetMethod(http.PUT) + req.SetParam("updateDesc", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/bcc/api/model.go b/bce-sdk-go/services/bcc/api/model.go new file mode 100644 index 0000000..51e1e09 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/model.go @@ -0,0 +1,2278 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type InstanceStatus string + +const ( + InstanceStatusRunning InstanceStatus = "Running" + InstanceStatusStarting InstanceStatus = "Starting" + InstanceStatusStopping InstanceStatus = "Stopping" + InstanceStatusStopped InstanceStatus = "Stopped" + InstanceStatusDeleted InstanceStatus = "Deleted" + InstanceStatusScaling InstanceStatus = "Scaling" + InstanceStatusExpired InstanceStatus = "Expired" + InstanceStatusError InstanceStatus = "Error" + InstanceStatusSnapshotProcessing InstanceStatus = "SnapshotProcessing" + InstanceStatusImageProcessing InstanceStatus = "ImageProcessing" + InstanceStatusChangeVpcProcessing InstanceStatus = "ChangeVpc" + InstanceStatusRecycled InstanceStatus = "Recycled" +) + +type InstanceType string + +const ( + InstanceTypeN1 InstanceType = "N1" + InstanceTypeN2 InstanceType = "N2" + InstanceTypeN3 InstanceType = "N3" + InstanceTypeC1 InstanceType = "C1" + InstanceTypeC2 InstanceType = "C2" + InstanceTypeS1 InstanceType = "S1" + InstanceTypeG1 InstanceType = "G1" + InstanceTypeF1 InstanceType = "F1" + + // InstanceTypeN4 网络增强型 BCC 实例: 通用网络增强型g3ne、计算网络增强型c3ne、内存网络增强型m3ne + InstanceTypeN4 InstanceType = "N4" + + // InstanceTypeN5 普通型Ⅳ BCC实例: 通用型g4、密集计算型ic4、计算型c4、内存型m4 + InstanceTypeN5 InstanceType = "N5" +) + +type StorageType string + +const ( + StorageTypeStd1 StorageType = "std1" + StorageTypeHP1 StorageType = "hp1" + StorageTypeCloudHP1 StorageType = "cloud_hp1" + StorageTypeLocal StorageType = "local" + StorageTypeSATA StorageType = "sata" + StorageTypeSSD StorageType = "ssd" + StorageTypeHDDThroughput StorageType = "HDD_Throughput" + StorageTypeHdd StorageType = "hdd" + StorageTypeLocalSSD StorageType = "local-ssd" + StorageTypeLocalHDD StorageType = "local-hdd" + StorageTypeLocalNVME StorageType = "local-nvme" + StorageTypeEnhancedPl1 StorageType = "enhanced_ssd_pl1" + StorageTypeEnhancedPl2 StorageType = "enhanced_ssd_pl2" +) + +type StorageTypeV3 string + +const ( + StorageTypeV3CloudSATA StorageTypeV3 = "Cloud_Sata" + StorageTypeV3CloudHDDGeneral StorageTypeV3 = "Cloud_HDD_General" + StorageTypeV3CloudHDDThroughput StorageTypeV3 = "Cloud_HDD_Throughput" + StorageTypeV3CloudPremium StorageTypeV3 = "Cloud_Premium" + StorageTypeV3CloudSSDGeneral StorageTypeV3 = "Cloud_SSD_General" + StorageTypeV3CloudSSDEnhanced StorageTypeV3 = "Cloud_SSD_Enhanced" + StorageTypeV3LocalHDD StorageTypeV3 = "Local_HDD" + StorageTypeV3LocalSSD StorageTypeV3 = "Local_SSD" + StorageTypeV3LocalNVME StorageTypeV3 = "Local_NVME" + StorageTypeV3LocalPVHDD StorageTypeV3 = "Local_PV_HDD" + StorageTypeV3LocalPVSSD StorageTypeV3 = "Local_PV_SSD" + StorageTypeV3LocalPVNVME StorageTypeV3 = "Local_PV_NVME" + StorageTypeV3EnhancedPl2 StorageTypeV3 = "enhanced_ssd_pl2" +) + +type PaymentTimingType string + +const ( + PaymentTimingPrePaid PaymentTimingType = "Prepaid" + PaymentTimingPostPaid PaymentTimingType = "Postpaid" + + // v3 + PaymentTimingSpotPaid PaymentTimingType = "Spotpaid" + PaymentTimingBidding PaymentTimingType = "bidding" +) + +// Instance define instance model +type InstanceModel struct { + InstanceId string `json:"id"` + SerialNumber string `json:"serialNumber"` + InstanceName string `json:"name"` + Hostname string `json:"hostname"` + InstanceType InstanceType `json:"instanceType"` + Spec string `json:"spec"` + Description string `json:"desc"` + Status InstanceStatus `json:"status"` + PaymentTiming string `json:"paymentTiming"` + CreationTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + ReleaseTime string `json:"releaseTime"` + PublicIP string `json:"publicIp"` + InternalIP string `json:"internalIp"` + CpuCount int `json:"cpuCount"` + IsomerismCard string `json:"isomerismCard"` + NpuVideoMemory string `json:"npuVideoMemory"` + GpuCard string `json:"gpuCard"` + FpgaCard string `json:"fpgaCard"` + CardCount string `json:"cardCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + LocalDiskSizeInGB int `json:"localDiskSizeInGB"` + ImageId string `json:"imageId"` + NetworkCapacityInMbps int `json:"networkCapacityInMbps"` + PlacementPolicy string `json:"placementPolicy"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + VpcId string `json:"vpcId"` + AutoRenew bool `json:"autoRenew"` + KeypairId string `json:"keypairId"` + KeypairName string `json:"keypairName"` + DedicatedHostId string `json:"dedicatedHostId"` + Tags []model.TagModel `json:"tags"` + Ipv6 string `json:"ipv6"` + EniQuota int `json:"eniQuota"` + EriQuota int `json:"eriQuota"` + RdmaTypeApi string `json:"rdmaTypeApi"` + SwitchId string `json:"switchId"` + HostId string `json:"hostId"` + DeploysetId string `json:"deploysetId"` + RackId string `json:"rackId"` + NicInfo NicInfo `json:"nicInfo"` + EniNum string `json:"eniNum"` + DeploySetList []DeploySetSimpleModel `json:"deploysetList"` + DeletionProtection int `json:"deletionProtection"` + NetEthQueueCount string `json:"netEthQueueCount"` +} + +type DeploySetSimpleModel struct { + Strategy string `json:"strategy"` + Name string `json:"name"` + Desc string `json:"desc"` + DeploySetId string `json:"deploysetId"` + Concurrency int `json:"concurrency"` +} + +type GetAllStocksResult struct { + BccStocks []BccStock `json:"bccStocks"` + BbcStocks []BbcStock `json:"bbcStocks"` +} + +type BccStock struct { + Spec string `json:"spec"` + SpecId string `json:"specId"` + InventoryQuantity int `json:"inventoryQuantity"` + RootOnLocal bool `json:"rootOnLocal"` + UpdatedTime string `json:"updatedTime"` + CollectionTime string `json:"collectionTime"` + ZoneName string `json:"logicalZone"` +} + +type BbcStock struct { + FlavorId string `json:"flavorId"` + InventoryQuantity int `json:"inventoryQuantity"` + UpdatedTime string `json:"updatedTime"` + CollectionTime string `json:"collectionTime"` + ZoneName string `json:"logicalZone"` +} + +type NicInfo struct { + Status string `json:"status"` + MacAddress string `json:"macAddress"` + DeviceId string `json:"deviceId"` + VpcId string `json:"vpcId"` + EniId string `json:"eniId"` + Name string `json:"name"` + Type string `json:"type"` + CreatedTime string `json:"createdTime"` + SubnetType string `json:"subnetType"` + SubnetId string `json:"subnetId"` + EniNum int `json:"eniNum"` + Az string `json:"az"` + EniUuid string `json:"eniUuid"` + Description string `json:"description"` + Ips []IpModel `json:"ips"` + SecurityGroups []string `json:"securityGroups"` +} + +type IpModel struct { + Eip string `json:"eip"` + EipStatus string `json:"eipStatus"` + EipSize string `json:"eipSize"` + EipId string `json:"eipId"` + Primary string `json:"primary"` + PrivateIp string `json:"privateIp"` + EipAllocationId string `json:"eipAllocationId"` + EipType string `json:"eipType"` + EipGroupId string `json:"eipGroupId"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type EphemeralDisk struct { + StorageType StorageType `json:"storageType"` + SizeInGB int `json:"sizeInGB"` + FreeSizeInGB int `json:"freeSizeInGB"` +} + +type EphemeralDiskV3 struct { + StorageType StorageTypeV3 `json:"storageType"` + SizeInGB int `json:"sizeInGB"` + FreeSizeInGB int `json:"freeSizeInGB"` +} + +type CreateCdsModel struct { + CdsSizeInGB int `json:"cdsSizeInGB"` + StorageType StorageType `json:"storageType"` + SnapShotId string `json:"snapshotId,omitempty"` +} + +type CreateCdsModelV3 struct { + CdsSizeInGB int `json:"cdsSizeInGB"` + StorageType StorageTypeV3 `json:"storageType"` + SnapShotId string `json:"snapshotId,omitempty"` +} + +type DiskInfo struct { + StorageType StorageType `json:"storageType"` + MinDiskSize int `json:"minDiskSize"` + MaxDiskSize int `json:"maxDiskSize"` +} + +type DiskZoneResource struct { + ZoneName string `json:"zoneName"` + DiskInfos []DiskInfo `json:"diskInfos"` +} + +type CreateInstanceArgs struct { + ImageId string `json:"imageId"` + Billing Billing `json:"billing"` + InstanceType InstanceType `json:"instanceType,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + LocalDiskSizeInGB int `json:"localDiskSizeInGB,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + EipName string `json:"eipName,omitempty"` + DedicateHostId string `json:"dedicatedHostId,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + IsOpenHostnameDomain bool `json:"isOpenHostnameDomain,omitempty"` + AutoSeqSuffix bool `json:"autoSeqSuffix,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds,omitempty"` + GpuCard string `json:"gpuCard,omitempty"` + FpgaCard string `json:"fpgaCard,omitempty"` + KunlunCard string `json:"kunlunCard,omitempty"` + IsomerismCard string `json:"isomerismCard,omitempty"` + CardCount string `json:"cardCount,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` + CdsAutoRenew bool `json:"cdsAutoRenew"` + RelationTag bool `json:"relationTag,omitempty"` + IsOpenIpv6 bool `json:"isOpenIpv6,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + DeployId string `json:"deployId,omitempty"` + BidModel string `json:"bidModel,omitempty"` + BidPrice string `json:"bidPrice,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + AspId string `json:"aspId,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + UserData string `json:"userData,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + DeployIdList []string `json:"deployIdList"` + DetetionProtection int `json:"deletionProtection"` + FileSystems []FileSystemModel `json:"fileSystems,omitempty"` + IsOpenHostEye bool `json:"isOpenHostEye,omitempty"` + ResGroupId string `json:"resGroupId,omitempty"` +} + +type CreateInstanceArgsV2 struct { + ImageId string `json:"imageId"` + Billing Billing `json:"billing"` + InstanceType InstanceType `json:"instanceType,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + LocalDiskSizeInGB int `json:"localDiskSizeInGB,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + EipName string `json:"eipName,omitempty"` + DedicateHostId string `json:"dedicatedHostId,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + IsOpenHostnameDomain *bool `json:"isOpenHostnameDomain"` + AutoSeqSuffix *bool `json:"autoSeqSuffix"` + AdminPass string `json:"adminPass,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds,omitempty"` + GpuCard string `json:"gpuCard,omitempty"` + FpgaCard string `json:"fpgaCard,omitempty"` + KunlunCard string `json:"kunlunCard,omitempty"` + IsomerismCard string `json:"isomerismCard,omitempty"` + CardCount string `json:"cardCount,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` + CdsAutoRenew *bool `json:"cdsAutoRenew"` + RelationTag *bool `json:"relationTag"` + IsOpenIpv6 *bool `json:"isOpenIpv6"` + Tags []model.TagModel `json:"tags,omitempty"` + DeployId string `json:"deployId,omitempty"` + BidModel string `json:"bidModel,omitempty"` + BidPrice string `json:"bidPrice,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + AspId string `json:"aspId,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + UserData string `json:"userData,omitempty"` + InternalIps []string `json:"internalIps,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + DeployIdList []string `json:"deployIdList"` + DetetionProtection int `json:"deletionProtection"` + FileSystems []FileSystemModel `json:"fileSystems,omitempty"` + IsOpenHostEye *bool `json:"isOpenHostEye"` + ResGroupId string `json:"resGroupId,omitempty"` +} + +type FileSystemModel struct { + FsID string `json:"fsId"` + MountAds string `json:"mountAds"` + Path string `json:"path"` + Protocol string `json:"protocol"` +} + +type CreateInstanceStockArgs struct { + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + CardCount string `json:"cardCount"` + InstanceType InstanceType `json:"instanceType"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + GpuCard string `json:"gpuCard"` +} + +type ResizeInstanceStockArgs struct { + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + InstanceId string `json:"instanceId"` +} + +type GetStockWithDeploySetArgs struct { + Spec string `json:"spec"` + DeploySetIds []string `json:"deploySetIds"` +} + +type GetStockWithDeploySetResults struct { + BccStocks []BccStock `json:"bccStocks"` +} + +type GetStockWithSpecArgs struct { + Spec string `json:"spec"` + DeploySetIds []string `json:"deploySetIds"` +} + +type GetStockWithSpecResults struct { + BccStocks []BccStock `json:"bccStocks"` +} + +type InstanceStockResult struct { + FlaovrId string `json:"flavorId"` + Count int `json:"Count"` +} + +type GetBidInstancePriceArgs struct { + InstanceType InstanceType `json:"instanceType"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Name string `json:"name,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + AspId string `json:"aspId,omitempty"` + ImageId string `json:"imageId,omitempty"` + BidModel string `json:"bidModel,omitempty"` + BidPrice string `json:"bidPrice,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + RelationTag bool `json:"relationTag,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + ClientToken string `json:"-"` +} + +type CreateInstanceResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type CreateInstanceBySpecArgs struct { + ImageId string `json:"imageId"` + Spec string `json:"spec"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + EipName string `json:"eipName,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + PurchaseMinCount int `json:"purchaseMinCount,omitempty"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + IsOpenHostnameDomain bool `json:"isOpenHostnameDomain,omitempty"` + AutoSeqSuffix bool `json:"autoSeqSuffix,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + Billing Billing `json:"billing"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds,omitempty"` + RelationTag bool `json:"relationTag,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + KeypairId string `json:"keypairId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` + RaidId string `json:"raidId,omitempty"` + EnableNuma bool `json:"enableNuma,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + CdsAutoRenew bool `json:"cdsAutoRenew"` + AspId string `json:"aspId"` + InternalIps []string `json:"internalIps,omitempty"` + DeployId string `json:"deployId,omitempty"` + UserData string `json:"userData,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + DeployIdList []string `json:"deployIdList"` + DetetionProtection int `json:"deletionProtection"` + IsOpenIpv6 bool `json:"isOpenIpv6,omitempty"` + SpecId string `json:"specId,omitempty"` + IsOpenHostEye bool `json:"isOpenHostEye,omitempty"` + BidModel string `json:"bidModel,omitempty"` + BidPrice string `json:"bidPrice,omitempty"` + ResGroupId string `json:"resGroupId,omitempty"` +} + +type CreateInstanceBySpecArgsV2 struct { + ImageId string `json:"imageId"` + Spec string `json:"spec"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + EipName string `json:"eipName,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + PurchaseMinCount int `json:"purchaseMinCount,omitempty"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + IsOpenHostnameDomain *bool `json:"isOpenHostnameDomain"` + AutoSeqSuffix *bool `json:"autoSeqSuffix"` + AdminPass string `json:"adminPass,omitempty"` + Billing Billing `json:"billing"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds,omitempty"` + RelationTag *bool `json:"relationTag"` + Tags []model.TagModel `json:"tags,omitempty"` + KeypairId string `json:"keypairId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` + RaidId string `json:"raidId,omitempty"` + EnableNuma *bool `json:"enableNuma"` + DataPartitionType string `json:"dataPartitionType,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + CdsAutoRenew *bool `json:"cdsAutoRenew"` + AspId string `json:"aspId"` + InternalIps []string `json:"internalIps,omitempty"` + DeployId string `json:"deployId,omitempty"` + UserData string `json:"userData,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + DeployIdList []string `json:"deployIdList"` + DetetionProtection int `json:"deletionProtection"` + IsOpenIpv6 *bool `json:"isOpenIpv6"` + SpecId string `json:"specId,omitempty"` + IsOpenHostEye *bool `json:"isOpenHostEye"` + BidModel string `json:"bidModel,omitempty"` + BidPrice string `json:"bidPrice,omitempty"` + ResGroupId string `json:"resGroupId,omitempty"` + EnableHt *bool `json:"enableHt"` +} + +const ( + LabelOperatorEqual LabelOperator = "equal" + LabelOperatorNotEqual LabelOperator = "not_equal" + LabelOperatorExist LabelOperator = "exist" + LabelOperatorNotExist LabelOperator = "not_exist" +) + +type LabelOperator string + +type LabelConstraint struct { + Key string `json:"labelKey,omitempty"` + Value string `json:"labelValue,omitempty"` + Operator LabelOperator `json:"operatorName,omitempty"` +} + +// --- 创建 BCC 的新接口的参数和返回值 + +type CreateSpecialInstanceBySpecArgs struct { + ImageId string `json:"imageId"` + Spec string `json:"spec"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType StorageType `json:"rootDiskStorageType,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []CreateCdsModel `json:"createCdsList,omitempty"` + NetWorkCapacityInMbps int `json:"networkCapacityInMbps,omitempty"` + InternetChargeType string `json:"internetChargeType,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Name string `json:"name,omitempty"` + Hostname string `json:"hostname,omitempty"` + IsOpenHostnameDomain bool `json:"isOpenHostnameDomain,omitempty"` + UserData string `json:"userData,omitempty"` + AutoSeqSuffix bool `json:"autoSeqSuffix,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + Billing Billing `json:"billing"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds,omitempty"` + RelationTag bool `json:"relationTag,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + KeypairId string `json:"keypairId"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` + CdsAutoRenew bool `json:"cdsAutoRenew"` + AspId string `json:"aspId"` + InternalIps []string `json:"internalIps,omitempty"` + DeployId string `json:"deployId,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + DeployIdList []string `json:"deployIdList"` + DetetionProtection int `json:"deletionProtection"` + + // CreateInstanceBySpecArgs 的基础上增加的参数 + LabelConstraints []LabelConstraint `json:"labelConstraints,omitempty"` + ResGroupId string `json:"resGroupId,omitempty"` +} + +type CreateSpecialInstanceBySpecResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type CreateInstanceV3Args struct { + InstanceSpec string `json:"instanceSpec,omitempty"` + SystemVolume SystemVolume `json:"systemVolume,omitempty"` + DataVolumes []DataVolume `json:"dataVolumes,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + HostName string `json:"hostName,omitempty"` + AutoSeqSuffix bool `json:"autoSeqSuffix,omitempty"` + HostNameDomain bool `json:"hostNameDomain,omitempty"` + Password string `json:"password,omitempty"` + Billing Billing `json:"billing"` + ZoneName string `json:"zoneName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + AssociatedResourceTag bool `json:"associatedResourceTag,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + CdsAutoRenew bool `json:"cdsAutoRenew,omitempty"` + AutoSnapshotPolicyId string `json:"autoSnapshotPolicyId,omitempty"` + PrivateIpAddresses []string `json:"privateIpAddresses,omitempty"` + DeploymentSetId string `json:"deploymentSetId,omitempty"` + DeployIdList []string `json:"deployIdList"` + ImageId string `json:"imageId,omitempty"` + UserData string `json:"userData,omitempty"` + InstanceMarketOptions InstanceMarketOptions `json:"instanceMarketOptions,omitempty"` + Ipv6 bool `json:"ipv6,omitempty"` + DedicatedHostId string `json:"dedicatedHostId,omitempty"` + InternetAccessible InternetAccessible `json:"internetAccessible,omitempty"` + ClientToken string `json:"-"` + RequestToken string `json:"requestToken"` + ResGroupId string `json:"resGroupId,omitempty"` +} + +type CreateInstanceV3Result struct { + InstanceIds []string `json:"instanceIds"` +} + +type SystemVolume struct { + StorageType StorageTypeV3 `json:"storageType,omitempty"` + VolumeSize int `json:"volumeSize,omitempty"` +} + +type DataVolume struct { + StorageType StorageTypeV3 `json:"storageType,omitempty"` + VolumeSize int `json:"volumeSize,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` + EncryptKey string `json:"encryptKey,omitempty"` +} + +type InstanceMarketOptions struct { + SpotOption string `json:"spotOption,omitempty"` + SpotPrice string `json:"spotPrice,omitempty"` +} + +type InternetAccessible struct { + InternetMaxBandwidthOut int `json:"internetMaxBandwidthOut,omitempty"` + InternetChargeType InternetChargeType `json:"internetChargeType,omitempty"` +} + +type InternetChargeType string + +const ( + BandwidthPrepaid InternetChargeType = "BANDWIDTH_PREPAID" + TrafficPostpaidByHour InternetChargeType = "TRAFFIC_POSTPAID_BY_HOUR" + BandwidthPostpaidByHour InternetChargeType = "BANDWIDTH_POSTPAID_BY_HOUR" +) + +type CreateInstanceBySpecResult struct { + InstanceIds []string `json:"instanceIds"` + OrderId string `json:"orderId,omitempty"` +} + +type ListInstanceArgs struct { + Marker string + MaxKeys int + InternalIp string + DedicatedHostId string + ZoneName string + KeypairId string + AutoRenew bool + InstanceIds string + InstanceNames string + CdsIds string + DeploySetIds string + SecurityGroupIds string + PaymentTiming string + Status string + Tags string + VpcId string + PrivateIps string +} + +type ListInstanceResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []InstanceModel `json:"instances"` +} + +type ListRecycleInstanceArgs struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + InstanceId string `json:"instanceId,omitempty"` + Name string `json:"name,omitempty"` + PaymentTiming string `json:"paymentTiming,omitempty"` + RecycleBegin string `json:"recycleBegin,omitempty"` + RecycleEnd string `json:"recycleEnd,omitempty"` +} + +type ListServerRequestV3Args struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + InstanceId string `json:"instanceId,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + PrivateIpAddress string `json:"privateIpAddress,omitempty"` + PublicIpAddress string `json:"publicIpAddress,omitempty"` + VpcName string `json:"vpcName,omitempty"` + SubnetName string `json:"subnetName,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + DedicatedHostId string `json:"dedicatedHostId,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + AutoRenew bool `json:"autoRenew,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + KeypairName string `json:"keypairName,omitempty"` + DeploymentSetId string `json:"deploymentSetId,omitempty"` + DeploymentSetName string `json:"deploymentSetName,omitempty"` + ResGroupId string `json:"resGroupId,omitempty"` + Tag model.TagModel `json:"tag,omitempty"` +} + +type LogicMarkerResultResponseV3 struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []InstanceModelV3 `json:"instances"` +} + +type ListRecycleInstanceResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []RecycleInstanceModel `json:"instances"` +} + +type InstanceModelV3 struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + HostId string `json:"hostId"` + HostName string `json:"hostName"` + InstanceSpec string `json:"instanceSpec"` + Status InstanceStatus `json:"status"` + Description string `json:"description"` + PaymentTiming string `json:"paymentTiming"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + ReleaseTime string `json:"releaseTime"` + PrivateIpAddress string `json:"privateIpAddress"` + PublicIpAddress string `json:"publicIpAddress"` + Cpu int `json:"cpu"` + Memory int `json:"memory"` + GpuCard string `json:"gpuCard"` + FpgaCard string `json:"fpgaCard"` + CardCount int `json:"cardCount"` + DataVolumes []DataVolumeV3 `json:"dataVolumes"` + ImageId string `json:"imageId"` + NetworkCapacityInMbps InternetAccessible `json:"networkCapacityInMbps"` + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` + VpcId string `json:"vpcId"` + AutoRenew bool `json:"autoRenew"` + KeypairId string `json:"keypairId"` + KeypairName string `json:"keypairName"` + HypervisorDedicatedId string `json:"hypervisorDedicatedId"` + Ipv6 string `json:"ipv6"` + Tags []model.TagModel `json:"tags"` + DeployId []string `json:"deployId"` + SerialNumber string `json:"serialNumber"` + SwitchId string `json:"switchId"` + RackId string `json:"rackId"` + NicInfo NicInfoV3 `json:"nicInfo"` + OsName string `json:"osName"` + OsType string `json:"osType"` +} + +type NicInfoV3 struct { + MacAddress string `json:"macAddress"` + EniId string `json:"eniId"` + Type string `json:"type"` + Ips []IpModelV3 `json:"ips"` + SecurityGroups []string `json:"securityGroups"` +} + +type IpModelV3 struct { + Primary bool `json:"primary"` + PrivateIp string `json:"privateIp"` +} + +type DataVolumeV3 struct { + VolumeId string `json:"volumeId"` + VolumeType string `json:"volumeType"` + VolumeSizeInGb int `json:"volumeSizeInGb"` + StorageType string `json:"storageType"` + SnapshotId string `json:"snapshotId"` + EncryptKey string `json:"encryptKey"` +} + +type RecycleInstanceModel struct { + InstanceId string `json:"id"` + SerialNumber string `json:"serialNumber"` + InstanceName string `json:"name"` + RecycleTime string `json:"recycleTime"` + DeleteTime string `json:"deleteTime"` + PaymentTiming string `json:"paymentTiming"` + ServiceName string `json:"serviceName"` + ServiceType string `json:"serviceType"` + ConfigItems []string `json:"configItems"` + ConfigItem RecycleInstanceModelConfigItem `json:"configItem"` +} + +type RecycleInstanceModelConfigItem struct { + Cpu int `json:"cpu"` + Memory int `json:"memory"` + Type string `json:"type"` + SpecId string `json:"specId"` + Spec string `json:"spec"` + ZoneName string `json:"zoneName"` +} + +type ModifyInstanceHostnameArgs struct { + Hostname string `json:"hostname"` + IsOpenHostnameDomain bool `json:"isOpenHostnameDomain"` + Reboot bool `json:"reboot"` +} + +type GetInstanceDetailResult struct { + Instance InstanceModel `json:"instance"` +} + +type AutoReleaseArgs struct { + ReleaseTime string `json:"releaseTime"` +} + +type ResizeInstanceArgs struct { + CpuCount int `json:"cpuCount"` + GpuCardCount int `json:"gpuCardCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + Spec string `json:"spec"` + LiveResize bool `json:"liveResize"` + ClientToken string `json:"-"` +} + +type RebuildInstanceArgs struct { + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + KeypairId string `json:"keypairId"` + IsOpenHostEye bool `json:"isOpenHostEye"` + IsPreserveData bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type RebuildInstanceArgsV2 struct { + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + KeypairId string `json:"keypairId"` + IsOpenHostEye *bool `json:"isOpenHostEye"` + IsPreserveData *bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type StopInstanceArgs struct { + ForceStop bool `json:"forceStop"` + StopWithNoCharge bool `json:"stopWithNoCharge"` +} + +type ChangeInstancePassArgs struct { + AdminPass string `json:"adminPass"` +} + +type ModifyInstanceAttributeArgs struct { + Name string `json:"name"` + NetEthQueueCount string `json:"netEthQueueCount"` +} + +type ModifyInstanceDescArgs struct { + Description string `json:"desc"` +} + +type BindSecurityGroupArgs struct { + SecurityGroupId string `json:"securityGroupId"` +} + +type GetInstanceVNCResult struct { + VNCUrl string `json:"vncUrl"` +} + +type InstancePurchaseReservedResult struct { + OrderId string `json:"orderId"` +} + +type GetBidInstancePriceResult struct { + Money string `json:"money"` + Count string `json:"count"` + PerMoney string `json:"perMoney"` +} + +type ListBidFlavorResult struct { + ZoneResources []ZoneResource `json:"zoneResources"` +} + +type ZoneResource struct { + ZoneName string `json:"zoneName"` + BccResources []BccResource `json:"bccResources"` +} + +type BccResource struct { + InstanceType InstanceType `json:"instanceType"` + Flavors []Flavor `json:"flavors"` +} + +type Flavor struct { + SpecId string `json:"specId"` + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + ProductType string `json:"productType"` + Spec string `json:"spec"` +} + +type PurchaseReservedArgs struct { + RelatedRenewFlag string `json:"relatedRenewFlag"` + Billing Billing `json:"billing"` + ClientToken string `json:"-"` +} + +const ( + RelatedRenewFlagCDS string = "CDS" + RelatedRenewFlagEIP string = "EIP" + RelatedRenewFlagMKT string = "MKT" + RelatedRenewFlagCDSEIP string = "CDS_EIP" + RelatedRenewFlagCDSMKT string = "CDS_MKT" + RelatedRenewFlagEIPMKT string = "EIP_MKT" + RelatedRenewFlagCDSEIPMKT string = "CDS_EIP_MKT" +) + +type DeleteInstanceWithRelateResourceArgs struct { + RelatedReleaseFlag bool `json:"relatedReleaseFlag"` + DeleteCdsSnapshotFlag bool `json:"deleteCdsSnapshotFlag"` + BccRecycleFlag bool `json:"bccRecycleFlag"` + DeleteRelatedEnisFlag bool `json:"deleteRelatedEnisFlag"` + DeleteImmediate bool `json:"deleteImmediate"` +} + +type InstanceChangeVpcArgs struct { + InstanceId string `json:"instanceId"` + SubnetId string `json:"subnetId"` + InternalIp string `json:"internalIp"` + Reboot bool `json:"reboot"` + SecurityGroupIds []string `json:"securityGroupIds"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` +} + +type InstanceChangeSubnetArgs struct { + InstanceId string `json:"instanceId"` + SubnetId string `json:"subnetId"` + InternalIp string `json:"internalIp"` + Reboot bool `json:"reboot"` +} + +type BatchAddIpArgs struct { + InstanceId string `json:"instanceId"` + PrivateIps []string `json:"privateIps"` + SecondaryPrivateIpAddressCount int `json:"secondaryPrivateIpAddressCount"` + AllocateMultiIpv6Addr bool `json:"allocateMultiIpv6Addr"` + ClientToken string `json:"-"` +} + +type BatchAddIpResponse struct { + PrivateIps []string `json:"privateIps"` +} + +type BatchDelIpArgs struct { + InstanceId string `json:"instanceId"` + PrivateIps []string `json:"privateIps"` + ClientToken string `json:"-"` +} + +type VolumeStatus string + +const ( + VolumeStatusAVAILABLE VolumeStatus = "Available" + VolumeStatusINUSE VolumeStatus = "InUse" + VolumeStatusSNAPSHOTPROCESSING VolumeStatus = "SnapshotProcessing" + VolumeStatusRECHARGING VolumeStatus = "Recharging" + VolumeStatusDETACHING VolumeStatus = "Detaching" + VolumeStatusDELETING VolumeStatus = "Deleting" + VolumeStatusEXPIRED VolumeStatus = "Expired" + VolumeStatusNOTAVAILABLE VolumeStatus = "NotAvailable" + VolumeStatusDELETED VolumeStatus = "Deleted" + VolumeStatusSCALING VolumeStatus = "Scaling" + VolumeStatusIMAGEPROCESSING VolumeStatus = "ImageProcessing" + VolumeStatusCREATING VolumeStatus = "Creating" + VolumeStatusATTACHING VolumeStatus = "Attaching" + VolumeStatusERROR VolumeStatus = "Error" +) + +type VolumeStatusV3 string + +const ( + VolumeStatusV3AVAILABLE VolumeStatusV3 = "Available" + VolumeStatusV3INUSE VolumeStatusV3 = "InUse" + VolumeStatusV3SNAPSHOTPROCESSING VolumeStatusV3 = "SnapshotProcessing" + VolumeStatusV3RECHARGING VolumeStatusV3 = "Recharging" + VolumeStatusV3DETACHING VolumeStatusV3 = "Detaching" + VolumeStatusV3DELETING VolumeStatusV3 = "Deleting" + VolumeStatusV3EXPIRED VolumeStatusV3 = "Expired" + VolumeStatusV3NOTAVAILABLE VolumeStatusV3 = "NotAvailable" + VolumeStatusV3DELETED VolumeStatusV3 = "Deleted" + VolumeStatusV3SCALING VolumeStatusV3 = "Scaling" + VolumeStatusV3IMAGEPROCESSING VolumeStatusV3 = "ImageProcessing" + VolumeStatusV3CREATING VolumeStatusV3 = "Creating" + VolumeStatusV3ATTACHING VolumeStatusV3 = "Attaching" + VolumeStatusV3ERROR VolumeStatusV3 = "Error" + VolumeStatusV3Recycled VolumeStatusV3 = "Recycled" +) + +type VolumeType string + +const ( + VolumeTypeSYSTEM VolumeType = "System" + VolumeTypeEPHEMERAL VolumeType = "Ephemeral" + VolumeTypeCDS VolumeType = "Cds" +) + +type VolumeTypeV3 string + +const ( + VolumeTypeV3SYSTEM VolumeTypeV3 = "SYSTEM" + VolumeTypeV3DATA VolumeTypeV3 = "DATA" +) + +type RenameCSDVolumeArgs struct { + Name string `json:"name"` +} + +type ModifyCSDVolumeArgs struct { + CdsName string `json:"cdsName,omitempty"` + Desc string `json:"desc,omitempty"` +} + +type DetachVolumeArgs struct { + InstanceId string `json:"instanceId"` +} + +type PurchaseReservedCSDVolumeArgs struct { + Billing *Billing `json:"billing"` + ClientToken string `json:"-"` +} + +type DeleteCDSVolumeArgs struct { + ManualSnapshot string `json:"manualSnapshot,omitempty"` + AutoSnapshot string `json:"autoSnapshot,omitempty"` + Recycle string `json:"recycle,omitempty"` +} + +type ModifyChargeTypeCSDVolumeArgs struct { + Billing *Billing `json:"billing"` +} + +type ListCDSVolumeResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Volumes []VolumeModel `json:"volumes"` +} + +type ListCDSVolumeResultV3 struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Volumes []VolumeModelV3 `json:"volumes"` +} + +type VolumeModel struct { + Type VolumeType `json:"type"` + StorageType StorageType `json:"storageType"` + Id string `json:"id"` + Name string `json:"name"` + DiskSizeInGB int `json:"diskSizeInGB"` + PaymentTiming string `json:"paymentTiming"` + ExpireTime string `json:"expireTime"` + Status VolumeStatus `json:"status"` + Desc string `json:"desc"` + Attachments []VolumeAttachmentModel `json:"attachments"` + ZoneName string `json:"zoneName"` + AutoSnapshotPolicy *AutoSnapshotPolicyModel `json:"autoSnapshotPolicy"` + CreateTime string `json:"createTime"` + IsSystemVolume bool `json:"isSystemVolume"` + RegionId string `json:"regionId"` + SourceSnapshotId string `json:"sourceSnapshotId"` + SnapshotNum string `json:"snapshotNum"` + Tags []model.TagModel `json:"tags"` + EnableAutoRenew bool `json:"enableAutoRenew"` + AutoRenewTime int `json:"autoRenewTime"` + Encrypted bool `json:"encrypted"` + ClusterId string `json:"clusterId"` + RoleName string `json:"roleName"` + CreatedFrom string `json:"createdFrom"` + ReleaseTime string `json:"releaseTime"` +} + +type VolumeModelV3 struct { + Id string `json:"volumeId"` + Name string `json:"volumeName"` + VolumeSize int `json:"volumeSizeInGB"` + VolumeStatus VolumeStatusV3 `json:"volumeStatus"` + VolumeType VolumeTypeV3 `json:"volumeType"` + StorageType StorageTypeV3 `json:"storageType"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + Desc string `json:"description"` + PaymentTiming string `json:"paymentTiming"` + EnableAutoRenew bool `json:"enableAutoRenew"` + AutoRenewTime int `json:"autoRenewTime"` + ZoneName string `json:"zoneName"` + SourceSnapshotId string `json:"sourceSnapshotId"` + Region string `json:"region"` + SnapshotCount int `json:"snapshotCount"` + AutoSnapshotPolicyId string `json:"autoSnapshotPolicyId"` + Encrypted bool `json:"encrypted"` + Tags []model.TagModel `json:"tags"` + Attachments []VolumeAttachmentsModel `json:"volumeAttachments"` +} + +type VolumeAttachmentsModel struct { + InstanceId string `json:"instanceId"` + Device string `json:"device"` + AttachTime string `json:"attachTime"` +} + +type VolumeAttachmentModel struct { + VolumeId string `json:"volumeId"` + InstanceId string `json:"instanceId"` + Device string `json:"device"` + Serial string `json:"serial"` +} + +type AttachVolumeResult struct { + VolumeAttachment *VolumeAttachmentModel `json:"volumeAttachment"` +} + +type CreateCDSVolumeArgs struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + CdsSizeInGB int `json:"cdsSizeInGB,omitempty"` + StorageType StorageType `json:"storageType,omitempty"` + Billing *Billing `json:"billing"` + EncryptKey string `json:"encryptKey"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` + InstanceId string `json:"instanceId"` + ClusterId string `json:"clusterId"` + Tags []model.TagModel `json:"tags"` + AutoSnapshotPolicy []AutoSnapshotPolicy `json:"autoSnapshotPolicy"` + ClientToken string `json:"-"` +} + +type AutoSnapshotPolicy struct { + AutoSnapshotPolicyId string `json:"autoSnapshotPolicyId"` +} + +type CreateCDSVolumeV3Args struct { + VolumeName string `json:"volumeName,omitempty"` + Description string `json:"description,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + VolumeSize int `json:"volumeSizeInGB,omitempty"` + StorageType StorageTypeV3 `json:"storageType,omitempty"` + Billing *Billing `json:"billing"` + EncryptKey string `json:"encryptKey"` + AutoSnapshotPolicyId string `json:"autoSnapshotPolicyId"` + InstanceId string `json:"instanceId"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` + ClientToken string `json:"-"` +} + +type CreateCDSVolumeResult struct { + VolumeIds []string `json:"volumeIds"` +} + +type GetVolumeDetailResult struct { + Volume *VolumeModel `json:"volume"` +} + +type GetVolumeDetailResultV3 struct { + Volume *VolumeModelV3 `json:"volume"` +} + +type GetAvailableDiskInfoResult struct { + CdsUsedCapacityGB string `json:"cdsUsedCapacityGB"` + CdsCreated string `json:"cdsCreated"` + CdsTotalCapacityGB string `json:"cdsTotalCapacityGB"` + CdsTotal string `json:"cdsTotal"` + CdsRatio string `json:"cdsRatio"` + DiskZoneResources []DiskZoneResource `json:"diskZoneResources"` +} + +type AttachVolumeArgs struct { + InstanceId string `json:"instanceId"` +} + +type ResizeCSDVolumeArgs struct { + NewCdsSizeInGB int `json:"newCdsSizeInGB,omitempty"` + NewVolumeType StorageType `json:"newVolumeType,omitempty"` + ClientToken string `json:"-"` +} + +type RollbackCSDVolumeArgs struct { + SnapshotId string `json:"snapshotId"` +} + +type ListCDSVolumeArgs struct { + MaxKeys int `json:"maxKeys"` + InstanceId string `json:"instanceId"` + ZoneName string `json:"zoneName"` + Marker string `json:"marker"` + ClusterId string `json:"clusterId"` + ChargeFilter string `json:"chargeFilter"` + UsageFilter string `json:"usageFilter"` + Name string `json:"name"` +} + +type AutoRenewCDSVolumeArgs struct { + VolumeId string `json:"volumeId"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` + ClientToken string `json:"-"` +} + +type CancelAutoRenewCDSVolumeArgs struct { + VolumeId string `json:"volumeId"` + ClientToken string `json:"-"` +} + +type AutoSnapshotPolicyModel struct { + CreatedTime string `json:"createdTime"` + Id string `json:"id"` + Status string `json:"status"` + RetentionDays int `json:"retentionDays"` + UpdatedTime string `json:"updatedTime"` + DeletedTime string `json:"deletedTime"` + LastExecuteTime string `json:"lastExecuteTime"` + VolumeCount int `json:"volumeCount"` + Name string `json:"name"` + TimePoints []int `json:"timePoints"` + RepeatWeekdays []int `json:"repeatWeekdays"` +} + +type SecurityGroupRuleModel struct { + SourceIp string `json:"sourceIp,omitempty"` + DestIp string `json:"destIp,omitempty"` + Protocol string `json:"protocol,omitempty"` + SourceGroupId string `json:"sourceGroupId,omitempty"` + Ethertype string `json:"ethertype,omitempty"` + PortRange string `json:"portRange,omitempty"` + DestGroupId string `json:"destGroupId,omitempty"` + SecurityGroupId string `json:"securityGroupId,omitempty"` + Remark string `json:"remark,omitempty"` + Direction string `json:"direction"` +} + +type SecurityGroupModel struct { + Id string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` + VpcId string `json:"vpcId"` + Rules []SecurityGroupRuleModel `json:"rules"` + Tags []model.TagModel `json:"tags"` +} + +type CreateSecurityGroupArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Desc string `json:"desc,omitempty"` + VpcId string `json:"vpcId,omitempty"` + Rules []SecurityGroupRuleModel `json:"rules"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +type ListSecurityGroupArgs struct { + Marker string + MaxKeys int + InstanceId string + VpcId string +} + +type CreateSecurityGroupResult struct { + SecurityGroupId string `json:"securityGroupId"` +} + +type ListSecurityGroupResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + SecurityGroups []SecurityGroupModel `json:"securityGroups"` +} + +type AuthorizeSecurityGroupArgs struct { + ClientToken string `json:"-"` + Rule *SecurityGroupRuleModel `json:"rule"` +} + +type RevokeSecurityGroupArgs struct { + Rule *SecurityGroupRuleModel `json:"rule"` +} + +type ImageType string + +const ( + ImageTypeIntegration ImageType = "Integration" + ImageTypeSystem ImageType = "System" + ImageTypeCustom ImageType = "Custom" + + // ImageTypeAll 所有镜像类型 + ImageTypeAll ImageType = "All" + + // ImageTypeSharing 共享镜像 + ImageTypeSharing ImageType = "Sharing" + + // ImageTypeGPUSystem gpu公有 + ImageTypeGPUSystem ImageType = "GpuBccSystem" + + // ImageTypeGPUCustom gpu 自定义 + ImageTypeGPUCustom ImageType = "GpuBccCustom" + + // ImageTypeBBCSystem BBC 公有 + ImageTypeBBCSystem ImageType = "BbcSystem" + + // ImageTypeBBCCustom BBC 自定义 + ImageTypeBBCCustom ImageType = "BbcCustom" +) + +type ImageStatus string + +const ( + ImageStatusCreating ImageStatus = "Creating" + ImageStatusCreateFailed ImageStatus = "CreateFailed" + ImageStatusAvailable ImageStatus = "Available" + ImageStatusNotAvailable ImageStatus = "NotAvailable" + ImageStatusError ImageStatus = "Error" +) + +type SharedUser struct { + AccountId string `json:"accountId,omitempty"` + Account string `json:"account,omitempty"` + UcAccount string `json:"ucAccount,omitempty"` +} + +type GetImageSharedUserResult struct { + Users []SharedUser `json:"users"` +} + +type GetImageOsResult struct { + OsInfo []OsModel `json:"osInfo"` +} + +type CreateImageResult struct { + ImageId string `json:"imageId"` +} + +type ListImageResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Images []ImageModel `json:"images"` +} + +type ImageModel struct { + OsVersion string `json:"osVersion"` + OsArch string `json:"osArch"` + OsLang string `json:"osLang"` + Status ImageStatus `json:"status"` + Desc string `json:"desc"` + Id string `json:"id"` + Name string `json:"name"` + OsName string `json:"osName"` + OsBuild string `json:"osBuild"` + CreateTime string `json:"createTime"` + Type ImageType `json:"type"` + OsType string `json:"osType"` + SpecialVersion string `json:"specialVersion"` + Package bool `json:"package"` + Encrypted bool `json:"encrypted"` + MinDiskGb int `json:"minDiskGb"` + DiskSize int `json:"diskSize"` + Snapshots []SnapshotModel `json:"snapshots"` +} + +type GetImageDetailResult struct { + Image *ImageModel `json:"image"` +} + +type RemoteCopyImageArgs struct { + Name string `json:"name,omitempty"` + DestRegion []string `json:"destRegion"` +} + +type RemoteCopyImageResult struct { + RemoteCopyImages []RemoteCopyImageModel `json:"result"` +} + +type RemoteCopyImageModel struct { + Region string `json:"region"` + ImageId string `json:"imageId"` + ErrMsg string `json:"errMsg"` + Code string `json:"code"` +} + +type CreateImageArgs struct { + InstanceId string `json:"instanceId,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` + ImageName string `json:"imageName"` + IsRelateCds bool `json:"relateCds"` + EncryptKey string `json:"encryptKey"` + ClientToken string `json:"-"` +} + +type ListImageArgs struct { + Marker string + MaxKeys int + ImageType string + ImageName string +} + +type OsModel struct { + OsVersion string `json:"osVersion"` + OsType string `json:"osType"` + InstanceId string `json:"instanceId"` + OsArch string `json:"osArch"` + OsName string `json:"osName"` + OsLang string `json:"osLang"` + SpecialVersion string `json:"specialVersion"` +} + +type GetImageOsArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type CreateSnapshotArgs struct { + ClientToken string `json:"-"` + VolumeId string `json:"volumeId"` + SnapshotName string `json:"snapshotName"` + Description string `json:"desc,omitempty"` + Tags []model.TagModel `json:"tags"` +} + +type CreateSnapshotResult struct { + SnapshotId string `json:"snapshotId"` +} + +type ListSnapshotArgs struct { + Marker string + MaxKeys int + VolumeId string +} + +type ListSnapshotChainArgs struct { + OrderBy string `json:"orderBy,omitempty"` + Order string `json:"order,omitempty"` + PageSize int `json:"pageSize,omitempty"` + PageNo int `json:"pageNo,omitempty"` + VolumeId string `json:"volumeId,omitempty"` +} + +type SnapshotStatus string + +const ( + SnapshotStatusCreating SnapshotStatus = "Creating" + SnapshotStatusCreatedFailed SnapshotStatus = "CreatedFailed" + SnapshotStatusAvailable SnapshotStatus = "Available" + SnapshotStatusNotAvailable SnapshotStatus = "NotAvailable" +) + +type SnapshotModel struct { + Id string `json:"id"` + Name string `json:"name"` + SizeInGB int `json:"sizeInGB"` + CreateTime string `json:"createTime"` + Status SnapshotStatus `json:"status"` + CreateMethod string `json:"createMethod"` + VolumeId string `json:"volumeId"` + Description string `json:"desc"` + ExpireTime string `json:"expireTime"` + Package bool `json:"package"` + TemplateId string `json:"templateId"` + InsnapId string `json:"insnapId"` + Encrypted bool `json:"encrypted"` +} + +type ListSnapshotResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Snapshots []SnapshotModel `json:"snapshots"` +} + +type ListSnapshotChainResult struct { + OrderBy string `json:"orderBy"` + TotalCount int `json:"totalCount"` + PageSize int `json:"pageSize"` + PageNo int `json:"pageNo"` + IsTruncated bool `json:"isTruncated"` + VolumeId string `json:"volumeId"` + Snapchains []SnapchainModel `json:"snapchains"` +} + +type SnapchainModel struct { + Status string `json:"status"` + ChainSize string `json:"chainSize"` + ChainId string `json:"chainId"` + InstanceId string `json:"instanceId"` + UserId string `json:"userId"` + VolumeId string `json:"volumeId"` + VolumeSize int `json:"volumeSize"` + ManualSnapCount int `json:"manualSnapCount"` + AutoSnapCount int `json:"autoSnapCount"` + CreateTime string `json:"createTime"` +} + +type GetSnapshotDetailResult struct { + Snapshot SnapshotModel `json:"snapshot"` +} + +type CreateASPArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + TimePoints []string `json:"timePoints"` + RepeatWeekdays []string `json:"repeatWeekdays"` + RetentionDays string `json:"retentionDays"` +} + +type CreateASPResult struct { + AspId string `json:"aspId"` +} + +type AttachASPArgs struct { + VolumeIds []string `json:"volumeIds"` +} + +type DetachASPArgs struct { + VolumeIds []string `json:"volumeIds"` +} + +type ListASPArgs struct { + Marker string + MaxKeys int + AspName string + VolumeName string +} + +type ListASPResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + AutoSnapshotPolicys []AutoSnapshotPolicyModel `json:"autoSnapshotPolicys"` +} + +type GetASPDetailResult struct { + AutoSnapshotPolicy AutoSnapshotPolicyModel `json:"autoSnapshotPolicy"` +} + +type UpdateASPArgs struct { + Name string `json:"name"` + TimePoints []string `json:"timePoints"` + RepeatWeekdays []string `json:"repeatWeekdays"` + RetentionDays string `json:"retentionDays"` + AspId string `json:"aspId"` +} + +type InstanceTypeModel struct { + Type string `json:"type"` + Name string `json:"name"` + CpuCount int `json:"cpuCount"` + MemorySizeInGB int `json:"memorySizeInGB"` + LocalDiskSizeInGB int `json:"localDiskSizeInGB"` +} + +type ListSpecResult struct { + InstanceTypes []InstanceTypeModel `json:"instanceTypes"` +} + +type ZoneModel struct { + ZoneName string `json:"zoneName"` +} + +type ListZoneResult struct { + Zones []ZoneModel `json:"zones"` +} + +type ListTypeZonesResult struct { + ZoneNames []string `json:"zoneNames"` +} + +type CreateDeploySetArgs struct { + Strategy string `json:"strategy"` + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` + Concurrency int `json:"concurrency,omitempty"` + ClientToken string `json:"-"` +} + +type ModifyDeploySetArgs struct { + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` + ClientToken string `json:"-"` +} + +type CreateDeploySetResp struct { + DeploySetIds []string `json:"deploySetIds"` +} + +type CreateDeploySetResult struct { + DeploySetId string `json:"deploySetIds"` +} + +type ListDeploySetsResult struct { + DeploySetList []DeploySetModel `json:"deploySets"` +} + +type DeploySetModel struct { + InstanceCount string `json:"instanceCount"` + Strategy string `json:"strategy"` + InstanceList []AzIntstanceStatis `json:"azIntstanceStatisList"` + Name string `json:"name"` + Desc string `json:"desc"` + DeploySetId string `json:"deploysetId"` + Concurrency int `json:"concurrency"` +} + +type DeploySetResult struct { + Strategy string `json:"strategy"` + Name string `json:"name"` + Desc string `json:"desc"` + DeploySetId string `json:"shortId"` + Concurrency int `json:"concurrency"` + InstanceList []AzIntstanceStatisDetail `json:"azIntstanceStatisList"` +} + +type UpdateInstanceDeployArgs struct { + ClientToken string `json:"-"` + InstanceId string `json:"instanceId,omitempty"` + DeploySetIds []string `json:"deploysetIdList,omitempty"` +} + +type DelInstanceDeployArgs struct { + ClientToken string `json:"-"` + InstanceIds []string `json:"instanceIdList,omitempty"` + DeploySetId string `json:"deployId,omitempty"` +} + +type AzIntstanceStatisDetail struct { + ZoneName string `json:"zoneName"` + Count int `json:"instanceCount"` + BccCount int `json:"bccInstanceCnt"` + BbcCount int `json:"bbcInstanceCnt"` + Total int `json:"instanceTotal"` + InstanceIds []string `json:"instanceIds"` + BccInstanceIds []string `json:"bccInstanceIds"` + BbcInstanceIds []string `json:"bbcInstanceIds"` +} + +type AzIntstanceStatis struct { + ZoneName string `json:"zoneName"` + Count int `json:"instanceCount"` + BbcCount int `json:"bbcInstanceCnt"` + BccCount int `json:"bccInstanceCnt"` + Total int `json:"instanceTotal"` +} + +type GetDeploySetResult struct { + DeploySetModel +} + +type RebuildBatchInstanceArgs struct { + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + KeypairId string `json:"keypairId"` + InstanceIds []string `json:"instanceIds"` + IsOpenHostEye bool `json:"isOpenHostEye"` + IsPreserveData bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type RebuildBatchInstanceArgsV2 struct { + ImageId string `json:"imageId"` + AdminPass string `json:"adminPass"` + KeypairId string `json:"keypairId"` + InstanceIds []string `json:"instanceIds"` + IsOpenHostEye *bool `json:"isOpenHostEye"` + IsPreserveData *bool `json:"isPreserveData"` + RaidId string `json:"raidId,omitempty"` + SysRootSize int `json:"sysRootSize,omitempty"` + RootPartitionType string `json:"rootPartitionType,omitempty"` + DataPartitionType string `json:"dataPartitionType,omitempty"` +} + +type ChangeToPrepaidRequest struct { + Duration int `json:"duration"` + RelationCds bool `json:"relationCds"` +} + +type ChangeToPrepaidResponse struct { + OrderId string `json:"orderId"` +} + +type BindTagsRequest struct { + ChangeTags []model.TagModel `json:"changeTags"` +} + +type UnBindTagsRequest struct { + ChangeTags []model.TagModel `json:"changeTags"` +} + +type CancelBidOrderRequest struct { + OrderId string `json:"orderId"` + ClientToken string `json:"-"` +} + +type CreateBidInstanceResult struct { + OrderId string `json:"orderId"` +} + +type ListFlavorSpecArgs struct { + ZoneName string `json:"zoneName,omitempty"` +} + +type ListFlavorSpecResult struct { + ZoneResources []ZoneResourceDetailSpec `json:"zoneResources"` +} + +type ZoneResourceDetailSpec struct { + ZoneName string `json:"zoneName"` + BccResources BccResources `json:"bccResources"` + EbcResources EbcResources `json:"ebcResources"` +} + +type BccResources struct { + FlavorGroups []FlavorGroup `json:"flavorGroups"` +} + +type FlavorGroup struct { + GroupId string `json:"groupId"` + Flavors []BccFlavor `json:"flavors"` +} + +type BccFlavor struct { + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + EphemeralDiskInGb int `json:"ephemeralDiskInGb"` + EphemeralDiskCount int `json:"ephemeralDiskCount"` + EphemeralDiskType string `json:"ephemeralDiskType"` + GpuCardType string `json:"gpuCardType"` + GpuCardCount int `json:"gpuCardCount"` + FpgaCardType string `json:"fpgaCardType"` + FpgaCardCount int `json:"fpgaCardCount"` + ProductType string `json:"productType"` + Spec string `json:"spec"` + SpecId string `json:"specId"` + CpuModel string `json:"cpuModel"` + CpuGHz string `json:"cpuGHz"` + NetworkBandwidth string `json:"networkBandwidth"` + NetworkPackage string `json:"networkPackage"` + NetEthQueueCount string `json:"netEthQueueCount"` + NetEthMaxQueueCount string `json:"netEthMaxQueueCount"` +} + +type EbcResources struct { + FlavorGroups []EbcFlavorGroup `json:"flavorGroups"` +} + +type EbcFlavorGroup struct { + GroupId string `json:"groupId"` + Flavors []EbcFlavor `json:"flavors"` +} + +type EbcFlavor struct { + CpuCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + EphemeralDiskInGb int `json:"ephemeralDiskInGb"` + EphemeralDiskCount string `json:"ephemeralDiskCount"` + EphemeralDiskType string `json:"ephemeralDiskType"` + GpuCardType string `json:"gpuCardType"` + GpuCardCount string `json:"gpuCardCount"` + FpgaCardType string `json:"fpgaCardType"` + FpgaCardCount string `json:"fpgaCardCount"` + ProductType string `json:"productType"` + Spec string `json:"spec"` + SpecId string `json:"specId"` + CpuModel string `json:"cpuModel"` + CpuGHz string `json:"cpuGHz"` + NetworkBandwidth string `json:"networkBandwidth"` + NetworkPackage string `json:"networkPackage"` +} + +type GetPriceBySpecArgs struct { + SpecId string `json:"specId"` + Spec string `json:"spec"` + PaymentTiming string `json:"paymentTiming"` + ZoneName string `json:"zoneName"` + PurchaseCount int `json:"purchaseCount,omitempty"` + PurchaseLength int `json:"purchaseLength"` +} + +type GetPriceBySpecResult struct { + Price []SpecIdPrices `json:"price"` +} + +type SpecIdPrices struct { + SpecId string `json:"specId"` + SpecPrices []SpecPrices `json:"specPrices"` +} + +type SpecPrices struct { + Spec string `json:"spec"` + Status string `json:"status"` + SpecPrice string `json:"specPrice"` +} + +type PrivateIP struct { + PublicIpAddress string `json:"publicIpAddress"` + Primary bool `json:"primary"` + PrivateIpAddress string `json:"privateIpAddress"` + Ipv6Address string `json:"ipv6Address"` +} + +type Eni struct { + EniId string `json:"eniId"` + Name string `json:"name"` + ZoneName string `json:"zoneName"` + Description string `json:"description"` + InstanceId string `json:"instanceId"` + MacAddress string `json:"macAddress"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Status string `json:"status"` + PrivateIpSet []PrivateIP `json:"privateIpSet"` +} + +type ListInstanceEniResult struct { + EniList []Eni `json:"enis"` +} + +type CreateKeypairArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` +} + +type ImportKeypairArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` + PublicKey string `json:"publicKey"` +} + +type KeypairModel struct { + KeypairId string `json:"keypairId"` + Name string `json:"name"` + Description string `json:"description"` + PublicKey string `json:"publicKey"` + RegionId string `json:"regionId"` + FingerPrint string `json:"fingerPrint"` + PrivateKey string `json:"privateKey"` + InstanceCount int `json:"instanceCount"` + CreatedTime string `json:"createdTime"` +} + +type KeypairResult struct { + Keypair KeypairModel `json:"keypair"` +} + +type AttackKeypairArgs struct { + KeypairId string `json:"keypairId"` + InstanceIds []string `json:"instanceIds"` +} + +type DetachKeypairArgs struct { + KeypairId string `json:"keypairId"` + InstanceIds []string `json:"instanceIds"` +} + +type DeleteKeypairArgs struct { + KeypairId string `json:"keypairId"` +} + +type ListKeypairArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + Name string `json:"name,omitempty"` +} + +type ListKeypairResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Keypairs []KeypairModel `json:"keypairs"` +} + +type RenameKeypairArgs struct { + Name string `json:"name"` + KeypairId string `json:"keypairId"` +} + +type KeypairUpdateDescArgs struct { + Description string `json:"description"` + KeypairId string `json:"keypairId"` +} + +type ListTypeZonesArgs struct { + InstanceType string `json:"instanceType"` + ProductType string `json:"productType"` + Spec string `json:"spec"` + SpecId string `json:"specId"` +} + +type BccCreateAutoRenewArgs struct { + InstanceId string `json:"instanceId"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` + RenewCds bool `json:"renewCds"` + RenewEip bool `json:"renewEip"` +} + +type BccDeleteAutoRenewArgs struct { + InstanceId string `json:"instanceId"` + RenewCds bool `json:"renewCds"` + RenewEip bool `json:"renewEip"` +} + +type DeleteInstanceIngorePaymentArgs struct { + InstanceId string `json:"instanceId"` + RelatedReleaseFlag bool `json:"relatedReleaseFlag"` + DeleteCdsSnapshotFlag bool `json:"deleteCdsSnapshotFlag"` + DeleteRelatedEnisFlag bool `json:"deleteRelatedEnisFlag"` + DeleteImmediate bool `json:"deleteImmediate"` +} + +type DeleteInstanceModel struct { + InstanceId string `json:"instanceId"` + Eip string `json:"eip"` + InsnapIds []string `json:"insnapIds"` + SnapshotIds []string `json:"snapshotIds"` + VolumeIds []string `json:"volumeIds"` +} + +type DeleteInstanceResult struct { + SuccessResources *DeleteInstanceModel `json:"successResources"` + FailResources *DeleteInstanceModel `json:"failResources"` +} + +type RecoveryInstanceArgs struct { + InstanceIds []RecoveryInstanceModel `json:"instanceIds"` +} + +type RecoveryInstanceModel struct { + InstanceId string `json:"instanceId"` +} + +type ListInstanceByInstanceIdArgs struct { + Marker string + MaxKeys int + InstanceIds []string `json:"instanceIds"` +} + +type ListInstancesResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []InstanceModel `json:"instances"` +} + +type VolumePrepayDeleteRequestArgs struct { + VolumeId string `json:"volumeId"` + RelatedReleaseFlag bool `json:"relatedReleaseFlag"` + DeleteImmediate bool `json:"deleteImmediate"` +} + +type VolumeDeleteResultResponse struct { + SuccessResources VolumeDeleteResultModel `json:"successResources"` + FailResources VolumeDeleteResultModel `json:"failResources"` +} + +type VolumeDeleteResultModel struct { + VolumeIds []string `json:"volumeIds"` +} + +type DeletionProtectionArgs struct { + DeletionProtection int `json:"deletionProtection"` +} + +type BatchDeleteInstanceWithRelateResourceArgs struct { + RelatedReleaseFlag bool `json:"relatedReleaseFlag"` + DeleteCdsSnapshotFlag bool `json:"deleteCdsSnapshotFlag"` + BccRecycleFlag bool `json:"bccRecycleFlag"` + DeleteRelatedEnisFlag bool `json:"deleteRelatedEnisFlag"` + InstanceIds []string `json:"instanceIds"` +} + +type BatchStartInstanceArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type BatchStopInstanceArgs struct { + ForceStop bool `json:"forceStop"` + StopWithNoCharge bool `json:"stopWithNoCharge"` + InstanceIds []string `json:"instanceIds"` +} + +type ListInstanceTypeArgs struct { + ZoneName string `json:"zoneName"` +} + +type ListInstanceTypeResults struct { + ZoneInstanceTypes []ZoneInstanceTypes `json:"zoneInstanceTypes"` +} + +type ZoneInstanceTypes struct { + ZoneName string `json:"zoneName"` + InstanceTypes []string `json:"instanceTypes"` +} + +type ListIdMappingArgs struct { + IdType string `json:"idType"` + ObjectType string `json:"objectType"` + InstanceIds []string `json:"instanceIds"` +} + +type ListIdMappingResults struct { + IdMapping []IdMapping `json:"mappings"` +} + +type IdMapping struct { + Uuid string `json:"uuid"` + Id string `json:"id"` +} + +type BatchResizeInstanceArgs struct { + Spec string `json:"spec"` + InstanceIdList []string `json:"instanceIdList"` +} + +type BatchResizeInstanceResults struct { + OrderUuidResults []string `json:"orderUuidResults"` +} + +// UpdateSecurityGroupRuleArgs defines the structure of input parameters for the UpdateSecurityGroupRule api +type UpdateSecurityGroupRuleArgs struct { + SgVersion int64 `json:"sgVersion,omitempty"` + SecurityGroupRuleId string `json:"securityGroupRuleId"` + Remark *string `json:"remark,omitempty"` + PortRange *string `json:"portRange,omitempty"` + SourceIp *string `json:"sourceIp,omitempty"` + SourceGroupId *string `json:"sourceGroupId,omitempty"` + DestIp *string `json:"destIp,omitempty"` + DestGroupId *string `json:"destGroupId,omitempty"` + Protocol *string `json:"protocol,omitempty"` +} + +// DeleteSecurityGroupRuleArgs defines the structure of input parameters for the DeleteSecurityGroupRule api +type DeleteSecurityGroupRuleArgs struct { + SgVersion int64 `json:"sgVersion,omitempty"` + SecurityGroupRuleId string `json:"securityGroupRuleId"` +} + +type GetInstanceDeleteProgressArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type Tag struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} + +type TagVolumeArgs struct { + ChangeTags []Tag `json:"changeTags"` + RelationTag bool `json:"relationTag"` +} + +type Volume struct { + VolumeId string `json:"volumeId"` + SizeInGB int `json:"sizeInGb"` +} + +type ListAvailableResizeSpecsArgs struct { + Spec string `json:"spec"` + SpecId string `json:"specId"` + Zone string `json:"zone"` + InstanceIdList []string `json:"instanceIdList"` +} + +type ListAvailableResizeSpecResults struct { + SpecList []string `json:"specList"` +} + +type BatchChangeInstanceToPrepayArgs struct { + Config []PrepayConfig `json:"config"` +} + +type PrepayConfig struct { + InstanceId string `json:"instanceId"` + Duration int `json:"duration"` + RelationCds bool `json:"relationCds"` + CdsList []string `json:"cdsList"` + AutoPay bool `json:"autoPay"` +} + +type BatchChangeInstanceToPostpayArgs struct { + Config []PostpayConfig `json:"config"` +} + +type PostpayConfig struct { + InstanceId string `json:"instanceId"` + RelationCds bool `json:"relationCds"` + CdsList []string `json:"cdsList"` +} + +type BatchChangeInstanceBillingMethodResult struct { + OrderId string `json:"orderId"` +} + +type Role struct { + RoleName string `json:"roleName"` +} + +type ListInstanceRolesResult struct { + Roles []Role `json:"roles"` +} + +type BindInstanceRoleResult struct { + FailInstances []FailInstances `json:"failInstances"` + InstanceRoleAssociations []InstanceRoleAssociations `json:"instanceRoleAssociations"` +} + +type FailInstances struct { + InstanceId string `json:"instanceId"` + FailMessage string `json:"failMessage"` +} + +type InstanceRoleAssociations struct { + InstanceID string `json:"instanceId"` +} + +type BindInstanceRoleArgs struct { + RoleName string `json:"roleName"` + Instances []Instances `json:"instances"` +} + +type Instances struct { + InstanceId string `json:"instanceId"` +} + +type UnBindInstanceRoleArgs struct { + RoleName string `json:"roleName"` + Instances []Instances `json:"instances"` +} + +type UnBindInstanceRoleResult struct { + FailInstances []FailInstances `json:"failInstances"` + InstanceRoleAssociations []InstanceRoleAssociations `json:"instanceRoleAssociations"` +} + +type DeleteIpv6Args struct { + InstanceId string `json:"instanceId"` + Reboot bool `json:"reboot"` +} + +type AddIpv6Args struct { + InstanceId string `json:"instanceId"` + Ipv6Address string `json:"ipv6Address"` + Reboot bool `json:"reboot"` +} + +type AddIpv6Result struct { + Ipv6Address string `json:"ipv6Address"` +} + +type RemoteCopySnapshotArgs struct { + ClientToken string `json:"-"` + DestRegionInfos []DestRegionInfo `json:"destRegionInfos"` +} + +type DestRegionInfo struct { + Name string `json:"name"` + DestRegion string `json:"destRegion"` +} + +type RemoteCopySnapshotResult struct { + Result []RemoteCopySnapshotResultItem `json:"result"` +} + +type RemoteCopySnapshotResultItem struct { + Region string `json:"region"` + SnapshotID string `json:"snapshotId"` +} + +type ImportCustomImageArgs struct { + OsName string `json:"osName"` + OsArch string `json:"osArch"` + OsType string `json:"osType"` + OsVersion string `json:"osVersion"` + Name string `json:"name"` + BosURL string `json:"bosUrl"` +} + +type ImportCustomImageResult struct { + Id string `json:"id"` +} + +type GetAvailableImagesBySpecArg struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + Spec string `json:"spec"` + OsName string `json:"osName"` +} + +type GetAvailableImagesBySpecResult struct { + IsTruncated bool `json:"isTruncated"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + Images ImageArg `json:"images"` +} + +type ImageArg []struct { + ImageID string `json:"imageId"` + ImageName string `json:"imageName"` + OsType string `json:"osType"` + OsVersion string `json:"osVersion"` + OsArch string `json:"osArch"` + OsName string `json:"osName"` + OsLang string `json:"osLang"` + MinSizeInGiB int `json:"minSizeInGiB"` +} diff --git a/bce-sdk-go/services/bcc/api/other.go b/bce-sdk-go/services/bcc/api/other.go new file mode 100644 index 0000000..da3498a --- /dev/null +++ b/bce-sdk-go/services/bcc/api/other.go @@ -0,0 +1,236 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// other.go - the other APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListSpec - get specification list information of the instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListSpecResult: result of the specifications +// - error: nil if success otherwise the specific error +func ListSpec(cli bce.Client) (*ListSpecResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSpecUri()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListSpecResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListZone - get the available zone list in the current region +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListZoneResult: result of the available zones +// - error: nil if success otherwise the specific error +func ListZone(cli bce.Client) (*ListZoneResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getZoneUri()) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListZoneResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListFlavorSpec - get the specified flavor list +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list the specified flavor +// +// RETURNS: +// - *ListFlavorSpecResult: result of the specified flavor list +// - error: nil if success otherwise the specific error +func ListFlavorSpec(cli bce.Client, args *ListFlavorSpecArgs) (*ListFlavorSpecResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getFlavorSpecUri()) + req.SetMethod(http.GET) + + if args != nil { + if len(args.ZoneName) != 0 { + req.SetParam("zoneName", args.ZoneName) + } + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListFlavorSpecResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetPriceBySpec - get the price information of specified instance. +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get the price information of specified instance. +// +// RETURNS: +// - *GetPriceBySpecResult: result of the specified instance's price information +// - error: nil if success otherwise the specific error +func GetPriceBySpec(cli bce.Client, args *GetPriceBySpecArgs) (*GetPriceBySpecResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getPriceBySpecUri()) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetPriceBySpecResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListTypeZones - get the available zone list in the current region +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListZoneResult: result of the available zones +// - error: nil if success otherwise the specific error +func ListTypeZones(cli bce.Client, args *ListTypeZonesArgs) (*ListTypeZonesResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getInstanceTypeZoneUri()) + req.SetMethod(http.GET) + if args != nil { + if len(args.InstanceType) != 0 { + req.SetParam("instanceType", args.InstanceType) + } + if len(args.ProductType) != 0 { + req.SetParam("productType", args.ProductType) + } + if len(args.SpecId) != 0 { + req.SetParam("specId", args.SpecId) + } + if len(args.Spec) != 0 { + req.SetParam("spec", args.Spec) + } + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListTypeZonesResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListInstanceEni - get the eni list of the bcc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the bcc instance id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ListInstanceEnis(cli bce.Client, instanceId string) (*ListInstanceEniResult, error) { + req := &bce.BceRequest{} + req.SetUri(getInstanceEniUri(instanceId)) + req.SetMethod(http.GET) + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListInstanceEniResult{} + print(jsonBody) + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bcc/api/securityGroup.go b/bce-sdk-go/services/bcc/api/securityGroup.go new file mode 100644 index 0000000..d142bba --- /dev/null +++ b/bce-sdk-go/services/bcc/api/securityGroup.go @@ -0,0 +1,270 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// securityGroup.go - the securityGroup APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateSecurityGroup - create a security group and related rules +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create security group +// - : +// +// RETURNS: +// - *CreateSecurityGroupResult: result of the security group id +// - error: nil if success otherwise the specific error +func CreateSecurityGroup(cli bce.Client, args *CreateSecurityGroupArgs) (*CreateSecurityGroupResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSecurityGroupUri()) + req.SetMethod(http.POST) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateSecurityGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListSecurityGroup - list all security groups with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: the arguments to list security groups +// +// RETURNS: +// - *ListSecurityGroupResult: result of the security group list +// - error: nil if success otherwise the specific error +func ListSecurityGroup(cli bce.Client, queryArgs *ListSecurityGroupArgs) (*ListSecurityGroupResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSecurityGroupUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.InstanceId) != 0 { + req.SetParam("instanceId", queryArgs.InstanceId) + } + if len(queryArgs.VpcId) != 0 { + req.SetParam("vpcId", queryArgs.VpcId) + } + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListSecurityGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// AuthorizeSecurityGroupRule - authorize a rule of security group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - securityGroupId: id of the security group +// - args: arguments to authorize security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func AuthorizeSecurityGroupRule(cli bce.Client, securityGroupId string, args *AuthorizeSecurityGroupArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSecurityGroupUriWithId(securityGroupId)) + req.SetMethod(http.PUT) + + if args.ClientToken != "" { + req.SetParam("clientToken", args.ClientToken) + } + req.SetParam("authorizeRule", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// RevokeSecurityGroupRule - revoke a rule of security group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - securityGroupId: id of the security group +// - args: arguments to revoke security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RevokeSecurityGroupRule(cli bce.Client, securityGroupId string, args *RevokeSecurityGroupArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSecurityGroupUriWithId(securityGroupId)) + req.SetMethod(http.PUT) + + req.SetParam("revokeRule", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteSecurityGroup - delete a security group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - securityGroupId: id of the security group to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteSecurityGroup(cli bce.Client, securityGroupId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSecurityGroupUriWithId(securityGroupId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteSecurityGroupRule - delete a security group rule +// +// PARAMS: +// - securityGroupRuleId: the id of the specific security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteSecurityGroupRule(cli bce.Client, args *DeleteSecurityGroupRuleArgs) error { + builder := bce.NewRequestBuilder(cli). + WithURL(getSecurityGroupRuleUri() + "/" + args.SecurityGroupRuleId). + WithMethod(http.DELETE) + if args.SgVersion != 0 { + builder.WithQueryParamFilter("sgVersion", strconv.FormatInt(args.SgVersion, 10)) + } + return builder.Do() +} + +// UpdateSecurityGroupRule - update security group rule with the specific parameters +// +// PARAMS: +// - args: the arguments to update the specific security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateSecurityGroupRule(cli bce.Client, args *UpdateSecurityGroupRuleArgs) error { + if args == nil { + return fmt.Errorf("the UpdateSecurityGroupRuleArgs cannot be nil") + } + builder := bce.NewRequestBuilder(cli). + WithURL(getSecurityGroupRuleUri() + "/update"). + WithMethod(http.PUT) + if args.SgVersion != 0 { + builder.WithQueryParamFilter("sgVersion", strconv.FormatInt(args.SgVersion, 10)) + } + return builder.WithBody(args).Do() +} diff --git a/bce-sdk-go/services/bcc/api/snapshot.go b/bce-sdk-go/services/bcc/api/snapshot.go new file mode 100644 index 0000000..e57ce08 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/snapshot.go @@ -0,0 +1,318 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// snapshot.go - the snapshot APIs definition supported by the BCC service + +// Package api defines all APIs supported by the BCC service of BCE. +package api + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateSnapshot - create a snapshot for specified volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create snapshot +// +// RETURNS: +// - *CreateSnapshotResult: result of the snapshot id newly created +// - error: nil if success otherwise the specific error +func CreateSnapshot(cli bce.Client, args *CreateSnapshotArgs) (*CreateSnapshotResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSnapshotUri()) + req.SetMethod(http.POST) + + if len(args.ClientToken) != 0 { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateSnapshotResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListSnapshot - list all snapshots with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: arguments to list snapshots +// +// RETURNS: +// - *ListSnapshotResult: result of the snapshot list +// - error: nil if success otherwise the specific error +func ListSnapshot(cli bce.Client, queryArgs *ListSnapshotArgs) (*ListSnapshotResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSnapshotUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListSnapshotResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListSnapshotChain - list all snapshot chains with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryArgs: arguments to list snapshot chains +// +// RETURNS: +// - *ListSnapshotChainResult: result of the snapshot chain list +// - error: nil if success otherwise the specific error +func ListSnapshotChain(cli bce.Client, queryArgs *ListSnapshotChainArgs) (*ListSnapshotChainResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSnapshotChainUri()) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.OrderBy) != 0 { + req.SetParam("orderBy", queryArgs.OrderBy) + } + if len(queryArgs.Order) != 0 { + req.SetParam("order", queryArgs.Order) + } + if queryArgs.PageSize != 0 { + req.SetParam("pageSize", strconv.Itoa(queryArgs.PageSize)) + } + if queryArgs.PageNo != 0 { + req.SetParam("pageNo", strconv.Itoa(queryArgs.PageNo)) + } + if len(queryArgs.VolumeId) != 0 { + req.SetParam("volumeId", queryArgs.VolumeId) + } + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListSnapshotChainResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetSnapshotDetail - get details of the specified snapshot +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - snapshotId: id of the snapshot +// +// RETURNS: +// - *GetSnapshotDetailResult: result of snapshot details +// - error: nil if success otherwise the specific error +func GetSnapshotDetail(cli bce.Client, snapshotId string) (*GetSnapshotDetailResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSnapshotUriWithId(snapshotId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &GetSnapshotDetailResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteSnapshot - delete a snapshot +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - snapshotId: id of the snapshot to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteSnapshot(cli bce.Client, snapshotId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getSnapshotUriWithId(snapshotId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + return nil +} + +func TagSnapshotChain(cli bce.Client, chainId string, args *TagVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getTagSnapshotChainUri(chainId)) + req.SetMethod(http.PUT) + req.SetParam("bind", "") + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func UntagSnapshotChain(cli bce.Client, chainId string, args *TagVolumeArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getUntagSnapshotChainUri(chainId)) + req.SetMethod(http.PUT) + req.SetParam("unbind", "") + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func CreateRemoteCopySnapshot(cli bce.Client, snapshotId string, args *RemoteCopySnapshotArgs) ( + *RemoteCopySnapshotResult, error) { + + // Build the request + req := &bce.BceRequest{} + req.SetUri(getRemoteCopySnapshotUri(snapshotId)) + req.SetMethod(http.PUT) + + if len(args.ClientToken) != 0 { + req.SetParam("clientToken", args.ClientToken) + } + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &RemoteCopySnapshotResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} diff --git a/bce-sdk-go/services/bcc/api/util.go b/bce-sdk-go/services/bcc/api/util.go new file mode 100644 index 0000000..a36a8c0 --- /dev/null +++ b/bce-sdk-go/services/bcc/api/util.go @@ -0,0 +1,437 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BCC service +package api + +import ( + "encoding/hex" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +const ( + URI_PREFIXV3 = bce.URI_PREFIX + "v3" + URI_PREFIXV2 = bce.URI_PREFIX + "v2" + URI_PREFIXV1 = bce.URI_PREFIX + "v1" + + REQUEST_ASP_URI = "/asp" + REQUEST_BATCHADDIP_URI = "/batchAddIp" + REQUEST_BATCHDELIP_URI = "/batchDelIp" + REQUEST_CREATE_URI = "/create" + REQUEST_UPDATE_URI = "/updateRelation" + REQUEST_DEL_URI = "/delRelation" + REQUEST_DEPLOYSET_URI = "/deployset" + REQUEST_IMAGE_URI = "/image" + REQUEST_IMAGE_SHAREDUSER_URI = "/sharedUsers" + REQUEST_IMAGE_OS_URI = "/os" + REQUEST_INSTANCE_URI = "/instance" + REQUEST_INSTANCE_LABEL_URI = "/instanceByLabel" + REQUEST_LIST_URI = "/list" + REQUEST_SECURITYGROUP_URI = "/securityGroup" + REQUEST_SNAPSHOT_URI = "/snapshot" + REQUEST_CHAIN_URI = "/chain" + REQUEST_SPEC_URI = "/instance/spec" + REQUEST_SUBNET_URI = "/subnet" + REQUEST_VPC_URI = "/vpc" + REQUEST_VNC_SUFFIX = "/vnc" + REQUEST_VOLUME_URI = "/volume" + REQUEST_ZONE_URI = "/zone" + REQUEST_RECYCLE = "/recycle" + REQUEST_DELETEPREPAY = "/volume/deletePrepay" + // + REQUEST_FLAVOR_SPEC_URI = "/instance/flavorSpec" + REQUEST_PRICE_URI = "/price" + REQUEST_AUTO_RENEW_URI = "/autoRenew" + REQUEST_CANCEL_AUTO_RENEW_URI = "/cancelAutoRenew" + REQUEST_BID_PRICE_URI = "/bidPrice" + REQUEST_BID_FLAVOR_URI = "/bidFlavor" + // + REQUEST_INSTANCE_PRICE_URI = "/instance/price" + REQUEST_INSTANCE_BY_SPEC_URI = "/instanceBySpec" + REQUEST_VOLUME_DISK_URI = "/volume/disk" + REQUEST_TYPE_ZONE_URI = "/instance/flavorZones" + REQUEST_ENI_URI = "/eni" + REQUEST_KEYPAIR_URI = "/keypair" + REQUEST_REBUILD_URI = "/rebuild" + REQUEST_TAG_URI = "/tag" + REQUEST_NOCHARGE_URI = "/noCharge" + REQUEST_BID_URI = "/bid" + REQUEST_RECOVERY_URI = "/recovery" + REQUEST_CANCEL_BIDORDER_URI = "/cancelBidOrder" + REQUEST_BATCH_CREATE_AUTORENEW_RULES_URI = "/batchCreateAutoRenewRules" + REQUEST_BATCH_Delete_AUTORENEW_RULES_URI = "/batchDeleteAutoRenewRules" + REQUEST_GET_ALL_STOCKS = "/getAllStocks" + REQUEST_GET_STOCK_WITH_DEPLOYSET = "/getStockWithDeploySet" + REQUEST_GET_STOCK_WITH_SPEC = "/getStockWithSpec" + REQUEST_DELETION_PROTECTION = "/deletionProtection" +) + +func getInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI +} + +func getInstanceByLabelUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_LABEL_URI +} + +func getInstanceUriV3() string { + return URI_PREFIXV3 + REQUEST_INSTANCE_URI +} + +func getRecycleInstanceListUri() string { + return URI_PREFIXV2 + REQUEST_RECYCLE + REQUEST_INSTANCE_URI +} + +func getServersByMarkerV3Uri() string { + return URI_PREFIXV3 + REQUEST_INSTANCE_URI + REQUEST_LIST_URI +} + +func getInstanceBySpecUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_BY_SPEC_URI +} + +func getInstanceUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/" + id +} + +func getRecoveryInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_RECOVERY_URI +} + +func getBatchAddIpUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BATCHADDIP_URI +} + +func getBatchDelIpUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BATCHDELIP_URI +} + +func getBidInstancePriceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BID_PRICE_URI +} + +func listBidFlavorUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BID_FLAVOR_URI +} + +func getInstanceVNCUri(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/" + id + REQUEST_VNC_SUFFIX +} + +func getInstanceDeletionProtectionUri(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/" + id + REQUEST_DELETION_PROTECTION +} + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} + +func getVolumeUri() string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI +} + +func getVolumeV3Uri() string { + return URI_PREFIXV3 + REQUEST_VOLUME_URI +} + +func getVolumeUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI + "/" + id +} + +func getDeletePrepayVolumeUri() string { + return URI_PREFIXV2 + REQUEST_DELETEPREPAY +} + +func getVolumeV3UriWithId(id string) string { + return URI_PREFIXV3 + REQUEST_VOLUME_URI + "/" + id +} + +func getAutoRenewVolumeUri() string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI + REQUEST_AUTO_RENEW_URI +} + +func getCancelAutoRenewVolumeUri() string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI + REQUEST_CANCEL_AUTO_RENEW_URI +} +func getAvailableDiskInfo() string { + return URI_PREFIXV2 + REQUEST_VOLUME_DISK_URI +} + +func getSecurityGroupUri() string { + return URI_PREFIXV2 + REQUEST_SECURITYGROUP_URI +} + +func getSecurityGroupUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_SECURITYGROUP_URI + "/" + id +} + +func getSecurityGroupRuleUri() string { + return URI_PREFIXV2 + REQUEST_SECURITYGROUP_URI + "/rule" +} + +func getImageUri() string { + return URI_PREFIXV2 + REQUEST_IMAGE_URI +} + +func getImageUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_IMAGE_URI + "/" + id +} + +func getImageSharedUserUri(id string) string { + return URI_PREFIXV2 + REQUEST_IMAGE_URI + "/" + id + REQUEST_IMAGE_SHAREDUSER_URI +} + +func getImageOsUri() string { + return URI_PREFIXV2 + REQUEST_IMAGE_URI + REQUEST_IMAGE_OS_URI +} + +func getSnapshotUri() string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI +} + +func getSnapshotChainUri() string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI + REQUEST_CHAIN_URI +} + +func getSnapshotUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI + "/" + id +} + +func getASPUri() string { + return URI_PREFIXV2 + REQUEST_ASP_URI +} + +func getASPUriWithId(id string) string { + return URI_PREFIXV2 + REQUEST_ASP_URI + "/" + id +} + +func getSpecUri() string { + return URI_PREFIXV2 + REQUEST_SPEC_URI +} + +func getZoneUri() string { + return URI_PREFIXV2 + REQUEST_ZONE_URI +} + +func getPriceBySpecUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_PRICE_URI +} + +func getInstanceTypeZoneUri() string { + return URI_PREFIXV1 + REQUEST_TYPE_ZONE_URI +} + +func getChangeSubnetUri() string { + return URI_PREFIXV2 + REQUEST_SUBNET_URI + "/changeSubnet" +} + +func getChangeVpcUri() string { + return URI_PREFIXV2 + REQUEST_VPC_URI + "/changeVpc" +} + +func getInstanceEniUri(instanceId string) string { + return URI_PREFIXV2 + REQUEST_ENI_URI + "/" + instanceId +} + +func getKeypairUri() string { + return URI_PREFIXV2 + REQUEST_KEYPAIR_URI +} + +func getKeypairWithId(id string) string { + return URI_PREFIXV2 + REQUEST_KEYPAIR_URI + "/" + id +} + +func getAllStocks() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_GET_ALL_STOCKS +} + +func getStockWithDeploySet() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_GET_STOCK_WITH_DEPLOYSET +} + +func getStockWithSpec() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_GET_STOCK_WITH_SPEC +} + +func getCreateInstanceStock() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/stock/createInstance" +} + +func getResizeInstanceStock() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/stock/resizeInstance" +} + +func getFlavorSpecUri() string { + return URI_PREFIXV2 + REQUEST_FLAVOR_SPEC_URI +} + +func getResizeInstanceBySpec(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_BY_SPEC_URI + "/" + id +} + +func getRebuildBatchInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_REBUILD_URI +} + +func getChangeToPrepaidUri(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/" + id +} + +func getbindInstanceToTagsUri(id string) string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/" + id + REQUEST_TAG_URI +} + +func GetInstanceNoChargeListUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_NOCHARGE_URI +} + +func GetCreateBidInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BID_URI +} + +func GetCancelBidOrderUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_CANCEL_BIDORDER_URI +} + +func getBatchCreateAutoRenewRulesUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BATCH_CREATE_AUTORENEW_RULES_URI +} + +func getBatchDeleteAutoRenewRulesUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + REQUEST_BATCH_Delete_AUTORENEW_RULES_URI +} + +func getDeleteInstanceDeleteIngorePaymentUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/delete" +} + +func getDeleteRecycledInstanceUri(id string) string { + return URI_PREFIXV2 + "/recycle" + REQUEST_INSTANCE_URI + "/" + id +} + +func getListInstancesByIdsUrl() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/listByInstanceId" +} + +func getBatchDeleteInstanceWithRelatedResourceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/batchDelete" +} + +func getBatchStartInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/batchAction" +} + +func getBatchStopInstanceUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/batchAction" +} + +func getListInstanceTypesUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/types" +} + +func getListIdMappingsUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/id/mapping" +} + +func getBatchResizeInstanceUri() string { + return URI_PREFIXV2 + "/instanceBatchBySpec" +} + +func getInstanceDeleteProgress() string { + return URI_PREFIXV2 + "/instance/deleteProgress" +} + +func getTagVolumeUri(id string) string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI + "/" + id + REQUEST_TAG_URI +} + +func getUntagVolumeUri(id string) string { + return URI_PREFIXV2 + REQUEST_VOLUME_URI + "/" + id + REQUEST_TAG_URI +} + +func getTagSnapshotChainUri(id string) string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI + REQUEST_CHAIN_URI + "/" + id + REQUEST_TAG_URI +} + +func getUntagSnapshotChainUri(id string) string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI + REQUEST_CHAIN_URI + "/" + id + REQUEST_TAG_URI +} + +func getListInstancesByOrderIdUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/getServersByOrderId" +} + +func getCreateInstanceReturnOrderIdUri() string { + return URI_PREFIXV2 + "/instanceReturnOrderId" +} + +func getListAvailableResizeSpecsUri() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI +} + +func getBatchChangeInstanceToPrepay() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/batch/charging" +} + +func getBatchChangeInstanceToPostpay() string { + return URI_PREFIXV2 + REQUEST_INSTANCE_URI + "/batch/charging" +} + +func getResizeInstanceReturnOrderId(instanceId string) string { + return URI_PREFIXV2 + "/instanceReturnOrderId" + instanceId +} + +func listInstanceRoles() string { + return URI_PREFIXV2 + "/instance/role/list" +} + +func postInstanceRole() string { + return URI_PREFIXV2 + "/instance/role" +} + +func deleteIpv6() string { + return URI_PREFIXV2 + "/instance/delIpv6" +} + +func addIpv6() string { + return URI_PREFIXV2 + "/instance/addIpv6" +} + +func getImageToTagsUri(id string) string { + return URI_PREFIXV2 + REQUEST_IMAGE_URI + "/" + id + REQUEST_TAG_URI +} + +func getRemoteCopySnapshotUri(id string) string { + return URI_PREFIXV2 + REQUEST_SNAPSHOT_URI + "/remote_copy/" + id +} + +func getImportCustomImageUri() string { + return URI_PREFIXV2 + "/image/import" +} + +func getAvailableImagesBySpecUri() string { + return URI_PREFIXV2 + "/image/getAvailableImagesBySpec" +} diff --git a/bce-sdk-go/services/bcc/client.go b/bce-sdk-go/services/bcc/client.go new file mode 100644 index 0000000..21c095e --- /dev/null +++ b/bce-sdk-go/services/bcc/client.go @@ -0,0 +1,2270 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BOS service + +// Package bcc defines the BCC services of BCE. The supported APIs are all defined in sub-package + +package bcc + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bcc/api" +) + +const DEFAULT_SERVICE_DOMAIN = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + +// Client of BCC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the BCC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endPoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endPoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// CreateInstance - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *api.CreateInstanceArgs) (*api.CreateInstanceResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstance(c, args, body) +} + +// CreateInstanceV2 - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceV2(argsV2 *api.CreateInstanceArgsV2) (*api.CreateInstanceResult, error) { + if len(argsV2.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return nil, err + } + argsV2.AdminPass = cryptedPass + } + defaultTrue := true + defaultFalse := false + if argsV2.IsOpenHostnameDomain == nil { + argsV2.IsOpenHostnameDomain = &defaultFalse + } + if argsV2.AutoSeqSuffix == nil { + argsV2.AutoSeqSuffix = &defaultFalse + } + if argsV2.IsOpenHostEye == nil { + argsV2.IsOpenHostEye = &defaultTrue + } + if argsV2.RelationTag == nil { + argsV2.RelationTag = &defaultFalse + } + if argsV2.CdsAutoRenew == nil { + argsV2.CdsAutoRenew = &defaultFalse + } + if argsV2.IsOpenIpv6 == nil { + argsV2.IsOpenIpv6 = &defaultFalse + } + jsonBytes, err := json.Marshal(argsV2) + if err != nil { + return nil, err + } + args := &api.CreateInstanceArgs{} + err = json.Unmarshal(jsonBytes, args) + if err != nil { + return nil, err + } + args.ClientToken = argsV2.ClientToken + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstance(c, args, body) +} + +// CreateInstance - create an instance with the specific parameters and support the passing in of label +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceByLabel(args *api.CreateSpecialInstanceBySpecArgs) (*api.CreateInstanceResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstanceByLabel(c, args, body) +} + +// CreateInstanceBySpec - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceBySpecResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceBySpec(args *api.CreateInstanceBySpecArgs) (*api.CreateInstanceBySpecResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstanceBySpec(c, args, body) +} + +// CreateInstanceBySpecV2 - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceBySpecResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceBySpecV2(argsV2 *api.CreateInstanceBySpecArgsV2) (*api.CreateInstanceBySpecResult, error) { + if len(argsV2.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return nil, err + } + + argsV2.AdminPass = cryptedPass + } + defaultTrue := true + defaultFalse := false + if argsV2.IsOpenHostnameDomain == nil { + argsV2.IsOpenHostnameDomain = &defaultFalse + } + if argsV2.AutoSeqSuffix == nil { + argsV2.AutoSeqSuffix = &defaultFalse + } + if argsV2.IsOpenHostEye == nil { + argsV2.IsOpenHostEye = &defaultTrue + } + if argsV2.RelationTag == nil { + argsV2.RelationTag = &defaultFalse + } + if argsV2.CdsAutoRenew == nil { + argsV2.CdsAutoRenew = &defaultFalse + } + if argsV2.IsOpenIpv6 == nil { + argsV2.IsOpenIpv6 = &defaultFalse + } + + jsonBytes, jsonErr := json.Marshal(argsV2) + if jsonErr != nil { + return nil, jsonErr + } + + args := &api.CreateInstanceBySpecArgs{} + err := json.Unmarshal(jsonBytes, args) + if err != nil { + return nil, err + } + args.ClientToken = argsV2.ClientToken + + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstanceBySpec(c, args, body) +} + +// CreateInstanceV3 - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceV3Result: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceV3(args *api.CreateInstanceV3Args) (*api.CreateInstanceV3Result, error) { + if len(args.Password) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.Password) + if err != nil { + return nil, err + } + + args.Password = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateInstanceV3(c, args, body) +} + +// ListInstances - list all instance with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance +// +// RETURNS: +// - *api.ListInstanceResult: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) ListInstances(args *api.ListInstanceArgs) (*api.ListInstanceResult, error) { + return api.ListInstances(c, args) +} + +// ListRecycleInstances - list all instance in the recycle bin with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance in the recycle bin +// +// RETURNS: +// - *api.ListRecycleInstanceResult: the result of list Instance in the recycle bin +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycleInstances(args *api.ListRecycleInstanceArgs) (*api.ListRecycleInstanceResult, error) { + return api.ListRecycleInstances(c, args) +} + +// ListServersByMarkerV3 - list all instance with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance +// +// RETURNS: +// - *api.LogicMarkerResultResponseV3: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) ListServersByMarkerV3(args *api.ListServerRequestV3Args) (*api.LogicMarkerResultResponseV3, error) { + return api.ListServersByMarkerV3(c, args) +} + +// GetInstanceDetail - get a specific instance detail info +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - *api.GetInstanceDetailResult: the result of get instance detail info +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceDetail(instanceId string) (*api.GetInstanceDetailResult, error) { + return api.GetInstanceDetail(c, instanceId) +} + +func (c *Client) GetInstanceDetailWithDeploySet(instanceId string, isDeploySet bool) (*api.GetInstanceDetailResult, + error) { + return api.GetInstanceDetailWithDeploySet(c, instanceId, isDeploySet) +} + +func (c *Client) GetInstanceDetailWithDeploySetAndFailed(instanceId string, + isDeploySet bool, containsFailed bool) (*api.GetInstanceDetailResult, + error) { + return api.GetInstanceDetailWithDeploySetAndFailed(c, instanceId, isDeploySet, containsFailed) +} + +// DeleteInstance - delete a specific instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstance(instanceId string) error { + return api.DeleteInstance(c, instanceId) +} + +// AutoReleaseInstance - set releaseTime of a postpay instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - releaseTime: an UTC string,eg:"2021-05-01T07:58:09Z" +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AutoReleaseInstance(instanceId string, releaseTime string) error { + args := &api.AutoReleaseArgs{ + ReleaseTime: releaseTime, + } + return api.AutoReleaseInstance(c, instanceId, args) +} + +// ResizeInstance - resize a specific instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to resize a specific instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeInstance(instanceId string, args *api.ResizeInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ResizeInstance(c, instanceId, args.ClientToken, body) +} + +// RebuildInstance - rebuild an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to rebuild an instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebuildInstance(instanceId string, args *api.RebuildInstanceArgs) error { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return err + } + args.AdminPass = cryptedPass + } + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.RebuildInstance(c, instanceId, body) +} + +func (c *Client) RebuildInstanceV2(instanceId string, argsV2 *api.RebuildInstanceArgsV2) error { + if len(argsV2.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return err + } + argsV2.AdminPass = cryptedPass + } + + defaultTrue := true + if argsV2.IsPreserveData == nil { + argsV2.IsPreserveData = &defaultTrue + } + if argsV2.IsOpenHostEye == nil { + argsV2.IsOpenHostEye = &defaultTrue + } + + argsV2JsonBytes, err := json.Marshal(argsV2) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(argsV2JsonBytes) + if err != nil { + return err + } + + return api.RebuildInstance(c, instanceId, body) +} + +// StartInstance - start an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StartInstance(instanceId string) error { + return api.StartInstance(c, instanceId) +} + +// StopInstance - stop an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - forceStop: choose to force stop an instance or not +// - stopWithNoCharge: choose to stop with nocharge an instance or not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StopInstanceWithNoCharge(instanceId string, forceStop bool, stopWithNoCharge bool) error { + args := &api.StopInstanceArgs{ + ForceStop: forceStop, + StopWithNoCharge: stopWithNoCharge, + } + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.StopInstance(c, instanceId, body) +} + +// StopInstance - stop an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - forceStop: choose to force stop an instance or not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StopInstance(instanceId string, forceStop bool) error { + args := &api.StopInstanceArgs{ + ForceStop: forceStop, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.StopInstance(c, instanceId, body) +} + +// RebootInstance - restart an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - forceStop: choose to force stop an instance or not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(instanceId string, forceStop bool) error { + args := &api.StopInstanceArgs{ + ForceStop: forceStop, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.RebootInstance(c, instanceId, body) +} + +func (c *Client) RecoveryInstance(args *api.RecoveryInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.RecoveryInstance(c, body) +} + +// ChangeInstancePass - change an instance's password +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to change password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ChangeInstancePass(instanceId string, args *api.ChangeInstancePassArgs) error { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return err + } + args.AdminPass = cryptedPass + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ChangeInstancePass(c, instanceId, body) +} + +// ModifyDeletionProtection - Modify deletion protection of specified instance +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to modify deletion protection, default 0 for deletable and 1 for deletion protection +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyDeletionProtection(instanceId string, args *api.DeletionProtectionArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.ModifyDeletionProtection(c, instanceId, body) +} + +// ModifyInstanceAttribute - modify an instance's attribute +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's attribute +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstanceAttribute(instanceId string, args *api.ModifyInstanceAttributeArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ModifyInstanceAttribute(c, instanceId, body) +} + +// ModifyInstanceDesc - modify an instance's description +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's description +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstanceDesc(instanceId string, args *api.ModifyInstanceDescArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ModifyInstanceDesc(c, instanceId, body) +} + +// ModifyInstanceHostname - modify an instance's hostname +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments of now instance's hostname +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyInstanceHostname(instanceId string, args *api.ModifyInstanceHostnameArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ModifyInstanceHostname(c, instanceId, body) +} + +// BindSecurityGroup - bind a security group to an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - securityGroupId: the security group ID which need to bind +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindSecurityGroup(instanceId string, securityGroupId string) error { + args := &api.BindSecurityGroupArgs{ + SecurityGroupId: securityGroupId, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BindSecurityGroup(c, instanceId, body) +} + +// UnBindSecurityGroup - unbind a security group ID from instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - securityGroupId: the security group ID which need to unbind +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindSecurityGroup(instanceId string, securityGroupId string) error { + args := &api.BindSecurityGroupArgs{ + SecurityGroupId: securityGroupId, + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.UnBindSecurityGroup(c, instanceId, body) +} + +// GetInstanceVNC - get an instance's VNC url +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - *api.GetInstanceVNCResult: the result of get instance's VNC url +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceVNC(instanceId string) (*api.GetInstanceVNCResult, error) { + return api.GetInstanceVNC(c, instanceId) +} + +// InstancePurchaseReserved - purchase reserve an instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to purchase reserved an instance +// +// RETURNS: +// - *api.InstancePurchaseReservedResult: the result of purchase reserve an instance +// - error: nil if success otherwise the specific error +func (c *Client) InstancePurchaseReserved(instanceId string, args *api.PurchaseReservedArgs) (*api.InstancePurchaseReservedResult, error) { + // this api only support Prepaid instance + args.Billing.PaymentTiming = api.PaymentTimingPrePaid + relatedRenewFlag := args.RelatedRenewFlag + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.InstancePurchaseReserved(c, instanceId, relatedRenewFlag, args.ClientToken, body) +} + +// GetBidInstancePrice - get the market price of the specified bidding instance +// +// PARAMS: +// - args: the arguments to get the bidding instance market price +// +// RETURNS: +// - *GetBidInstancePriceResult: result of the market price of the specified bidding instance +// - error: nil if success otherwise the specific error +func (c *Client) GetBidInstancePrice(args *api.GetBidInstancePriceArgs) (*api.GetBidInstancePriceResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.GetBidInstancePrice(c, args.ClientToken, body) +} + +// ListBidFlavor - list all flavors of the bidding instance +// +// RETURNS: +// - *ListBidFlavorResult: result of the flavor list +// - error: nil if success otherwise the specific error +func (c *Client) ListBidFlavor() (*api.ListBidFlavorResult, error) { + return api.ListBidFlavor(c) +} + +// DeleteInstanceWithRelateResource - delete an instance and all eip/cds relate it +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to delete instance and its relate resource +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstanceWithRelateResource(instanceId string, args *api.DeleteInstanceWithRelateResourceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.DeleteInstanceWithRelatedResource(c, instanceId, body) +} + +// InstanceChangeSubnet - change an instance's subnet +// +// PARAMS: +// - args: the arguments to change an instance's subnet +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceChangeSubnet(args *api.InstanceChangeSubnetArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.InstanceChangeSubnet(c, body) +} + +// InstanceChangeVpc - change an instance's vpc +// +// PARAMS: +// - args: the arguments to change an instance's vpc +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceChangeVpc(args *api.InstanceChangeVpcArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.InstanceChangeVpc(c, body) +} + +// BatchAddIP - Add ips to instance +// +// PARAMS: +// - args: the arguments to add ips to bbc instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchAddIP(args *api.BatchAddIpArgs) (*api.BatchAddIpResponse, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.BatchAddIp(c, args, body) +} + +// BatchDelIP - Delete ips of instance +// +// PARAMS: +// - args: the arguments to add ips to bbc instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDelIP(args *api.BatchDelIpArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.BatchDelIp(c, args, body) +} + +// cds sdk +// CreateCDSVolume - create a CDS volume +// +// PARAMS: +// - args: the arguments to create CDS +// +// RETURNS: +// - *api.CreateCDSVolumeResult: the result of create CDS volume, contains new volume ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateCDSVolume(args *api.CreateCDSVolumeArgs) (*api.CreateCDSVolumeResult, error) { + return api.CreateCDSVolume(c, args) +} + +// cds sdk +// CreateCDSVolumeV3 - create a CDS volume +// +// PARAMS: +// - args: the arguments to create CDS +// +// RETURNS: +// - *api.CreateCDSVolumeResult: the result of create CDS volume, contains new volume ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateCDSVolumeV3(args *api.CreateCDSVolumeV3Args) (*api.CreateCDSVolumeResult, error) { + return api.CreateCDSVolumeV3(c, args) +} + +// ListCDSVolume - list all cds volume with the specific parameters +// +// PARAMS: +// - args: the arguments to list all cds +// +// RETURNS: +// - *api.ListCDSVolumeResult: the result of list all CDS volume +// - error: nil if success otherwise the specific error +func (c *Client) ListCDSVolume(queryArgs *api.ListCDSVolumeArgs) (*api.ListCDSVolumeResult, error) { + return api.ListCDSVolume(c, queryArgs) +} + +// ListCDSVolumeV3 - list all cds volume with the specific parameters +// +// PARAMS: +// - args: the arguments to list all cds +// +// RETURNS: +// - *api.ListCDSVolumeResultV3: the result of list all CDS volume +// - error: nil if success otherwise the specific error +func (c *Client) ListCDSVolumeV3(queryArgs *api.ListCDSVolumeArgs) (*api.ListCDSVolumeResultV3, error) { + return api.ListCDSVolumeV3(c, queryArgs) +} + +// GetCDSVolumeDetail - get a CDS volume's detail info +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// +// RETURNS: +// - *api.GetVolumeDetailResult: the result of get a specific CDS volume's info +// - error: nil if success otherwise the specific error +func (c *Client) GetCDSVolumeDetail(volumeId string) (*api.GetVolumeDetailResult, error) { + return api.GetCDSVolumeDetail(c, volumeId) +} + +// GetCDSVolumeDetailV3 - get a CDS volume's detail info +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// +// RETURNS: +// - *api.GetVolumeDetailResultV3: the result of get a specific CDS volume's info +// - error: nil if success otherwise the specific error +func (c *Client) GetCDSVolumeDetailV3(volumeId string) (*api.GetVolumeDetailResultV3, error) { + return api.GetCDSVolumeDetailV3(c, volumeId) +} + +// AttachCDSVolume - attach a CDS volume to an instance +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to attach a CDS volume +// +// RETURNS: +// - *api.AttachVolumeResult: the result of attach a CDS volume +// - error: nil if success otherwise the specific error +func (c *Client) AttachCDSVolume(volumeId string, args *api.AttachVolumeArgs) (*api.AttachVolumeResult, error) { + return api.AttachCDSVolume(c, volumeId, args) +} + +// DetachCDSVolume - detach a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to detach a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DetachCDSVolume(volumeId string, args *api.DetachVolumeArgs) error { + return api.DetachCDSVolume(c, volumeId, args) +} + +// DeleteCDSVolume - delete a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteCDSVolume(volumeId string) error { + return api.DeleteCDSVolume(c, volumeId) +} + +// DeleteCDSVolumeNew - delete a CDS volume and snapshot +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to delete a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteCDSVolumeNew(volumeId string, args *api.DeleteCDSVolumeArgs) error { + return api.DeleteCDSVolumeNew(c, volumeId, args) +} + +// ResizeCDSVolume - resize a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to resize CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeCDSVolume(volumeId string, args *api.ResizeCSDVolumeArgs) error { + return api.ResizeCDSVolume(c, volumeId, args) +} + +// RollbackCDSVolume - rollback a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to rollback a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RollbackCDSVolume(volumeId string, args *api.RollbackCSDVolumeArgs) error { + return api.RollbackCDSVolume(c, volumeId, args) +} + +// PurchaseReservedCDSVolume - purchase reserve a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to purchase reserve a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PurchaseReservedCDSVolume(volumeId string, args *api.PurchaseReservedCSDVolumeArgs) error { + return api.PurchaseReservedCDSVolume(c, volumeId, args) +} + +// RenameCDSVolume - rename a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to rename a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameCDSVolume(volumeId string, args *api.RenameCSDVolumeArgs) error { + return api.RenameCDSVolume(c, volumeId, args) +} + +// ModifyCDSVolume - modify a CDS volume +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to modify a CDS volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyCDSVolume(volumeId string, args *api.ModifyCSDVolumeArgs) error { + return api.ModifyCDSVolume(c, volumeId, args) +} + +// ModifyChargeTypeCDSVolume - modify a CDS volume's charge type +// +// PARAMS: +// - volumeId: the specific CDS volume ID +// - args: the arguments to create instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyChargeTypeCDSVolume(volumeId string, args *api.ModifyChargeTypeCSDVolumeArgs) error { + return api.ModifyChargeTypeCDSVolume(c, volumeId, args) +} + +// AutoRenewCDSVolume - auto renew the specified cds volume +// +// PARAMS: +// - args: the arguments to auto renew the cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AutoRenewCDSVolume(args *api.AutoRenewCDSVolumeArgs) error { + return api.AutoRenewCDSVolume(c, args) +} + +// CancelAutoRenewCDSVolume - cancel auto renew the specified cds volume +// +// PARAMS: +// - args: the arguments to cancel auto renew the cds volume +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelAutoRenewCDSVolume(args *api.CancelAutoRenewCDSVolumeArgs) error { + return api.CancelAutoRenewCDSVolume(c, args) +} + +// securityGroup sdk +// CreateSecurityGroup - create a security group +// +// PARAMS: +// - args: the arguments to create security group +// +// RETURNS: +// - *api.CreateSecurityGroupResult: the result of create security group +// - error: nil if success otherwise the specific error +func (c *Client) CreateSecurityGroup(args *api.CreateSecurityGroupArgs) (*api.CreateSecurityGroupResult, error) { + return api.CreateSecurityGroup(c, args) +} + +// ListSecurityGroup - list all security group +// +// PARAMS: +// - args: the arguments to list all security group +// +// RETURNS: +// - *api.ListSecurityGroupResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroup(queryArgs *api.ListSecurityGroupArgs) (*api.ListSecurityGroupResult, error) { + return api.ListSecurityGroup(c, queryArgs) +} + +// AuthorizeSecurityGroupRule - authorize a security group rule +// +// PARAMS: +// - securityGroupId: the specific securityGroup ID +// - args: the arguments to authorize a security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AuthorizeSecurityGroupRule(securityGroupId string, args *api.AuthorizeSecurityGroupArgs) error { + return api.AuthorizeSecurityGroupRule(c, securityGroupId, args) +} + +// RevokeSecurityGroupRule - revoke a security group rule +// +// PARAMS: +// - securityGroupId: the specific securityGroup ID +// - args: the arguments to revoke security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RevokeSecurityGroupRule(securityGroupId string, args *api.RevokeSecurityGroupArgs) error { + return api.RevokeSecurityGroupRule(c, securityGroupId, args) +} + +// DeleteSecurityGroup - delete a security group +// +// PARAMS: +// - securityGroupId: the specific securityGroup ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSecurityGroup(securityGroupId string) error { + return api.DeleteSecurityGroup(c, securityGroupId) +} + +// image sdk +// CreateImage - create an image +// +// PARAMS: +// - args: the arguments to create image +// +// RETURNS: +// - *api.CreateImageResult: the result of create Image +// - error: nil if success otherwise the specific error +func (c *Client) CreateImage(args *api.CreateImageArgs) (*api.CreateImageResult, error) { + return api.CreateImage(c, args) +} + +// ListImage - list all images +// +// PARAMS: +// - args: the arguments to list all images +// +// RETURNS: +// - *api.ListImageResult: the result of list all images +// - error: nil if success otherwise the specific error +func (c *Client) ListImage(queryArgs *api.ListImageArgs) (*api.ListImageResult, error) { + return api.ListImage(c, queryArgs) +} + +// GetImageDetail - get an image's detail info +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - *api.GetImageDetailResult: the result of get image's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetImageDetail(imageId string) (*api.GetImageDetailResult, error) { + return api.GetImageDetail(c, imageId) +} + +// DeleteImage - delete an image +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteImage(imageId string) error { + return api.DeleteImage(c, imageId) +} + +// RemoteCopyImage - copy an image from other region +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to remote copy an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RemoteCopyImage(imageId string, args *api.RemoteCopyImageArgs) error { + return api.RemoteCopyImage(c, imageId, args) +} + +// RemoteCopyImageReturnImageIds - copy an image from other region +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to remote copy an image +// +// RETURNS: +// - imageIds of destination region if success otherwise the specific error +func (c *Client) RemoteCopyImageReturnImageIds(imageId string, args *api.RemoteCopyImageArgs) (*api.RemoteCopyImageResult, error) { + return api.RemoteCopyImageReturnImageIds(c, imageId, args) +} + +// CancelRemoteCopyImage - cancel a copy image from other region operation +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelRemoteCopyImage(imageId string) error { + return api.CancelRemoteCopyImage(c, imageId) +} + +// ShareImage - share an image +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to share an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ShareImage(imageId string, args *api.SharedUser) error { + return api.ShareImage(c, imageId, args) +} + +// UnShareImage - cancel share an image +// +// PARAMS: +// - imageId: the specific image ID +// - args: the arguments to cancel share an image +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnShareImage(imageId string, args *api.SharedUser) error { + return api.UnShareImage(c, imageId, args) +} + +// GetImageSharedUser - get user list use this image +// +// PARAMS: +// - imageId: the specific image ID +// +// RETURNS: +// - *api.GetImageSharedUserResult: the result of user list +// - error: nil if success otherwise the specific error +func (c *Client) GetImageSharedUser(imageId string) (*api.GetImageSharedUserResult, error) { + return api.GetImageSharedUser(c, imageId) +} + +// GetImageOS - get image OS +// +// PARAMS: +// - args: the arguments to get OS info +// +// RETURNS: +// - *api.GetImageOsResult: the result of get image OS info +// - error: nil if success otherwise the specific error +func (c *Client) GetImageOS(args *api.GetImageOsArgs) (*api.GetImageOsResult, error) { + return api.GetImageOS(c, args) +} + +// CreateSnapshot - create a snapshot +// +// PARAMS: +// - args: the arguments to create a snapshot +// +// RETURNS: +// - *api.CreateSnapshotResult: the result of create snapshot +// - error: nil if success otherwise the specific error +func (c *Client) CreateSnapshot(args *api.CreateSnapshotArgs) (*api.CreateSnapshotResult, error) { + return api.CreateSnapshot(c, args) +} + +// ListSnapshot - list all snapshots +// +// PARAMS: +// - args: the arguments to list all snapshots +// +// RETURNS: +// - *api.ListSnapshotResult: the result of list all snapshots +// - error: nil if success otherwise the specific error +func (c *Client) ListSnapshot(args *api.ListSnapshotArgs) (*api.ListSnapshotResult, error) { + return api.ListSnapshot(c, args) +} + +// ListSnapshotChain - list all snapshot chains +// +// PARAMS: +// - args: the arguments to list all snapshot chains +// +// RETURNS: +// - *api.ListSnapshotChainResult: the result of list all snapshot chains +// - error: nil if success otherwise the specific error +func (c *Client) ListSnapshotChain(args *api.ListSnapshotChainArgs) (*api.ListSnapshotChainResult, error) { + return api.ListSnapshotChain(c, args) +} + +// GetSnapshotDetail - get a snapshot's detail info +// +// PARAMS: +// - snapshotId: the specific snapshot ID +// +// RETURNS: +// - *api.GetSnapshotDetailResult: the result of get snapshot's detail info +// - error: nil if success otherwise the specific error +func (c *Client) GetSnapshotDetail(snapshotId string) (*api.GetSnapshotDetailResult, error) { + return api.GetSnapshotDetail(c, snapshotId) +} + +// DeleteSnapshot - delete a snapshot +// +// PARAMS: +// - snapshotId: the specific snapshot ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSnapshot(snapshotId string) error { + return api.DeleteSnapshot(c, snapshotId) +} + +// CreateAutoSnapshotPolicy - create an auto snapshot policy +// +// PARAMS: +// - args: the arguments to create an auto snapshot policy +// +// RETURNS: +// - *api.CreateASPResult: the result of create an auto snapshot policy +// - error: nil if success otherwise the specific error +func (c *Client) CreateAutoSnapshotPolicy(args *api.CreateASPArgs) (*api.CreateASPResult, error) { + return api.CreateAutoSnapshotPolicy(c, args) +} + +// AttachAutoSnapshotPolicy - attach an ASP to volumes +// +// PARAMS: +// - aspId: the specific auto snapshot policy ID +// - args: the arguments to attach an ASP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AttachAutoSnapshotPolicy(aspId string, args *api.AttachASPArgs) error { + return api.AttachAutoSnapshotPolicy(c, aspId, args) +} + +// DetachAutoSnapshotPolicy - detach an ASP +// +// PARAMS: +// - aspId: the specific auto snapshot policy ID +// - args: the arguments to detach an ASP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DetachAutoSnapshotPolicy(aspId string, args *api.DetachASPArgs) error { + return api.DetachAutoSnapshotPolicy(c, aspId, args) +} + +// DeleteAutoSnapshotPolicy - delete an auto snapshot policy +// +// PARAMS: +// - aspId: the specific auto snapshot policy ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAutoSnapshotPolicy(aspId string) error { + return api.DeleteAutoSnapshotPolicy(c, aspId) +} + +// ListAutoSnapshotPolicy - list all auto snapshot policies +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.ListASPResult: the result of list all auto snapshot policies +// - error: nil if success otherwise the specific error +func (c *Client) ListAutoSnapshotPolicy(args *api.ListASPArgs) (*api.ListASPResult, error) { + return api.ListAutoSnapshotPolicy(c, args) +} + +// GetAutoSnapshotPolicy - get an auto snapshot policy's meta +// +// PARAMS: +// - aspId: the specific auto snapshot policy ID +// +// RETURNS: +// - *api.GetASPDetailResult: the result of get an auto snapshot policy's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetAutoSnapshotPolicy(aspId string) (*api.GetASPDetailResult, error) { + return api.GetAutoSnapshotPolicyDetail(c, aspId) +} + +// UpdateAutoSnapshotPolicy - update an auto snapshot policy +// +// PARAMS: +// - args: the arguments to update an auto snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAutoSnapshotPolicy(args *api.UpdateASPArgs) error { + return api.UpdateAutoSnapshotPolicy(c, args) +} + +// ListSpec - list all spec +// +// RETURNS: +// - *api.ListSpecResult: the result of all spec +// - error: nil if success otherwise the specific error +func (c *Client) ListSpec() (*api.ListSpecResult, error) { + return api.ListSpec(c) +} + +// ListZone - list all zones +// +// RETURNS: +// - *api.ListZoneResult: the result of list all zones +// - error: nil if success otherwise the specific error +func (c *Client) ListZone() (*api.ListZoneResult, error) { + return api.ListZone(c) +} + +// ListFlavorSpec - get the specified flavor list +// +// PARAMS: +// - args: the arguments to list the specified flavor +// +// RETURNS: +// - *api.ListFlavorSpecResult: result of the specified flavor list +// - error: nil if success otherwise the specific error +func (c *Client) ListFlavorSpec(args *api.ListFlavorSpecArgs) (*api.ListFlavorSpecResult, error) { + return api.ListFlavorSpec(c, args) +} + +// GetPriceBySpec - get the price information of specified instance. +// +// PARAMS: +// - args: the arguments to get the price information of specified instance. +// +// RETURNS: +// - *api.GetPriceBySpecResult: result of the specified instance's price information +// - error: nil if success otherwise the specific error +func (c *Client) GetPriceBySpec(args *api.GetPriceBySpecArgs) (*api.GetPriceBySpecResult, error) { + return api.GetPriceBySpec(c, args) +} + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - args: the arguments to create a deploy set +// +// RETURNS: +// - *CreateDeploySetResult: results of creating a deploy set +// - error: nil if success otherwise the specific error +func (c *Client) CreateDeploySet(args *api.CreateDeploySetArgs) (*api.CreateDeploySetResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.CreateDeploySet(c, args.ClientToken, body) +} + +// ListDeploySets - list all deploy sets +// +// RETURNS: +// - *ListDeploySetsResult: the result of list all deploy sets +// - error: nil if success otherwise the specific error +func (c *Client) ListDeploySets() (*api.ListDeploySetsResult, error) { + return api.ListDeploySets(c) +} + +// ModifyDeploySet - modify the deploy set +// +// PARAMS: +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *ModifyDeploySetArgs: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) ModifyDeploySet(deploySetId string, args *api.ModifyDeploySetArgs) (error, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, nil + } + return api.ModifyDeploySet(c, deploySetId, args.ClientToken, body), nil +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDeploySet(deploySetId string) error { + return api.DeleteDeploySet(c, deploySetId) +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *GetDeploySetResult: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) GetDeploySet(deploySetId string) (*api.DeploySetResult, error) { + return api.GetDeploySet(c, deploySetId) +} + +// UpdateInstanceDeploySet - update deployset and instance relation +// +// PARAMS: +// - args: the arguments to update deployset and instance relation +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceDeploySet(args *api.UpdateInstanceDeployArgs) (error, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.UpdateInstanceDeploy(c, args.ClientToken, body), nil +} + +// DelInstanceDeploySet - delete deployset and instance relation +// +// PARAMS: +// - args: the arguments to delete deployset and instance relation +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DelInstanceDeploySet(args *api.DelInstanceDeployArgs) (error, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.DelInstanceDeploy(c, args.ClientToken, body), nil +} + +// ResizeInstanceBySpec - resize a specific instance +// +// PARAMS: +// - instanceId: the specific instance ID +// - args: the arguments to resize a specific instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeInstanceBySpec(instanceId string, args *api.ResizeInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.ResizeInstanceBySpec(c, instanceId, args.ClientToken, body) +} + +// RebuildBatchInstance - batch rebuild instances +// +// PARAMS: +// - args: the arguments to batch rebuild instances +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchRebuildInstances(args *api.RebuildBatchInstanceArgs) error { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return err + } + args.AdminPass = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BatchRebuildInstances(c, body) +} + +// RebuildBatchInstancesV2 - batch rebuild instances +// +// PARAMS: +// - args: the arguments to batch rebuild instances +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchRebuildInstancesV2(argsV2 *api.RebuildBatchInstanceArgsV2) error { + if len(argsV2.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, argsV2.AdminPass) + if err != nil { + return err + } + argsV2.AdminPass = cryptedPass + } + + defaultTrue := true + if argsV2.IsOpenHostEye == nil { + argsV2.IsOpenHostEye = &defaultTrue + } + if argsV2.IsPreserveData == nil { + argsV2.IsPreserveData = &defaultTrue + } + + jsonBytes, jsonErr := json.Marshal(argsV2) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BatchRebuildInstances(c, body) +} + +// ChangeToPrepaid - to prepaid +// +// PARAMS: +// - instanceId: instanceId +// - args: the arguments to ChangeToPrepaid +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ChangeToPrepaid(instanceId string, args *api.ChangeToPrepaidRequest) (*api.ChangeToPrepaidResponse, + error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.ChangeToPrepaid(c, instanceId, body) +} + +// BindInstanceToTags - bind instance to tags +// +// PARAMS: +// - instanceId: instanceId +// - args: the arguments to BindInstanceToTags +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindInstanceToTags(instanceId string, args *api.BindTagsRequest) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BindInstanceToTags(c, instanceId, body) +} + +// UnBindInstanceToTags - unbind instance to tags +// +// PARAMS: +// - instanceId: instanceId +// - args: the arguments to unBindInstanceToTags +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindInstanceToTags(instanceId string, args *api.UnBindTagsRequest) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.UnBindInstanceToTags(c, instanceId, body) +} + +// GetInstanceNoChargeList - get instance with nocharge list +// +// PARAMS: +// - args: the arguments to list all nocharge instance +// +// RETURNS: +// - *api.ListInstanceResult: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceNoChargeList(args *api.ListInstanceArgs) (*api.ListInstanceResult, error) { + return api.GetInstanceNoChargeList(c, args) +} + +// CreateBidInstance - create an instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create instance +// +// RETURNS: +// - *api.CreateInstanceResult: the result of create Instance, contains new Instance ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateBidInstance(args *api.CreateInstanceArgs) (*api.CreateInstanceResult, error) { + if len(args.AdminPass) > 0 { + cryptedPass, err := api.Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.AdminPass) + if err != nil { + return nil, err + } + + args.AdminPass = cryptedPass + } + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CreateBidInstance(c, args.ClientToken, body) +} + +// CancelBidOrder - Cancel the bidding instance order. +// +// PARAMS: +// - args: the arguments to cancel bid order +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelBidOrder(args *api.CancelBidOrderRequest) (*api.CreateBidInstanceResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.CancelBidOrder(c, args.ClientToken, body) +} + +// GetAvailableDiskInfo - get available diskInfos of the specified zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: the zone name eg:cn-bj-a +// +// RETURNS: +// - *GetAvailableDiskInfoResult: the result of the specified zone diskInfos +// - error: nil if success otherwise the specific error +func (c *Client) GetAvailableDiskInfo(zoneName string) (*api.GetAvailableDiskInfoResult, error) { + return api.GetAvailableDiskInfo(c, zoneName) +} + +// DeletePrepayVolume - delete the volumes for prepay +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments of method +// +// RETURNS: +// - *VolumeDeleteResultResponse: the result of deleting volumes +// - error: nil if success otherwise the specific error +func (c *Client) DeletePrepayVolume(args *api.VolumePrepayDeleteRequestArgs) (*api.VolumeDeleteResultResponse, error) { + return api.DeletePrepayVolume(c, args) +} + +// ListTypeZones - list instanceType zones +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceType: the instanceType like "N1" +// +// RETURNS: +// - *api.ListTypeZonesResult: the result of list instanceType zones +// - error: nil if success otherwise the specific error +func (c *Client) ListTypeZones(args *api.ListTypeZonesArgs) (*api.ListTypeZonesResult, error) { + return api.ListTypeZones(c, args) +} + +// ListInstanceEni - get the eni list of the bcc instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: the bcc instance id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ListInstanceEnis(instanceId string) (*api.ListInstanceEniResult, error) { + return api.ListInstanceEnis(c, instanceId) +} + +func (c *Client) CreateKeypair(args *api.CreateKeypairArgs) (*api.KeypairResult, error) { + return api.CreateKeypair(c, args) +} + +func (c *Client) ImportKeypair(args *api.ImportKeypairArgs) (*api.KeypairResult, error) { + return api.ImportKeypair(c, args) +} + +func (c *Client) AttachKeypair(args *api.AttackKeypairArgs) error { + return api.AttachKeypair(c, args) +} + +func (c *Client) DetachKeypair(args *api.DetachKeypairArgs) error { + return api.DetachKeypair(c, args) +} + +func (c *Client) DeleteKeypair(args *api.DeleteKeypairArgs) error { + return api.DeleteKeypair(c, args) +} + +func (c *Client) GetKeypairDetail(keypairId string) (*api.KeypairResult, error) { + return api.GetKeypairDetail(c, keypairId) +} + +func (c *Client) ListKeypairs(args *api.ListKeypairArgs) (*api.ListKeypairResult, error) { + return api.ListKeypairs(c, args) +} + +func (c *Client) RenameKeypair(args *api.RenameKeypairArgs) error { + return api.RenameKeypair(c, args) +} + +func (c *Client) UpdateKeypairDescription(args *api.KeypairUpdateDescArgs) error { + return api.UpdateKeypairDescription(c, args) +} + +// GetAllStocks - get the bcc and bbc's stock +// +// RETURNS: +// - *GetAllStocksResult: the result of the bcc and bbc's stock +// - error: nil if success otherwise the specific error +func (c *Client) GetAllStocks() (*api.GetAllStocksResult, error) { + return api.GetAllStocks(c) +} + +// GetStockWithDeploySet - get the bcc's stock with deploySet +// +// RETURNS: +// - *GetStockWithDeploySetResults: the result of the bcc's stock +// - error: nil if success otherwise the specific error +func (c *Client) GetStockWithDeploySet(args *api.GetStockWithDeploySetArgs) (*api.GetStockWithDeploySetResults, error) { + return api.GetStockWithDeploySet(c, args) +} + +// GetStockWithSpec - get the bcc's stock with spec +// +// RETURNS: +// - *GetStockWithSpecResults: the result of the bcc's stock +// - error: nil if success otherwise the specific error +func (c *Client) GetStockWithSpec(args *api.GetStockWithSpecArgs) (*api.GetStockWithSpecResults, error) { + return api.GetStockWithSpec(c, args) +} + +func (c *Client) GetInstanceCreateStock(args *api.CreateInstanceStockArgs) (*api.InstanceStockResult, error) { + return api.GetInstanceCreateStock(c, args) +} + +func (c *Client) GetInstanceResizeStock(args *api.ResizeInstanceStockArgs) (*api.InstanceStockResult, error) { + return api.GetInstanceResizeStock(c, args) +} + +// BatchCreateAutoRenewRules - Batch Create AutoRenew Rules +// +// PARAMS: +// - args: the arguments to batch create autorenew rules +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchCreateAutoRenewRules(args *api.BccCreateAutoRenewArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.BatchCreateAutoRenewRules(c, body) +} + +// BatchDeleteAutoRenewRules - Batch Delete AutoRenew Rules +// +// PARAMS: +// - args: the arguments to batch delete autorenew rules +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDeleteAutoRenewRules(args *api.BccDeleteAutoRenewArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.BatchDeleteAutoRenewRules(c, body) +} + +func (c *Client) DeleteInstanceIngorePayment(args *api.DeleteInstanceIngorePaymentArgs) (*api.DeleteInstanceResult, error) { + return api.DeleteInstanceIngorePayment(c, args) +} + +// DeleteRecycledInstance - delete a recycled instance +// +// PARAMS: +// - instanceId: the specific instance ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecycledInstance(instanceId string) error { + return api.DeleteRecycledInstance(c, instanceId) +} + +func (c *Client) ListInstanceByInstanceIds(args *api.ListInstanceByInstanceIdArgs) (*api.ListInstancesResult, error) { + return api.ListInstanceByInstanceIds(c, args) +} + +// BatchDeleteInstanceWithRelateResource - batch delete instance and all eip/cds relate it +// +// PARAMS: +// - args: the arguments to batch delete instance and its relate resource +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDeleteInstanceWithRelateResource(args *api.BatchDeleteInstanceWithRelateResourceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BatchDeleteInstanceWithRelatedResource(c, body) +} + +// BatchStartInstance - batch start instance +// +// PARAMS: +// - args: the arguments to batch start instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchStartInstance(args *api.BatchStartInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BatchStartInstance(c, body) +} + +// BatchStopInstance - batch stop instance +// +// PARAMS: +// - args: the arguments to batch stop instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchStopInstance(args *api.BatchStopInstanceArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.BatchStopInstance(c, body) +} + +// ListInstanceTypes - list all instance type with the specific parameters +// +// PARAMS: +// - args: the arguments to list all instance type +// +// RETURNS: +// - *api.ListInstanceTypeResults: the result of list Instance type +// - error: nil if success otherwise the specific error +func (c *Client) ListInstanceTypes(args *api.ListInstanceTypeArgs) (*api.ListInstanceTypeResults, error) { + return api.ListInstanceTypes(c, args) +} + +// ListIdMappings - Long and short ID conversion parameters +// +// PARAMS: +// - args: the arguments to Long and short ID conversion +// +// RETURNS: +// - *api.ListIdMappingResults: result of the Long short mapping +// - error: nil if success otherwise the specific error +func (c *Client) ListIdMappings(args *api.ListIdMappingArgs) (*api.ListIdMappingResults, error) { + return api.ListIdMappings(c, args) +} + +// BatchResizeInstance - batch resize a specific instance +// +// PARAMS: +// - args: the arguments to resize a specific instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchResizeInstance(args *api.BatchResizeInstanceArgs) (*api.BatchResizeInstanceResults, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.BatchResizeInstance(c, body) +} + +// DeleteSecurityGroupRule - delete a security group rule +// +// PARAMS: +// - securityGroupRuleId: the id of the specific security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSecurityGroupRule(args *api.DeleteSecurityGroupRuleArgs) error { + return api.DeleteSecurityGroupRule(c, args) +} + +// UpdateSecurityGroupRule - update security group rule with the specific parameters +// +// PARAMS: +// - args: the arguments to update the specific security group rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSecurityGroupRule(args *api.UpdateSecurityGroupRuleArgs) error { + return api.UpdateSecurityGroupRule(c, args) +} + +func (c *Client) GetInstanceDeleteProgress(args *api.GetInstanceDeleteProgressArgs) (map[string]interface{}, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.GetInstanceDeleteProgress(c, body) +} +func (c *Client) TagVolume(volumeId string, args *api.TagVolumeArgs) error { + return api.TagVolume(c, volumeId, args) +} + +func (c *Client) UntagVolume(volumeId string, args *api.TagVolumeArgs) error { + return api.UntagVolume(c, volumeId, args) +} + +func (c *Client) TagSnapshotChain(chainId string, args *api.TagVolumeArgs) error { + return api.TagSnapshotChain(c, chainId, args) +} + +func (c *Client) UntagSnapshotChain(chainId string, args *api.TagVolumeArgs) error { + return api.UntagSnapshotChain(c, chainId, args) +} + +func (c *Client) ListAvailableResizeSpecs(args *api.ListAvailableResizeSpecsArgs) ( + *api.ListAvailableResizeSpecResults, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.ListAvailableResizeSpecs(c, body) +} + +func (c *Client) BatchChangeInstanceToPrepay(args *api.BatchChangeInstanceToPrepayArgs) ( + *api.BatchChangeInstanceBillingMethodResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.BatchChangeInstanceToPrepay(c, body) + +} + +func (c *Client) BatchChangeInstanceToPostpay(args *api.BatchChangeInstanceToPostpayArgs) ( + *api.BatchChangeInstanceBillingMethodResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.BatchChangeInstanceToPostpay(c, body) +} + +func (c *Client) ListInstanceRoles() (*api.ListInstanceRolesResult, error) { + + return api.ListInstanceRoles(c) +} + +func (c *Client) BindInstanceRole(args *api.BindInstanceRoleArgs) (*api.BindInstanceRoleResult, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.BindInstanceRole(c, body) +} + +func (c *Client) UnBindInstanceRole(args *api.UnBindInstanceRoleArgs) (*api.UnBindInstanceRoleResult, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.UnBindInstanceRole(c, body) +} + +func (c *Client) DeleteIpv6(args *api.DeleteIpv6Args) error { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.DeleteIpv6(c, body) +} + +func (c *Client) AddIpv6(args *api.AddIpv6Args) (*api.AddIpv6Result, error) { + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + return api.AddIpv6(c, body) +} + +func (c *Client) BindImageToTags(imageId string, args *api.BindTagsRequest) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.BindImageToTags(c, imageId, body) +} + +func (c *Client) UnBindImageToTags(imageId string, args *api.UnBindTagsRequest) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + return api.UnBindImageToTags(c, imageId, body) +} + +func (c *Client) CreateRemoteCopySnapshot(snapshotId string, args *api.RemoteCopySnapshotArgs) (*api.RemoteCopySnapshotResult, + error) { + + return api.CreateRemoteCopySnapshot(c, snapshotId, args) +} + +func (c *Client) ImportCustomImage(args *api.ImportCustomImageArgs) (*api.ImportCustomImageResult, + error) { + + return api.ImportCustomImage(c, args) +} + +func (c *Client) GetAvailableImagesBySpec(args *api.GetAvailableImagesBySpecArg) (*api.GetAvailableImagesBySpecResult, error) { + + return api.GetAvailableImagesBySpec(c, args) +} diff --git a/bce-sdk-go/services/bcc/client_test.go b/bce-sdk-go/services/bcc/client_test.go new file mode 100644 index 0000000..1add200 --- /dev/null +++ b/bce-sdk-go/services/bcc/client_test.go @@ -0,0 +1,1760 @@ +package bcc + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/model" + "github.com/baidubce/bce-sdk-go/services/bcc/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BCC_CLIENT *Client + BCC_TestCdsId string + BCC_TestBccId string + BCC_TestSecurityGroupId string + BCC_TestImageId string + BCC_TestSnapshotId string + BCC_TestAspId string + BCC_TestDeploySetId string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + BCC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) + // log.SetLogLevel(log.DEBUG) + BCC_TestBccId = "i-7pxLG046" + BCC_TestCdsId = "cds_id" + BCC_TestImageId = "m-Q0ezqMIa" +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestCreateInstance(t *testing.T) { + InternalIps := []string{"ip"} + DeploySetIds := []string{"DeploySetId1", "DeploySetId2"} + createInstanceArgs := &api.CreateInstanceArgs{ + ImageId: "m-aCVG7Jxt", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + InstanceType: api.InstanceTypeN5, + CpuCount: 1, + MemoryCapacityInGB: 1, + RootDiskSizeInGb: 40, + RootDiskStorageType: api.StorageTypeEnhancedPl1, + ZoneName: "ZoneName", + SubnetId: "SubnetId", + SecurityGroupId: "SecurityGroupId", + RelationTag: true, + PurchaseCount: 1, + Name: "sdkTest", + KeypairId: "KeypairId", + InternalIps: InternalIps, + DeployIdList: DeploySetIds, + } + createResult, err := BCC_CLIENT.CreateInstance(createInstanceArgs) + ExpectEqual(t.Errorf, err, nil) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateInstanceV2(t *testing.T) { + InternalIps := []string{"ip"} + DeploySetIds := []string{"DeploySetId1", "DeploySetId2"} + RelationTag := true + IsOpenHostEye := true + argsV2 := &api.CreateInstanceArgsV2{ + ClientToken: "clientToken", + RequestToken: "requestToken", + ImageId: "m-aCVG7Jxt", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + InstanceType: api.InstanceTypeN5, + CpuCount: 1, + MemoryCapacityInGB: 1, + RootDiskSizeInGb: 40, + RootDiskStorageType: api.StorageTypeEnhancedPl1, + ZoneName: "ZoneName", + SubnetId: "SubnetId", + SecurityGroupId: "SecurityGroupId", + RelationTag: &RelationTag, + PurchaseCount: 1, + Name: "sdkTest", + KeypairId: "KeypairId", + InternalIps: InternalIps, + DeployIdList: DeploySetIds, + IsOpenHostEye: &IsOpenHostEye, + } + + createResult, err := BCC_CLIENT.CreateInstanceV2(argsV2) + ExpectEqual(t.Errorf, err, nil) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateSpecialInstanceBySpec(t *testing.T) { + createInstanceBySpecArgs := &api.CreateSpecialInstanceBySpecArgs{ + ImageId: "ImageId", + Spec: "bcc.g5.c1m4", + ZoneName: "cn-bj-a", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + + LabelConstraints: []api.LabelConstraint{{ + Key: "feaA", + Operator: api.LabelOperatorExist, + }, { + Key: "feaB", + Value: "typeB", + Operator: api.LabelOperatorNotEqual, + }}, + } + // 将使用『有 feaA 这个 label』且『feaB 这个 label 的值不是 typeB』的测试机创建实例 + createResult, err := BCC_CLIENT.CreateInstanceByLabel(createInstanceBySpecArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(createResult) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateInstanceBySpec(t *testing.T) { + DeploySetIds := []string{"DeploySetId"} + createInstanceBySpecArgs := &api.CreateInstanceBySpecArgs{ + ImageId: "ImageId", + Spec: "bcc.l3.c16m64", + Name: "sdkTest2", + AdminPass: "123qaz!@#", + ZoneName: "cn-bj-a", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + DeployIdList: DeploySetIds, + } + createResult, err := BCC_CLIENT.CreateInstanceBySpec(createInstanceBySpecArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(createResult) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateInstanceBySpecV2(t *testing.T) { + DeploySetIds := []string{"DeploySetId"} + EnableHt := true + createInstanceBySpecArgs := &api.CreateInstanceBySpecArgsV2{ + ImageId: "ImageId", + Spec: "bcc.l3.c16m64", + Name: "sdkTest2", + AdminPass: "123qaz!@#", + ZoneName: "cn-bj-a", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + DeployIdList: DeploySetIds, + EnableHt: &EnableHt, + } + createResult, err := BCC_CLIENT.CreateInstanceBySpecV2(createInstanceBySpecArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(createResult) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateInstanceV3(t *testing.T) { + createInstanceV3Args := &api.CreateInstanceV3Args{ + InstanceSpec: "bcc.l1.c1m1", + SystemVolume: api.SystemVolume{ + StorageType: api.StorageTypeV3CloudSSDGeneral, + VolumeSize: 20, + }, + DataVolumes: []api.DataVolume{ + { + StorageType: api.StorageTypeV3LocalSSD, + VolumeSize: 460, + }, + }, + PurchaseCount: 1, + InstanceName: "sdkTest8", + Password: "123qaz!@#", + ZoneName: "cn-bj-b", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + }, + }, + AssociatedResourceTag: true, + Tags: []model.TagModel{ + { + TagKey: "v3", + TagValue: "1", + }, + }, + AutoRenewTime: 12, + CdsAutoRenew: true, + // 私有网络子网 IP 数组,当前仅支持批量创建多台实例时支持传入相同子网的多个 IP。 + PrivateIpAddresses: []string{ + "ip", + }, + DeployIdList: []string{ + // "dset-PAAeNoJt", + "DeployId", + }, + ImageId: "ImageId", + InternetAccessible: api.InternetAccessible{ + InternetMaxBandwidthOut: 5, + InternetChargeType: api.TrafficPostpaidByHour, + }, + InstanceMarketOptions: api.InstanceMarketOptions{ + SpotOption: "custom", + SpotPrice: "10", + }, + } + createResult, err := BCC_CLIENT.CreateInstanceV3(createInstanceV3Args) + ExpectEqual(t.Errorf, err, nil) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCreateSecurityGroup(t *testing.T) { + args := &api.CreateSecurityGroupArgs{ + Name: "testSecurityGroup", + VpcId: "vpc-uiudcexceb7y", + Desc: "vpc1 sdk test create security group", + Rules: []api.SecurityGroupRuleModel{ + { + Remark: "备注", + Protocol: "tcp", + PortRange: "1-65535", + Direction: "ingress", + SourceIp: "", + SourceGroupId: "", + }, + }, + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, + } + result, err := BCC_CLIENT.CreateSecurityGroup(args) + ExpectEqual(t.Errorf, err, nil) + BCC_TestSecurityGroupId = result.SecurityGroupId +} + +func TestCreateImage(t *testing.T) { + args := &api.CreateImageArgs{ + ImageName: "testImageName", + InstanceId: BCC_TestBccId, + } + result, err := BCC_CLIENT.CreateImage(args) + ExpectEqual(t.Errorf, err, nil) + BCC_TestImageId = result.ImageId +} + +func TestListInstances(t *testing.T) { + listArgs := &api.ListInstanceArgs{ + ZoneName: "cn-bj-a", + } + res, err := BCC_CLIENT.ListInstances(listArgs) + ExpectEqual(t.Errorf, err, nil) + // fmt.Println(res.Instances[0].NetEthQueueCount) + fmt.Println(res) +} + +func TestListRecycleInstances(t *testing.T) { + listArgs := &api.ListRecycleInstanceArgs{} + res, err := BCC_CLIENT.ListRecycleInstances(listArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestGetInstanceDetail(t *testing.T) { + res, err := BCC_CLIENT.GetInstanceDetail("i-aFW4mJkZ") + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res.Instance.NetEthQueueCount) + fmt.Println(res) +} + +func TestGetInstanceDetailWithDeploySetAndFailed(t *testing.T) { + res, err := BCC_CLIENT.GetInstanceDetailWithDeploySetAndFailed("InstanceId", false, true) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestResizeInstance(t *testing.T) { + resizeArgs := &api.ResizeInstanceArgs{ + CpuCount: 2, + MemoryCapacityInGB: 4, + } + err := BCC_CLIENT.ResizeInstance(BCC_TestBccId, resizeArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestLiveResizeInstance(t *testing.T) { + resizeArgs := &api.ResizeInstanceArgs{ + CpuCount: 2, + MemoryCapacityInGB: 4, + LiveResize: true, + } + err := BCC_CLIENT.ResizeInstance(BCC_TestBccId, resizeArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestStopInstanceWithNoCharge(t *testing.T) { + err := BCC_CLIENT.StopInstanceWithNoCharge(BCC_TestBccId, true, true) + ExpectEqual(t.Errorf, err, nil) +} + +func TestStopInstance(t *testing.T) { + err := BCC_CLIENT.StopInstance(BCC_TestBccId, true) + ExpectEqual(t.Errorf, err, nil) +} + +func TestStartInstance(t *testing.T) { + err := BCC_CLIENT.StartInstance(BCC_TestBccId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRebootInstance(t *testing.T) { + err := BCC_CLIENT.RebootInstance(BCC_TestBccId, true) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRebuildInstance(t *testing.T) { + rebuildArgs := &api.RebuildInstanceArgs{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + } + err := BCC_CLIENT.RebuildInstance(BCC_TestBccId, rebuildArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRebuildInstanceV2(t *testing.T) { + rebuildArgs := &api.RebuildInstanceArgsV2{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + } + err := BCC_CLIENT.RebuildInstanceV2(BCC_TestBccId, rebuildArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestChangeInstancePass(t *testing.T) { + changeArgs := &api.ChangeInstancePassArgs{ + AdminPass: "321zaq#@!", + } + err := BCC_CLIENT.ChangeInstancePass(BCC_TestBccId, changeArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstanceAttribute(t *testing.T) { + modifyArgs := &api.ModifyInstanceAttributeArgs{ + Name: "test-modify", + NetEthQueueCount: "3", + } + err := BCC_CLIENT.ModifyInstanceAttribute(BCC_TestBccId, modifyArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstanceDesc(t *testing.T) { + modifyArgs := &api.ModifyInstanceDescArgs{ + Description: "test modify", + } + err := BCC_CLIENT.ModifyInstanceDesc(BCC_TestBccId, modifyArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyInstanceHostname(t *testing.T) { + modifyArgs := &api.ModifyInstanceHostnameArgs{ + Hostname: "test-modify-domain", + IsOpenHostnameDomain: false, + Reboot: true, + } + err := BCC_CLIENT.ModifyInstanceHostname(BCC_TestBccId, modifyArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetInstanceVNC(t *testing.T) { + _, err := BCC_CLIENT.GetInstanceVNC(BCC_TestBccId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchAddIp(t *testing.T) { + privateIps := []string{"privateIp"} + batchAddIpArgs := &api.BatchAddIpArgs{ + InstanceId: BCC_TestBccId, + PrivateIps: privateIps, + SecondaryPrivateIpAddressCount: 1, + } + result, err := BCC_CLIENT.BatchAddIP(batchAddIpArgs) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchDelIp(t *testing.T) { + privateIps := []string{"privateIp"} + batchDelIpArgs := &api.BatchDelIpArgs{ + InstanceId: BCC_TestBccId, + PrivateIps: privateIps, + } + err := BCC_CLIENT.BatchDelIP(batchDelIpArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBindSecurityGroup(t *testing.T) { + err := BCC_CLIENT.BindSecurityGroup(BCC_TestBccId, BCC_TestSecurityGroupId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnBindSecurityGroup(t *testing.T) { + err := BCC_CLIENT.UnBindSecurityGroup(BCC_TestBccId, BCC_TestSecurityGroupId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateCDSVolume(t *testing.T) { + args := &api.CreateCDSVolumeArgs{ + PurchaseCount: 1, + CdsSizeInGB: 40, + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "MONTH", + }, + }, + AutoSnapshotPolicy: []api.AutoSnapshotPolicy{ + { + AutoSnapshotPolicyId: "Test_AutoSnapshotPolicyId", + }, + }, + RenewTimeUnit: "month", + RenewTime: 2, + } + + result, _ := BCC_CLIENT.CreateCDSVolume(args) + BCC_TestCdsId = result.VolumeIds[0] + fmt.Print(BCC_TestCdsId) +} + +func TestCreateCDSVolumeV3(t *testing.T) { + args := &api.CreateCDSVolumeV3Args{ + ZoneName: "cn-bj-a", + VolumeName: "volumeV3Test", + Description: "v3 test", + PurchaseCount: 1, + VolumeSize: 50, + StorageType: api.StorageTypeV3CloudSSDGeneral, + Billing: &api.Billing{ + PaymentTiming: api.PaymentTimingPostPaid, + }, + } + + result, err := BCC_CLIENT.CreateCDSVolumeV3(args) + fmt.Println(result) + ExpectEqual(t.Errorf, err, nil) + BCC_TestCdsId = result.VolumeIds[0] +} + +func TestGetBidInstancePrice(t *testing.T) { + args := &api.GetBidInstancePriceArgs{ + InstanceType: api.InstanceTypeN1, + CpuCount: 1, + MemoryCapacityInGB: 2, + RootDiskSizeInGb: 45, + RootDiskStorageType: api.StorageTypeCloudHP1, + PurchaseCount: 1, + } + result, err := BCC_CLIENT.GetBidInstancePrice(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestListBidFlavor(t *testing.T) { + result, err := BCC_CLIENT.ListBidFlavor() + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestCreateSnapshot(t *testing.T) { + args := &api.CreateSnapshotArgs{ + VolumeId: BCC_TestCdsId, + SnapshotName: "testSnapshotName", + Tags: []model.TagModel{ + { + TagKey: "test", + TagValue: "val", + }, + }, + } + result, err := BCC_CLIENT.CreateSnapshot(args) + ExpectEqual(t.Errorf, err, nil) + + BCC_TestSnapshotId = result.SnapshotId +} + +func TestListSnapshot(t *testing.T) { + args := &api.ListSnapshotArgs{} + _, err := BCC_CLIENT.ListSnapshot(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListSnapshotChain(t *testing.T) { + args := &api.ListSnapshotChainArgs{ + OrderBy: "chainId", + Order: "asc", + PageSize: 2, + PageNo: 2, + } + res, err := BCC_CLIENT.ListSnapshotChain(args) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetSnapshotDetail(t *testing.T) { + _, err := BCC_CLIENT.GetSnapshotDetail(BCC_TestSnapshotId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteSnapshot(t *testing.T) { + err := BCC_CLIENT.DeleteSnapshot(BCC_TestSnapshotId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateAutoSnapshotPolicy(t *testing.T) { + args := &api.CreateASPArgs{ + Name: "testAspName", + TimePoints: []string{"20"}, + RepeatWeekdays: []string{"1", "5"}, + RetentionDays: "7", + } + result, err := BCC_CLIENT.CreateAutoSnapshotPolicy(args) + ExpectEqual(t.Errorf, err, nil) + BCC_TestAspId = result.AspId +} + +func TestAttachAutoSnapshotPolicy(t *testing.T) { + args := &api.AttachASPArgs{ + VolumeIds: []string{BCC_TestCdsId}, + } + err := BCC_CLIENT.AttachAutoSnapshotPolicy(BCC_TestAspId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDetachAutoSnapshotPolicy(t *testing.T) { + args := &api.DetachASPArgs{ + VolumeIds: []string{BCC_TestCdsId}, + } + err := BCC_CLIENT.DetachAutoSnapshotPolicy(BCC_TestAspId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUpdateAutoSnapshotPolicy(t *testing.T) { + args := &api.UpdateASPArgs{ + AspId: "AspId", + Name: "Name", + TimePoints: []string{"21"}, + RepeatWeekdays: []string{"2"}, + } + err := BCC_CLIENT.UpdateAutoSnapshotPolicy(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListAutoSnapshotPolicy(t *testing.T) { + args := &api.ListASPArgs{} + _, err := BCC_CLIENT.ListAutoSnapshotPolicy(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetAutoSnapshotPolicy(t *testing.T) { + _, err := BCC_CLIENT.GetAutoSnapshotPolicy(BCC_TestAspId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteAutoSnapshotPolicy(t *testing.T) { + err := BCC_CLIENT.DeleteAutoSnapshotPolicy(BCC_TestAspId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListCDSVolume(t *testing.T) { + queryArgs := &api.ListCDSVolumeArgs{ + InstanceId: BCC_TestBccId, + } + res, err := BCC_CLIENT.ListCDSVolume(queryArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestListCDSVolumeV3(t *testing.T) { + queryArgs := &api.ListCDSVolumeArgs{ + InstanceId: BCC_TestBccId, + } + res, err := BCC_CLIENT.ListCDSVolumeV3(queryArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetCDSVolumeDetail(t *testing.T) { + res, err := BCC_CLIENT.GetCDSVolumeDetail(BCC_TestCdsId) + fmt.Println(res.Volume) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetCDSVolumeDetailV3(t *testing.T) { + res, err := BCC_CLIENT.GetCDSVolumeDetailV3(BCC_TestCdsId) + fmt.Println(res.Volume) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAttachCDSVolume(t *testing.T) { + args := &api.AttachVolumeArgs{ + InstanceId: BCC_TestBccId, + } + + _, err := BCC_CLIENT.AttachCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDetachCDSVolume(t *testing.T) { + args := &api.DetachVolumeArgs{ + InstanceId: BCC_TestBccId, + } + + err := BCC_CLIENT.DetachCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestResizeCDSVolume(t *testing.T) { + args := &api.ResizeCSDVolumeArgs{ + NewCdsSizeInGB: 100, + } + + err := BCC_CLIENT.ResizeCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRollbackCDSVolume(t *testing.T) { + args := &api.RollbackCSDVolumeArgs{ + SnapshotId: "SnapshotId", + } + + err := BCC_CLIENT.RollbackCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestPurchaseReservedCDSVolume(t *testing.T) { + args := &api.PurchaseReservedCSDVolumeArgs{ + Billing: &api.Billing{ + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "Month", + }, + }, + } + + err := BCC_CLIENT.PurchaseReservedCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRenameCDSVolume(t *testing.T) { + args := &api.RenameCSDVolumeArgs{ + Name: "testRenamedName", + } + + err := BCC_CLIENT.RenameCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyCDSVolume(t *testing.T) { + args := &api.ModifyCSDVolumeArgs{ + CdsName: "modifiedName", + Desc: "modifiedDesc", + } + + err := BCC_CLIENT.ModifyCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyChargeTypeCDSVolume(t *testing.T) { + args := &api.ModifyChargeTypeCSDVolumeArgs{ + Billing: &api.Billing{ + Reservation: &api.Reservation{ + ReservationLength: 1, + }, + }, + } + + err := BCC_CLIENT.ModifyChargeTypeCDSVolume(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteCDSVolumeNew(t *testing.T) { + args := &api.DeleteCDSVolumeArgs{ + AutoSnapshot: "on", + Recycle: "on", + } + + err := BCC_CLIENT.DeleteCDSVolumeNew(BCC_TestCdsId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteCDSVolume(t *testing.T) { + err := BCC_CLIENT.DeleteCDSVolume(BCC_TestCdsId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAutoRenewCDSVolume(t *testing.T) { + args := &api.AutoRenewCDSVolumeArgs{ + VolumeId: "VolumeId", + RenewTimeUnit: "month", + RenewTime: 2, + } + + err := BCC_CLIENT.AutoRenewCDSVolume(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCancelAutoRenewCDSVolume(t *testing.T) { + args := &api.CancelAutoRenewCDSVolumeArgs{ + VolumeId: "VolumeId", + } + + err := BCC_CLIENT.CancelAutoRenewCDSVolume(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListSecurityGroup(t *testing.T) { + queryArgs := &api.ListSecurityGroupArgs{ + VpcId: "vpc-uiudcexceb7y", + } + result, err := BCC_CLIENT.ListSecurityGroup(queryArgs) + r, _ := json.Marshal(result) + fmt.Println(string(r)) + ExpectEqual(t.Errorf, err, nil) +} + +func TestInstanceChangeVpc(t *testing.T) { + args := &api.InstanceChangeVpcArgs{ + InstanceId: "InstanceId", + SubnetId: "SubnetId", + Reboot: false, + SecurityGroupIds: []string{"SecurityGroupId"}, + EnterpriseSecurityGroupIds: []string{"EnterpriseSecurityGroupIds"}, + } + + err := BCC_CLIENT.InstanceChangeVpc(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestInstanceChangeSubnet(t *testing.T) { + args := &api.InstanceChangeSubnetArgs{ + InstanceId: "i-YRMFRB6Z", + SubnetId: "sbn-z1y9tcedqnh6", + InternalIp: "192.168.5.12", + Reboot: false, + } + + err := BCC_CLIENT.InstanceChangeSubnet(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAuthorizeSecurityGroupRule(t *testing.T) { + args := &api.AuthorizeSecurityGroupArgs{ + Rule: &api.SecurityGroupRuleModel{ + Remark: "备注", + Protocol: "udp", + PortRange: "1-65535", + Direction: "ingress", + SourceIp: "", + SourceGroupId: "", + }, + } + err := BCC_CLIENT.AuthorizeSecurityGroupRule(BCC_TestSecurityGroupId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRevokeSecurityGroupRule(t *testing.T) { + args := &api.RevokeSecurityGroupArgs{ + Rule: &api.SecurityGroupRuleModel{ + Remark: "备注", + Protocol: "udp", + PortRange: "1-65535", + Direction: "ingress", + SourceIp: "", + SourceGroupId: "", + }, + } + err := BCC_CLIENT.RevokeSecurityGroupRule(BCC_TestSecurityGroupId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteSecurityGroupRule(t *testing.T) { + err := BCC_CLIENT.DeleteSecurityGroup(BCC_TestSecurityGroupId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListImage(t *testing.T) { + queryArgs := &api.ListImageArgs{} + _, err := BCC_CLIENT.ListImage(queryArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetImageDetail(t *testing.T) { + _, err := BCC_CLIENT.GetImageDetail(BCC_TestImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRemoteCopyImage(t *testing.T) { + args := &api.RemoteCopyImageArgs{ + Name: "testRemoteCopy", + DestRegion: []string{"hkg"}, + } + err := BCC_CLIENT.RemoteCopyImage(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRemoteCopyImageReturnImageIds(t *testing.T) { + args := &api.RemoteCopyImageArgs{ + Name: "Copy", + DestRegion: []string{"hkg"}, + } + result, err := BCC_CLIENT.RemoteCopyImageReturnImageIds(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestCancelRemoteCopyImage(t *testing.T) { + err := BCC_CLIENT.CancelRemoteCopyImage(BCC_TestImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestShareImage(t *testing.T) { + args := &api.SharedUser{ + AccountId: "id", + } + err := BCC_CLIENT.ShareImage(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnShareImage(t *testing.T) { + args := &api.SharedUser{ + AccountId: "id", + } + err := BCC_CLIENT.UnShareImage(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetImageSharedUser(t *testing.T) { + _, err := BCC_CLIENT.GetImageSharedUser(BCC_TestImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetImageOS(t *testing.T) { + args := &api.GetImageOsArgs{} + _, err := BCC_CLIENT.GetImageOS(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteImage(t *testing.T) { + err := BCC_CLIENT.DeleteImage(BCC_TestImageId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteInstance(t *testing.T) { + err := BCC_CLIENT.DeleteInstance(BCC_TestBccId) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteInstanceWithRelateResource(t *testing.T) { + args := &api.DeleteInstanceWithRelateResourceArgs{ + BccRecycleFlag: true, + DeleteImmediate: true, + } + + err := BCC_CLIENT.DeleteInstanceWithRelateResource(BCC_TestBccId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListSpec(t *testing.T) { + _, err := BCC_CLIENT.ListSpec() + ExpectEqual(t.Errorf, err, nil) +} + +func TestListZone(t *testing.T) { + _, err := BCC_CLIENT.ListZone() + ExpectEqual(t.Errorf, err, nil) +} + +func TestListFlavorSpec(t *testing.T) { + args := &api.ListFlavorSpecArgs{} + res, err := BCC_CLIENT.ListFlavorSpec(args) + ExpectEqual(t.Errorf, err, nil) + //fmt.Println(res.ZoneResources[0].BccResources.FlavorGroups[0].Flavors[0].NetEthQueueCount) + //fmt.Println(res.ZoneResources[0].BccResources.FlavorGroups[0].Flavors[0].NetEthMaxQueueCount) + fmt.Println(res) +} + +func TestGetPriceBySpec(t *testing.T) { + args := &api.GetPriceBySpecArgs{ + SpecId: "g1", + Spec: "", + PaymentTiming: "Postpaid", + ZoneName: "cn-gz-b", + PurchaseCount: 1, + PurchaseLength: 1, + } + res, err := BCC_CLIENT.GetPriceBySpec(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestCreateDeploySet(t *testing.T) { + testDeploySetName := "testName" + testDeployDesc := "testDesc" + testStrategy := "HOST_HA" + queryArgs := &api.CreateDeploySetArgs{ + Strategy: testStrategy, + Name: testDeploySetName, + Desc: testDeployDesc, + Concurrency: 5, + } + rep, err := BCC_CLIENT.CreateDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListDeploySets(t *testing.T) { + rep, err := BCC_CLIENT.ListDeploySets() + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyDeploySet(t *testing.T) { + testDeploySetName := "testName" + testDeployDesc := "goDesc" + queryArgs := &api.ModifyDeploySetArgs{ + Name: testDeploySetName, + Desc: testDeployDesc, + } + BCC_TestDeploySetId = "DeploySetId" + rep, err := BCC_CLIENT.ModifyDeploySet(BCC_TestDeploySetId, queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteDeploySet(t *testing.T) { + testDeleteDeploySetId := "DeploySetId" + err := BCC_CLIENT.DeleteDeploySet(testDeleteDeploySetId) + fmt.Println(err) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetDeploySet(t *testing.T) { + testDeploySetID := "DeploySetId" + rep, err := BCC_CLIENT.GetDeploySet(testDeploySetID) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUpdateInstanceDeploySet(t *testing.T) { + queryArgs := &api.UpdateInstanceDeployArgs{ + InstanceId: "InstanceId", + DeploySetIds: []string{"DeploySetId"}, + } + rep, err := BCC_CLIENT.UpdateInstanceDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDelInstanceDeploySet(t *testing.T) { + queryArgs := &api.DelInstanceDeployArgs{ + DeploySetId: "DeploySetId", + InstanceIds: []string{"InstanceId"}, + } + rep, err := BCC_CLIENT.DelInstanceDeploySet(queryArgs) + fmt.Println(rep) + ExpectEqual(t.Errorf, err, nil) +} + +func TestResizeInstanceBySpec(t *testing.T) { + resizeArgs := &api.ResizeInstanceArgs{ + Spec: "Spec", + } + err := BCC_CLIENT.ResizeInstanceBySpec(BCC_TestBccId, resizeArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchRebuildInstances(t *testing.T) { + rebuildArgs := &api.RebuildBatchInstanceArgs{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + InstanceIds: []string{BCC_TestBccId}, + } + err := BCC_CLIENT.BatchRebuildInstances(rebuildArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchRebuildInstancesV2(t *testing.T) { + rebuildArgs := &api.RebuildBatchInstanceArgsV2{ + ImageId: "ImageId", + AdminPass: "123qaz!@#", + InstanceIds: []string{BCC_TestBccId}, + } + err := BCC_CLIENT.BatchRebuildInstancesV2(rebuildArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestChangeToPrepaid(t *testing.T) { + args := &api.ChangeToPrepaidRequest{ + Duration: 1, + RelationCds: true, + } + _, err := BCC_CLIENT.ChangeToPrepaid(BCC_TestBccId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBindInstanceToTags(t *testing.T) { + args := &api.BindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, + } + err := BCC_CLIENT.BindInstanceToTags(BCC_TestBccId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnBindInstanceToTags(t *testing.T) { + args := &api.UnBindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, + } + err := BCC_CLIENT.UnBindInstanceToTags(BCC_TestBccId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetInstanceNoChargeList(t *testing.T) { + listArgs := &api.ListInstanceArgs{} + _, err := BCC_CLIENT.GetInstanceNoChargeList(listArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateBidInstance(t *testing.T) { + createInstanceArgs := &api.CreateInstanceArgs{ + ImageId: "ImageId", + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingBidding, + }, + InstanceType: api.InstanceTypeN3, + CpuCount: 1, + MemoryCapacityInGB: 4, + RootDiskSizeInGb: 40, + RootDiskStorageType: api.StorageTypeHP1, + ZoneName: "zoneName", + SubnetId: "SubnetId", + SecurityGroupId: "SecurityGroupId", + RelationTag: true, + PurchaseCount: 1, + Name: "sdkTest", + BidModel: "BidModel", + BidPrice: "BidPrice", + } + createResult, err := BCC_CLIENT.CreateBidInstance(createInstanceArgs) + ExpectEqual(t.Errorf, err, nil) + BCC_TestBccId = createResult.InstanceIds[0] +} + +func TestCancelBidOrder(t *testing.T) { + createInstanceArgs := &api.CancelBidOrderRequest{ + OrderId: "OrderId", + } + _, err := BCC_CLIENT.CancelBidOrder(createInstanceArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestInstancePurchaseReserved(t *testing.T) { + purchaseReservedArgs := &api.PurchaseReservedArgs{ + Billing: api.Billing{ + PaymentTiming: api.PaymentTimingPrePaid, + Reservation: &api.Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "MONTH", + }, + }, + RelatedRenewFlag: "", + } + _, err := BCC_CLIENT.InstancePurchaseReserved(BCC_TestBccId, purchaseReservedArgs) + // fmt.Print(err) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetAvailableDiskInfo(t *testing.T) { + zoneName := "cn-bj-a" + if res, err := BCC_CLIENT.GetAvailableDiskInfo(zoneName); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) + } else { + fmt.Println("Get the specific zone flavor success, result: ", res) + } +} + +func TestListTypeZones(t *testing.T) { + args := &api.ListTypeZonesArgs{ + InstanceType: "", + ProductType: "", + Spec: "bcc.g3.c2m12", + SpecId: "", + } + if res, err := BCC_CLIENT.ListTypeZones(args); err != nil { + fmt.Println("Get the specific zone flavor failed: ", err) + } else { + fmt.Println("Get the specific zone flavor success, result: ", res) + } +} + +func TestListInstanceEnis(t *testing.T) { + instanceId := "InstanceId" + if res, err := BCC_CLIENT.ListInstanceEnis(instanceId); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestCreateKeypair(t *testing.T) { + args := &api.CreateKeypairArgs{ + Name: "gosdk", + Description: "go sdk test", + } + if res, err := BCC_CLIENT.CreateKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestImportKeypair(t *testing.T) { + args := &api.ImportKeypairArgs{ + ClientToken: "", + Name: "goImport", + Description: "go sdk test", + PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCNItVsPPOYbMH4W5fyFqoYZwfL2A1G9IWgofhrrNYVmUr22qx42FPcyR6Fj1frHGNUIZ0NN3CzS8wXg/aKWJkYMiZGjlwmppdrNGWUjmPZD9GbHw/w8sVGCBEyyCEVlTZHQe+AgfzOr/yzqpUmCareBIlQDlR1PzX39wDf7ohpzmJy2e+B+amNy2pgsxG9OI9a4RacGLAeD/OTE/nvj027pEwbWbxM1BsJjrMeH51gWGqv8zANJFL2MGqdBaUGH0r4iXTWGZ+TkA1L7np8qWNCwquve2iy8dlHw7OnzA+hsFVZJROjJimzMY+yNNiy3CqzdO+WaBXG9MWUxtLf3ZjF", + } + if res, err := BCC_CLIENT.ImportKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestListKeypairs(t *testing.T) { + args := &api.ListKeypairArgs{ + Marker: "", + MaxKeys: 0, + Name: "ac", + } + if res, err := BCC_CLIENT.ListKeypairs(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success, result: ", res) + } +} + +func TestRenameKeypair(t *testing.T) { + args := &api.RenameKeypairArgs{ + Name: "renameKeypair", + KeypairId: "KeypairId", + } + if err := BCC_CLIENT.RenameKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success") + } +} + +func TestUpdateKeypairDescription(t *testing.T) { + args := &api.KeypairUpdateDescArgs{ + KeypairId: "KeypairId", + Description: "UpdateKeypairDescription test", + } + if err := BCC_CLIENT.UpdateKeypairDescription(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success") + } +} + +func TestGetKeypairDetail(t *testing.T) { + keypairId := "KeypairId" + if resp, err := BCC_CLIENT.GetKeypairDetail(keypairId); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success resp:", resp.Keypair.InstanceCount) + } +} + +func TestAttachKeypair(t *testing.T) { + args := &api.AttackKeypairArgs{ + KeypairId: "KeypairId", + InstanceIds: []string{"InstanceId"}, + } + if err := BCC_CLIENT.AttachKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success") + } +} + +func TestDetachKeypair(t *testing.T) { + args := &api.DetachKeypairArgs{ + KeypairId: "KeypairId", + InstanceIds: []string{"InstanceId"}, + } + if err := BCC_CLIENT.DetachKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success") + } +} + +func TestDeleteKeypair(t *testing.T) { + args := &api.DeleteKeypairArgs{ + KeypairId: "KeypairId", + } + if err := BCC_CLIENT.DeleteKeypair(args); err != nil { + fmt.Println("Get specific instance eni failed: ", err) + } else { + fmt.Println("Get specific instance eni success") + } +} + +func TestBatchCreateAutoRenewRules(t *testing.T) { + bccAutoRenewArgs := &api.BccCreateAutoRenewArgs{ + InstanceId: BCC_TestBccId, + RenewTimeUnit: "month", + RenewTime: 1, + } + err := BCC_CLIENT.BatchCreateAutoRenewRules(bccAutoRenewArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchDeleteAutoRenewRules(t *testing.T) { + bccAutoRenewArgs := &api.BccDeleteAutoRenewArgs{ + InstanceId: BCC_TestBccId, + } + err := BCC_CLIENT.BatchDeleteAutoRenewRules(bccAutoRenewArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteInstanceIngorePayment(t *testing.T) { + args := &api.DeleteInstanceIngorePaymentArgs{ + InstanceId: "InstanceId", + RelatedReleaseFlag: true, + DeleteRelatedEnisFlag: true, + DeleteCdsSnapshotFlag: true, + } + if res, err := BCC_CLIENT.DeleteInstanceIngorePayment(args); err != nil { + fmt.Println("delete instance failed: ", err) + } else { + fmt.Println("delete instance success, result: ", res) + } +} + +func TestRecoveryInstance(t *testing.T) { + args := &api.RecoveryInstanceArgs{ + InstanceIds: []api.RecoveryInstanceModel{ + { + InstanceId: BCC_TestBccId, + }, + }, + } + if err := BCC_CLIENT.RecoveryInstance(args); err != nil { + fmt.Println("recovery instance failed: ", err) + } else { + fmt.Println("recovery instance success") + } +} + +func TestGetAllStocks(t *testing.T) { + if res, err := BCC_CLIENT.GetAllStocks(); err != nil { + fmt.Println("get all stocks failed: ", err) + } else { + fmt.Println("get all stocks success, result: ", res) + } +} + +func TestGetStockWithDeploySet(t *testing.T) { + args := &api.GetStockWithDeploySetArgs{ + Spec: "bcc.g3.c2m8", + DeploySetIds: []string{"DeploySetId"}, + } + if res, err := BCC_CLIENT.GetStockWithDeploySet(args); err != nil { + fmt.Println("get stock with deploySet failed: ", err) + } else { + fmt.Println("get stock with deploySet, result: ", res) + } +} + +func TestGetStockWithSpec(t *testing.T) { + args := &api.GetStockWithSpecArgs{ + Spec: "bcc.g3.c2m8", + DeploySetIds: []string{"dset-RekVqK7V"}, + } + if res, err := BCC_CLIENT.GetStockWithSpec(args); err != nil { + fmt.Println("get stock with spec failed: ", err) + } else { + fmt.Println("get stock with spec, result: ", res) + } +} + +func TestListInstanceByInstanceIds(t *testing.T) { + args := &api.ListInstanceByInstanceIdArgs{ + InstanceIds: []string{"i-gRYyYyjr", "i-GGc7Buqs"}, + Marker: "", + MaxKeys: 3, + } + result, err := BCC_CLIENT.ListInstanceByInstanceIds(args) + if err != nil { + fmt.Println("list instance failed: ", err) + } else { + fmt.Println("list instance success") + data, e := json.Marshal(result) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("list instance : %s", data) + } +} + +func TestListServersByMarkerV3(t *testing.T) { + args := &api.ListServerRequestV3Args{ + Marker: "", + MaxKeys: 3, + } + result, err := BCC_CLIENT.ListServersByMarkerV3(args) + if err != nil { + fmt.Println("list instance failed: ", err) + } else { + fmt.Println("list instance success") + data, e := json.Marshal(result) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("list instance : %s", data) + } +} + +func TestDeletePrepayVolume(t *testing.T) { + args := &api.VolumePrepayDeleteRequestArgs{ + VolumeId: "v-tVDW1NkK", + RelatedReleaseFlag: false, + } + result, err := BCC_CLIENT.DeletePrepayVolume(args) + if err != nil { + fmt.Println("delete volume failed: ", err) + } else { + fmt.Println("delete volume success") + data, e := json.Marshal(result) + if e != nil { + fmt.Println("json marshal failed!") + return + } + fmt.Printf("delete volume : %s", data) + } +} + +func TestBatchDeleteInstanceWithRelateResource(t *testing.T) { + args := &api.BatchDeleteInstanceWithRelateResourceArgs{ + RelatedReleaseFlag: true, + BccRecycleFlag: true, + InstanceIds: []string{"i-gRYyYyjx", "i-GGc7Buqd"}, + } + + err := BCC_CLIENT.BatchDeleteInstanceWithRelateResource(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchStartInstance(t *testing.T) { + args := &api.BatchStartInstanceArgs{ + InstanceIds: []string{"i-gRYyYyjx", "i-GGc7Buqd"}, + } + err := BCC_CLIENT.BatchStartInstance(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestBatchStopInstance(t *testing.T) { + args := &api.BatchStopInstanceArgs{ + ForceStop: true, + StopWithNoCharge: false, + InstanceIds: []string{"i-gRYyYyjx", "i-GGc7Buqd"}, + } + err := BCC_CLIENT.BatchStopInstance(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListInstanceTypes(t *testing.T) { + listArgs := &api.ListInstanceTypeArgs{ + ZoneName: "cn-bj-a", + } + res, err := BCC_CLIENT.ListInstanceTypes(listArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestListIdMappings(t *testing.T) { + listArgs := &api.ListIdMappingArgs{ + IdType: "shot", + ObjectType: "vm", + InstanceIds: []string{ + "i-wQzV1qYZ", + "i-b1jcrdt5", + }, + } + res, err := BCC_CLIENT.ListIdMappings(listArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestBatchResizeInstance(t *testing.T) { + listArgs := &api.BatchResizeInstanceArgs{ + Spec: "spec", + InstanceIdList: []string{ + "i-wQzV1qYZ", + "i-b1jcrdt5", + }, + } + res, err := BCC_CLIENT.BatchResizeInstance(listArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestClient_DeleteSecurityGroupRule(t *testing.T) { + args := &api.DeleteSecurityGroupRuleArgs{ + SecurityGroupRuleId: "r-zkcrsnesy13b", + } + err := BCC_CLIENT.DeleteSecurityGroupRule(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateSecurityGroupRule(t *testing.T) { + remark := "" + args := &api.UpdateSecurityGroupRuleArgs{ + SecurityGroupRuleId: "r-sdxzpzxe2igh", + Remark: &remark, + } + err := BCC_CLIENT.UpdateSecurityGroupRule(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetInstanceDeleteProgress(t *testing.T) { + args := &api.GetInstanceDeleteProgressArgs{ + InstanceIds: []string{ + BCC_TestBccId, + }, + } + + res, err := BCC_CLIENT.GetInstanceDeleteProgress(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(res) +} + +func TestTagVolume(t *testing.T) { + tagArgs := &api.TagVolumeArgs{ + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, + } + err := BCC_CLIENT.TagVolume(BCC_TestCdsId, tagArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUntagVolume(t *testing.T) { + tagArgs := &api.TagVolumeArgs{ + ChangeTags: []api.Tag{ + { + TagKey: "go-SDK-Tag-Key3", + TagValue: "go_SDK-Tag-Value2", + }, + }, + } + err := BCC_CLIENT.UntagVolume(BCC_TestCdsId, tagArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestTagSnapshotChain(t *testing.T) { + tagArgs := &api.TagVolumeArgs{ + ChangeTags: []api.Tag{ + { + TagKey: "go-k", + TagValue: "go-v", + }, + }, + } + err := BCC_CLIENT.TagSnapshotChain("sl-PdPu6Oel", tagArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUntagSnapshotChain(t *testing.T) { + tagArgs := &api.TagVolumeArgs{ + ChangeTags: []api.Tag{ + { + TagKey: "go-k", + TagValue: "go-v", + }, + }, + } + err := BCC_CLIENT.UntagSnapshotChain("sl-PdPu6Oel", tagArgs) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListAvailableResizeSpecs(t *testing.T) { + listAvailableResizeSpecsArgs := &api.ListAvailableResizeSpecsArgs{ + Spec: "bcc.ic5.c1m1", + Zone: "cn-bj-a", + } + createResult, err := BCC_CLIENT.ListAvailableResizeSpecs(listAvailableResizeSpecsArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(createResult) +} + +func TestBatchChangeInstanceToPrepay(t *testing.T) { + batchChangeInstanceToPrepayArgs := &api.BatchChangeInstanceToPrepayArgs{ + Config: []api.PrepayConfig{ + { + InstanceId: BCC_TestBccId, + Duration: 1, + CdsList: []string{ + BCC_TestCdsId, + }, + }, + }, + } + result, err := BCC_CLIENT.BatchChangeInstanceToPrepay(batchChangeInstanceToPrepayArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestBatchChangeInstanceToPostpay(t *testing.T) { + batchChangeInstanceToPostArgs := &api.BatchChangeInstanceToPostpayArgs{ + Config: []api.PostpayConfig{ + { + InstanceId: BCC_TestBccId, + CdsList: []string{ + BCC_TestCdsId, + }, + }, + }, + } + result, err := BCC_CLIENT.BatchChangeInstanceToPostpay(batchChangeInstanceToPostArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestListInstanceRoles(t *testing.T) { + result, err := BCC_CLIENT.ListInstanceRoles() + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestBindInstanceRole(t *testing.T) { + bindInstanceRoleArgs := &api.BindInstanceRoleArgs{ + RoleName: "Test_BCC", + Instances: []api.Instances{ + { + InstanceId: BCC_TestBccId, + }, + }, + } + + result, err := BCC_CLIENT.BindInstanceRole(bindInstanceRoleArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestUnBindInstanceRole(t *testing.T) { + unbindInstanceRoleArgs := &api.UnBindInstanceRoleArgs{ + RoleName: "Test_BCC", + Instances: []api.Instances{ + { + InstanceId: BCC_TestBccId, + }, + }, + } + + result, err := BCC_CLIENT.UnBindInstanceRole(unbindInstanceRoleArgs) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestDeleteIpv6(t *testing.T) { + deleteIpv6Args := &api.DeleteIpv6Args{ + InstanceId: BCC_TestBccId, + Reboot: true, + } + + err := BCC_CLIENT.DeleteIpv6(deleteIpv6Args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAddIpv6(t *testing.T) { + addIpv6Args := &api.AddIpv6Args{ + InstanceId: BCC_TestBccId, + Reboot: true, + Ipv6Address: "2400:da00:e003:0:41c:4100:0:2", + } + + result, err := BCC_CLIENT.AddIpv6(addIpv6Args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestBindImageToTags(t *testing.T) { + args := &api.BindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, + } + err := BCC_CLIENT.BindImageToTags(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUnBindImageToTags(t *testing.T) { + args := &api.UnBindTagsRequest{ + ChangeTags: []model.TagModel{ + { + TagKey: "TagKey", + TagValue: "TagValue", + }, + }, + } + err := BCC_CLIENT.UnBindImageToTags(BCC_TestImageId, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateRemoteCopySnapshot(t *testing.T) { + args := &api.RemoteCopySnapshotArgs{ + ClientToken: "ClientTokenForTest", + DestRegionInfos: []api.DestRegionInfo{ + { + Name: "Test", + DestRegion: "bj", + }, + }, + } + result, err := BCC_CLIENT.CreateRemoteCopySnapshot("s-S9HdTie0", args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestImportCustomImage(t *testing.T) { + args := &api.ImportCustomImageArgs{ + OsName: "Centos", + OsArch: "32", + OsType: "linux", + OsVersion: "6.5", + Name: "import_image_test", + BosURL: "http://cloud.baidu.com/testurl", + } + + result, err := BCC_CLIENT.ImportCustomImage(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestGetAvailableImagesBySpec(t *testing.T) { + + args := &api.GetAvailableImagesBySpecArg{ + OsName: "Centos", + Spec: "bcc.ic4.c1m1", + MaxKeys: 1, + Marker: "m-21bmeYvH", + } + + result, err := BCC_CLIENT.GetAvailableImagesBySpec(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestListCDSVolumeV3New(t *testing.T) { + + args := &api.ListCDSVolumeArgs{ + ChargeFilter: "postpay", + Name: "cdsTest", + UsageFilter: "data", + } + + result, err := BCC_CLIENT.ListCDSVolumeV3(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} + +func TestListCDSVolumeNew(t *testing.T) { + + args := &api.ListCDSVolumeArgs{ + ChargeFilter: "postpay", + Name: "test-ebcc-api-gc_datadiskvCSM", + UsageFilter: "data", + } + + result, err := BCC_CLIENT.ListCDSVolume(args) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(result) +} diff --git a/bce-sdk-go/services/bcc/config.json b/bce-sdk-go/services/bcc/config.json new file mode 100644 index 0000000..61e7c8e --- /dev/null +++ b/bce-sdk-go/services/bcc/config.json @@ -0,0 +1,5 @@ +{ + "AK":"", + "SK":"", + "Endpoint":"" +} \ No newline at end of file diff --git a/bce-sdk-go/services/bci/bci.go b/bce-sdk-go/services/bci/bci.go new file mode 100644 index 0000000..35a58ef --- /dev/null +++ b/bce-sdk-go/services/bci/bci.go @@ -0,0 +1,139 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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 bci + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateInstance - create a bci with the specific parameters +// +// PARAMS: +// - args: the arguments to create a bci +// +// RETURNS: +// - *CreateInstanceResult: the result of create bci +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *CreateInstanceArgs) (*CreateInstanceResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateInstanceArgs cannot be nil.") + } + + result := &CreateInstanceResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForBci()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListInstance - list all bcis with the specific parameters +// +// PARAMS: +// - args: the arguments to list all bcis +// +// RETURNS: +// - *ListInstanceResult: the result of list all bcis +// - error: nil if success otherwise the specific error +func (c *Client) ListInstances(args *ListInstanceArgs) (*ListInstanceResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListInstanceArgs cannot be nil.") + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListInstanceResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForBci()). + WithMethod(http.GET). + WithQueryParam("keywordType", args.KeywordType). + WithQueryParamFilter("keyword", args.Keyword). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetInstance - query bci detail with the specific parameters +// +// PARAMS: +// - args: the arguments to list all bci +// +// RETURNS: +// - *GetInstanceResult: the result of query bci detail +// - error: nil if success otherwise the specific error +func (c *Client) GetInstance(args *GetInstanceArgs) (*GetInstanceResult, error) { + if args == nil { + return nil, fmt.Errorf("The GetInstanceArgs cannot be nil.") + } + + result := &GetInstanceResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForBciId(args.InstanceId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// DeleteInstance - delete a bci +// +// PARAMS: +// - args: the arguments to delete a bci +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstance(args *DeleteInstanceArgs) error { + if args == nil { + return fmt.Errorf("The DeleteInstanceArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForBciId(args.InstanceId)). + WithMethod(http.DELETE). + WithQueryParamFilter("relatedReleaseFlag", strconv.FormatBool(args.RelatedReleaseFlag)). + Do() +} + +// BatchDeleteInstance - batch delete bcis +// +// PARAMS: +// - args: the arguments to batch delete bcis +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDeleteInstance(args *BatchDeleteInstanceArgs) error { + if args == nil { + return fmt.Errorf("The BatchDeleteInstanceArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForBci() + "/batchDel"). + WithMethod(http.POST). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/bci/client.go b/bce-sdk-go/services/bci/client.go new file mode 100644 index 0000000..65acdf2 --- /dev/null +++ b/bce-sdk-go/services/bci/client.go @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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 bci + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX_v2 = bce.URI_PREFIX + "v2" + DEFAULT_PREFIX = URI_PREFIX_v2 + + DEFAULT_BCI_ENDPOINT = "bci." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_BCI_URL = "/instance" +) + +// Client of BCI service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_BCI_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForBci() string { + return DEFAULT_PREFIX + REQUEST_BCI_URL +} + +func getURLForBciId(bciId string) string { + return getURLForBci() + "/" + bciId +} diff --git a/bce-sdk-go/services/bci/client_test.go b/bce-sdk-go/services/bci/client_test.go new file mode 100644 index 0000000..95e8c56 --- /dev/null +++ b/bce-sdk-go/services/bci/client_test.go @@ -0,0 +1,207 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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 bci + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BCI_CLIENT *Client + BciInstanceId string +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + BCI_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateInstance(t *testing.T) { + args := &CreateInstanceArgs{ + Name: "cym-bci-test", + ZoneName: "zoneC", + SubnetIds: []string{"sbn-g463qx0aqu7q"}, + SecurityGroupIds: []string{"g-59gf44p4jmwe"}, + RestartPolicy: "Always", + AutoCreateEip: false, + EipRouteType: "BGP", + EipBandwidthInMbps: 150, + EipBillingMethod: "ByTraffic", + Tags: []Tag{ + { + TagKey: "mytag", + TagValue: "serverglen", + }, + }, + ImageRegistryCredentials: []ImageRegistryCredential{ + { + Server: "docker.io/wywcoder", + UserName: "wywcoder", + Password: "Qaz123456", + }, + }, + Containers: []Container{ + { + Name: "container01", + Image: "registry.baidubce.com/bci-zjm-public/ubuntu:18.04", + Memory: 1, + CPU: 0.5, + WorkingDir: "", + ImagePullPolicy: "IfNotPresent", + Commands: []string{ + "/bin/sh", + "-c", + "sleep 30000 && exit 0", + }, + VolumeMounts: []VolumeMount{ + { + MountPath: "/usr/local/share", + ReadOnly: false, + Name: "emptydir", + Type: "EmptyDir", + }, + { + MountPath: "/config", + ReadOnly: false, + Name: "configfile", + Type: "ConfigFile", + }, + }, + Ports: []Port{ + { + Port: 8099, + Protocol: "TCP", + }, + }, + EnvironmentVars: []Environment{ + { + Key: "java", + Value: "/usr/local/jre", + }, + }, + }}, + Volume: &Volume{ + Nfs: []NfsVolume{}, + EmptyDir: []EmptyDirVolume{ + { + Name: "emptydir", + }, + }, + ConfigFile: []ConfigFileVolume{ + { + Name: "configfile", + ConfigFiles: []ConfigFileDetail{ + { + Path: "podconfig", + File: "filenxx", + }, + }, + }}, + }, + } + // res, _ := json.Marshal(args) + // fmt.Println(string(res)) + result, err := BCI_CLIENT.CreateInstance(args) + fmt.Printf("CreateInstance result: %+v, err: %+v \n", result, err) + ExpectEqual(t.Errorf, nil, err) + BciInstanceId = result.InstanceId +} + +func TestClient_ListInstance(t *testing.T) { + args := &ListInstanceArgs{ + MaxKeys: 5, + } + result, err := BCI_CLIENT.ListInstances(args) + fmt.Printf("ListInstances result: %+v, err: %+v \n", result, err) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetInstance(t *testing.T) { + args := &GetInstanceArgs{ + InstanceId: BciInstanceId, + } + result, err := BCI_CLIENT.GetInstance(args) + fmt.Printf("GetInstance err: %+v, result: %+v \n", err, *result.Instance) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteInstance(t *testing.T) { + args := &DeleteInstanceArgs{ + InstanceId: BciInstanceId, + RelatedReleaseFlag: true, + } + err := BCI_CLIENT.DeleteInstance(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BatchDeleteInstance(t *testing.T) { + args := &BatchDeleteInstanceArgs{ + InstanceIds: []string{BciInstanceId}, + RelatedReleaseFlag: true, + } + err := BCI_CLIENT.BatchDeleteInstance(args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/bci/model.go b/bce-sdk-go/services/bci/model.go new file mode 100644 index 0000000..26cf080 --- /dev/null +++ b/bce-sdk-go/services/bci/model.go @@ -0,0 +1,294 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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 bci + +type CreateInstanceArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + ZoneName string `json:"zoneName,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + SubnetIds []string `json:"subnetIds,omitempty"` + RestartPolicy string `json:"restartPolicy,omitempty"` + EipIp string `json:"eipIp,omitempty"` + AutoCreateEip bool `json:"autoCreateEip,omitempty"` + EipName string `json:"eipName,omitempty"` + EipRouteType string `json:"eipRouteType,omitempty"` + EipBandwidthInMbps int `json:"eipBandwidthInMbps,omitempty"` + EipBillingMethod string `json:"eipBillingMethod,omitempty"` + GPUType string `json:"gpuType,omitempty"` + TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds,omitempty"` + HostName string `json:"hostName,omitempty"` + Tags []Tag `json:"tags,omitempty"` + ImageRegistryCredentials []ImageRegistryCredential `json:"imageRegistryCredentials,omitempty"` + Containers []Container `json:"containers,omitempty"` + InitContainers []Container `json:"initContainers,omitempty"` + Volume *Volume `json:"volume,omitempty"` +} + +type CreateInstanceResult struct { + InstanceId string `json:"instanceId,omitempty"` +} + +type ListInstanceArgs struct { + KeywordType string `json:"keywordType,omitempty"` + Keyword string `json:"keyword,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListInstanceResult struct { + Marker string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated,omitempty"` + NextMarker string `json:"nextMarker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + Result []InstanceModel `json:"result,omitempty"` +} + +type GetInstanceArgs struct { + InstanceId string `json:"instanceId"` +} + +type GetInstanceResult struct { + Instance *InstanceDetailModel `json:"instance,omitempty"` +} + +type DeleteInstanceArgs struct { + InstanceId string `json:"instanceId"` + RelatedReleaseFlag bool `json:"relatedReleaseFlag,omitempty"` +} + +type BatchDeleteInstanceArgs struct { + InstanceIds []string `json:"instanceIds,omitempty"` + RelatedReleaseFlag bool `json:"relatedReleaseFlag,omitempty"` +} + +type InstanceDetailModel struct { + InstanceModel + Volume *Volume `json:"volume,omitempty"` + Containers []ContainerDetailModel `json:"containers,omitempty"` + InitContainers []ContainerDetailModel `json:"initContainers,omitempty"` + SecurityGroups []SecurityGroupModel `json:"securityGroups,omitempty"` + Vpc *VpcModel `json:"vpc,omitempty"` + Subnet *SubnetModel `json:"subnet,omitempty"` +} + +type ContainerDetailModel struct { + Name string `json:"name,omitempty"` + Image string `json:"image,omitempty"` + CPU float64 `json:"cpu,omitempty"` + GPU float64 `json:"gpu,omitempty"` + Memory float64 `json:"memory,omitempty"` + WorkingDir string `json:"workingDir"` + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + Commands []string `json:"commands,omitempty"` + Args []string `json:"args,omitempty"` + Ports []Port `json:"ports,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` + Envs []Environment `json:"envs,omitempty"` + CreateTime string `json:"createTime,omitempty"` + UpdateTime string `json:"updateTime,omitempty"` + DeleteTime string `json:"deleteTime,omitempty"` + PreviousState *ContainerStatus `json:"previousState,omitempty"` + CurrentState *ContainerStatus `json:"currentState,omitempty"` + Ready bool `json:"ready,omitempty"` + RestartCount int `json:"restartCount,omitempty"` +} + +type ContainerStatus struct { + State string `json:"state,omitempty"` + Reason string `json:"reason,omitempty"` + Message string `json:"message,omitempty"` + StartTime string `json:"startTime,omitempty"` + FinishTime string `json:"finishTime,omitempty"` + DetailStatus string `json:"detailStatus,omitempty"` + ExitCode int `json:"exitCode,omitempty"` +} + +type SecurityGroupModel struct { + SecurityGroupId string `json:"securityGroupId,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + VpcId string `json:"vpcId,omitempty"` +} + +type VpcModel struct { + VpcId string `json:"vpcId,omitempty"` + Name string `json:"name,omitempty"` + Cidr string `json:"cidr,omitempty"` + CreateTime string `json:"createTime,omitempty"` + Description string `json:"description,omitempty"` + IsDefault bool `json:"isDefault,omitempty"` +} + +type SubnetModel struct { + SubnetId string `json:"subnetId,omitempty"` + Name string `json:"name,omitempty"` + Cidr string `json:"cidr,omitempty"` + VpcId string `json:"vpcId,omitempty"` + SubnetType string `json:"subnetType,omitempty"` + Description string `json:"description,omitempty"` + CreateTime string `json:"createTime,omitempty"` +} + +type InstanceModel struct { + InstanceId string `json:"instanceId,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + Status string `json:"status,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + CPUType string `json:"cpuType,omitempty"` + GPUType string `json:"gpuType,omitempty"` + BandwidthInMbps int `json:"bandwidthInMbps,omitempty"` + InternalIp string `json:"internalIp,omitempty"` + PublicIp string `json:"publicIp,omitempty"` + CreateTime string `json:"createTime,omitempty"` + UpdateTime string `json:"updateTime,omitempty"` + DeleteTime string `json:"deleteTime,omitempty"` + RestartPolicy string `json:"restartPolicy,omitempty"` + Tags []Tag `json:"tags,omitempty"` +} + +type Tag struct { + TagKey string `json:"tagKey,omitempty"` + TagValue string `json:"tagValue,omitempty"` +} + +type ImageRegistryCredential struct { + Server string `json:"server,omitempty"` + UserName string `json:"userName,omitempty"` + Password string `json:"password,omitempty"` +} + +type Container struct { + Name string `json:"name"` + Image string `json:"image"` + Memory float64 `json:"memory,omitempty"` + CPU float64 `json:"cpu,omitempty"` + GPU float64 `json:"gpu,omitempty"` + WorkingDir string `json:"workingDir"` + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + Commands []string `json:"commands,omitempty"` + Args []string `json:"args,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` + Ports []Port `json:"ports,omitempty"` + EnvironmentVars []Environment `json:"environmentVars,omitempty"` + LivenessProbe *Probe `json:"livenessProbe,omitempty"` + ReadinessProbe *Probe `json:"readinessProbe,omitempty"` + StartupProbe *Probe `json:"startupProbe,omitempty"` + Stdin bool `json:"stdin,omitempty"` + StdinOnce bool `json:"stdinOnce,omitempty"` + Tty bool `json:"tty,omitempty"` + SecurityContext *ContainerSecurityContext `json:"securityContext,omitempty"` +} + +type VolumeMount struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + MountPath string `json:"mountPath,omitempty"` + ReadOnly bool `json:"readOnly,omitiempty"` +} + +type Port struct { + Name string `json:"name,omitempty"` + Port int `json:"port,omitempty"` + Protocol string `json:"protocol,omitempty"` +} + +type Environment struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type Probe struct { + InitialDelaySeconds int `json:"initialDelaySeconds,omitempty"` + TimeoutSeconds int `json:"timeoutSeconds,omitempty"` + PeriodSeconds int `json:"periodSeconds,omitempty"` + SuccessThreshold int `json:"successThreshold,omitempty"` + FailureThreshold int `json:"failureThreshold,omitempty"` + TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds,omitempty"` + HTTPGet *HTTPGetAction `json:"httpGet,omitempty"` + TCPSocket *TCPSocketAction `json:"tcpSocket,omitempty"` + Exec *ExecAction `json:"exec,omitempty"` + GRPC *GRPCAction `json:"grpc,omitempty"` +} + +type HTTPGetAction struct { + Path string `json:"path,omitempty"` + Port int `json:"port,omitempty"` + Scheme string `json:"scheme,omitempty"` + Host string `json:"host,omitempty"` + HTTPHeaders *HTTPHeader `json:"httpHeaders,omitempty"` +} + +type HTTPHeader struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +type TCPSocketAction struct { + Port int `json:"port,omitempty"` + Host string `json:"host,omitempty"` +} + +type ExecAction struct { + Command []string `json:"command,omitempty"` +} + +type GRPCAction struct { + Port int `json:"port,omitempty"` + Service string `josn:"service,omitempty"` +} + +type ContainerSecurityContext struct { + Capabilities *Capabilities `json:"capabilities,omitempty"` + RunAsUser int64 `json:"runAsUser,omitempty"` + RunAsGroup int64 `json:"runAsGroup,omitempty"` + RunAsNonRoot bool `json:"runAsNonRoot,omitempty"` + ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"` +} + +type Capabilities struct { + Add []string `json:"add,omitempty"` + Drop []string `json:"drop,omitempty"` +} + +type Volume struct { + Nfs []NfsVolume `json:"nfs,omitempty"` + EmptyDir []EmptyDirVolume `json:"emptyDir,omitempty"` + ConfigFile []ConfigFileVolume `json:"configFile,omitempty"` +} + +type NfsVolume struct { + Name string `json:"name,omitempty"` + Server string `json:"server,omitempty"` + Path string `json:"path,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` +} + +type EmptyDirVolume struct { + Name string `json:"name,omitempty"` + Medium string `json:"medium,omitempty"` + SizeLimit float64 `json:"sizeLimit,omitempty"` +} + +type ConfigFileVolume struct { + Name string `json:"name,omitempty"` + DefaultMode int `json:"defaultMode,omitempty"` + ConfigFiles []ConfigFileDetail `json:"configFiles,omitempty"` +} + +type ConfigFileDetail struct { + Path string `json:"path,omitempty"` + File string `json:"file,omitempty"` +} diff --git a/bce-sdk-go/services/bcm/bcm.go b/bce-sdk-go/services/bcm/bcm.go new file mode 100644 index 0000000..70c7bd5 --- /dev/null +++ b/bce-sdk-go/services/bcm/bcm.go @@ -0,0 +1,119 @@ +package bcm + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + // GetMetricDataPath userId - scope - metricName + GetMetricDataPath = "/json-api/v1/metricdata/%s/%s/%s" + // BatchMetricDataPath userId - scope + BatchMetricDataPath = "/json-api/v1/metricdata/metricName/%s/%s" + + Average = "average" + Maximum = "maximum" + Minimum = "minimum" + Sum = "sum" + SampleCount = "sampleCount" +) + +// GetMetricData get metric data +func (c *Client) GetMetricData(req *GetMetricDataRequest) (*GetMetricDataResponse, error) { + if len(req.UserId) <= 0 { + return nil, errors.New("userId should not be empty") + } + if len(req.Scope) <= 0 { + return nil, errors.New("scope should not be empty") + } + if len(req.MetricName) <= 0 { + return nil, errors.New("metricName should not be empty") + } + if len(req.Statistics) <= 0 { + return nil, errors.New("statistics should not be empty") + } + if req.PeriodInSecond < 10 { + return nil, errors.New("periodInSecond should not be greater 10") + } + if len(req.StartTime) <= 0 { + return nil, errors.New("startTime should not be empty") + } + if len(req.EndTime) <= 0 { + return nil, errors.New("endTime should not be empty") + } + if len(req.Dimensions) <= 0 { + return nil, errors.New("dimension should not be empty") + } + dimensionStr := "" + for key, value := range req.Dimensions { + dimensionStr = dimensionStr + key + ":" + value + ";" + } + dimensionStr = strings.TrimRight(dimensionStr, ";") + + result := &GetMetricDataResponse{} + url := fmt.Sprintf(GetMetricDataPath, req.UserId, req.Scope, req.MetricName) + err := bce.NewRequestBuilder(c). + WithURL(url). + WithQueryParam("dimensions", dimensionStr). + WithQueryParam("statistics[]", strings.Join(req.Statistics, ",")). + WithQueryParam("periodInSecond", strconv.Itoa(req.PeriodInSecond)). + WithQueryParam("startTime", req.StartTime). + WithQueryParam("endTime", req.EndTime). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} + +// BatchGetMetricData batch get metric data +func (c *Client) BatchGetMetricData(req *BatchGetMetricDataRequest) (*BatchGetMetricDataResponse, error) { + if len(req.UserId) <= 0 { + return nil, errors.New("userId should not be empty") + } + if len(req.Scope) <= 0 { + return nil, errors.New("scope should not be empty") + } + if len(req.MetricNames) <= 0 { + return nil, errors.New("metricName should not be empty") + } + if len(req.Statistics) <= 0 { + return nil, errors.New("statistics should not be empty") + } + if req.PeriodInSecond < 10 { + return nil, errors.New("periodInSecond should not be greater 10") + } + if len(req.StartTime) <= 0 { + return nil, errors.New("startTime should not be empty") + } + if len(req.EndTime) <= 0 { + return nil, errors.New("endTime should not be empty") + } + if len(req.Dimensions) <= 0 { + return nil, errors.New("dimension should not be empty") + } + dimensionStr := "" + for key, value := range req.Dimensions { + dimensionStr = dimensionStr + key + ":" + value + ";" + } + dimensionStr = strings.TrimRight(dimensionStr, ";") + + result := &BatchGetMetricDataResponse{} + url := fmt.Sprintf(BatchMetricDataPath, req.UserId, req.Scope) + err := bce.NewRequestBuilder(c). + WithURL(url). + WithQueryParam("metricName[]", strings.Join(req.MetricNames, ",")). + WithQueryParam("dimensions", dimensionStr). + WithQueryParam("statistics[]", strings.Join(req.Statistics, ",")). + WithQueryParam("periodInSecond", strconv.Itoa(req.PeriodInSecond)). + WithQueryParam("startTime", req.StartTime). + WithQueryParam("endTime", req.EndTime). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/bcm/client.go b/bce-sdk-go/services/bcm/client.go new file mode 100644 index 0000000..f0af011 --- /dev/null +++ b/bce-sdk-go/services/bcm/client.go @@ -0,0 +1,49 @@ +package bcm + +import ( + "strings" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + ProductName = "bcm" + DefaultBcmEndpoint = ProductName + "." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of BCM service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the bcm service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + + if len(endpoint) == 0 { + endpoint = DefaultBcmEndpoint + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: map[string]struct{}{ + strings.ToLower(http.HOST): {}, + strings.ToLower(http.BCE_DATE): {}, + }, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} diff --git a/bce-sdk-go/services/bcm/client_test.go b/bce-sdk-go/services/bcm/client_test.go new file mode 100644 index 0000000..a51fbbb --- /dev/null +++ b/bce-sdk-go/services/bcm/client_test.go @@ -0,0 +1,133 @@ +package bcm + +import ( + "encoding/json" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + bcmClient *Client + bcmConf *Conf +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` + InstanceId string `json:"InstanceId"` + UserId string `json:"UserId"` +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + bcmConf = &Conf{} + _ = decoder.Decode(bcmConf) + + bcmClient, _ = NewClient(bcmConf.AK, bcmConf.SK, bcmConf.Endpoint) + log.SetLogLevel(log.WARN) +} + +func TestClient_GetMetricData(t *testing.T) { + dimensions := map[string]string{ + "InstanceId": bcmConf.InstanceId, + } + req := &GetMetricDataRequest{ + UserId: bcmConf.UserId, + Scope: "BCE_BCC", + MetricName: "vCPUUsagePercent", + Dimensions: dimensions, + Statistics: strings.Split(Average+","+SampleCount+","+Sum+","+Minimum+","+Maximum, ","), + PeriodInSecond: 60, + StartTime: time.Now().UTC().Add(-2 * time.Hour).Format("2006-01-02T15:04:05Z"), + EndTime: time.Now().UTC().Add(-1 * time.Hour).Format("2006-01-02T15:04:05Z"), + } + resp, err := bcmClient.GetMetricData(req) + if err != nil { + t.Errorf("bcm get metric data error with %v\n", err) + } + if resp.Code != "OK" { + t.Errorf("bcm get metric data response code error with %v\n", resp.Code) + } + if len(resp.DataPoints) < 1 { + t.Error("bcm get metric data response dataPoints size not be greater 0\n") + } else { + if resp.DataPoints[0].Sum <= 0 { + t.Error("bcm get metric data response dataPoints[0] sum not be greater 0\n") + } + if resp.DataPoints[0].Average <= 0 { + t.Error("bcm get metric data response dataPoints[0] average not be greater 0\n") + } + if resp.DataPoints[0].SampleCount <= 0 { + t.Error("bcm get metric data response dataPoints[0] sampleCount not be greater 0\n") + } + if resp.DataPoints[0].Minimum <= 0 { + t.Error("bcm get metric data response dataPoints[0] minimum not be greater 0\n") + } + if resp.DataPoints[0].Maximum <= 0 { + t.Error("bcm get metric data response dataPoints[0] maximum not be greater 0\n") + } + } +} + +func TestClient_BatchGetMetricData(t *testing.T) { + dimensions := map[string]string{ + "InstanceId": bcmConf.InstanceId, + } + req := &BatchGetMetricDataRequest{ + UserId: bcmConf.UserId, + Scope: "BCE_BCC", + MetricNames: []string{"vCPUUsagePercent", "CpuIdlePercent"}, + Dimensions: dimensions, + Statistics: strings.Split(Average+","+SampleCount+","+Sum+","+Minimum+","+Maximum, ","), + PeriodInSecond: 60, + StartTime: time.Now().UTC().Add(-2 * time.Hour).Format("2006-01-02T15:04:05Z"), + EndTime: time.Now().UTC().Add(-1 * time.Hour).Format("2006-01-02T15:04:05Z"), + } + resp, err := bcmClient.BatchGetMetricData(req) + if err != nil { + t.Errorf("bcm batch get metric data error with %v\n", err) + } + if resp.Code != "OK" { + t.Errorf("bcm batch get metric data response code error with %v\n", resp.Code) + } + if len(resp.ErrorList) > 0 { + t.Error("bcm batch get metric data response errorList size not be lower 1\n") + } + if len(resp.SuccessList) < 2 { + t.Error("bcm batch get metric data response successList size not be greater 1\n") + } else { + if len(resp.SuccessList[0].DataPoints) <= 0 { + t.Error("bcm batch get metric data response successList dataPoints size not be greater 0\n") + } + if resp.SuccessList[0].DataPoints[0].Sum <= 0 { + t.Error("bcm batch get metric data response successList dataPoints[0] sum not be greater 0\n") + } + if resp.SuccessList[0].DataPoints[0].Average <= 0 { + t.Error("bcm batch get metric data response successList dataPoints[0] average not be greater 0\n") + } + if resp.SuccessList[0].DataPoints[0].SampleCount <= 0 { + t.Error("bcm batch get metric data response successList dataPoints[0] sampleCount not be greater 0\n") + } + if resp.SuccessList[0].DataPoints[0].Minimum <= 0 { + t.Error("bcm batch get metric data response successList dataPoints[0] minimum not be greater 0\n") + } + if resp.SuccessList[0].DataPoints[0].Maximum <= 0 { + t.Error("bcm batch get metric data response successList dataPoints[0] maximum not be greater 0\n") + } + } + +} diff --git a/bce-sdk-go/services/bcm/model.go b/bce-sdk-go/services/bcm/model.go new file mode 100644 index 0000000..b5bfac5 --- /dev/null +++ b/bce-sdk-go/services/bcm/model.go @@ -0,0 +1,64 @@ +package bcm + +type GetMetricDataRequest struct { + UserId string `json:"userId,omitempty"` + Scope string `json:"scope,omitempty"` + MetricName string `json:"metricName,omitempty"` + Dimensions map[string]string `json:"dimensions,omitempty"` + Statistics []string `json:"statistics,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` + PeriodInSecond int `json:"periodInSecond,omitempty"` +} + +type GetMetricDataResponse struct { + RequestId string `json:"requestId,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + DataPoints []*DataPoints `json:"dataPoints,omitempty"` +} + +type DataPoints struct { + Average float64 `json:"average,omitempty"` + Sum float64 `json:"sum,omitempty"` + Minimum float64 `json:"minimum,omitempty"` + Maximum float64 `json:"maximum,omitempty"` + SampleCount int64 `json:"sampleCount,omitempty"` + Timestamp string `json:"timestamp,omitempty"` +} + +type BatchGetMetricDataRequest struct { + UserId string `json:"userId,omitempty"` + Scope string `json:"scope,omitempty"` + MetricNames []string `json:"metricNames,omitempty"` + Dimensions map[string]string `json:"dimensions,omitempty"` + Statistics []string `json:"statistics,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` + PeriodInSecond int `json:"periodInSecond,omitempty"` +} + +type BatchGetMetricDataResponse struct { + RequestId string `json:"requestId,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + SuccessList []*SuccessBatchGetMetricData `json:"successList,omitempty"` + ErrorList []*ErrorBatchGetMetricData `json:"errorList,omitempty"` +} + +type SuccessBatchGetMetricData struct { + MetricName string `json:"metricName,omitempty"` + Dimensions []*Dimension `json:"dimensions,omitempty"` + DataPoints []*DataPoints `json:"dataPoints,omitempty"` +} + +type ErrorBatchGetMetricData struct { + MetricName string `json:"metricName,omitempty"` + Dimensions []*Dimension `json:"dimensions,omitempty"` + Message string `json:"message,omitempty"` +} + +type Dimension struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} diff --git a/bce-sdk-go/services/bec/api/common.go b/bce-sdk-go/services/bec/api/common.go new file mode 100644 index 0000000..88f85dc --- /dev/null +++ b/bce-sdk-go/services/bec/api/common.go @@ -0,0 +1,120 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// commond.go - commmon and shared logic + +// Package api defines all APIs supported by the BEC service of BCE. +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +type PostHttpReq struct { + Url string + Body interface{} + Result interface{} + Params map[string]string +} + +type GetHttpReq struct { + Url string + Result interface{} + Params map[string]string +} + +func Post(cli bce.Client, phr *PostHttpReq) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + + return PostOrPut(cli, phr, req) +} + +func Put(cli bce.Client, phr *PostHttpReq) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + + return PostOrPut(cli, phr, req) +} + +func PostOrPut(cli bce.Client, phr *PostHttpReq, req *bce.BceRequest) error { + req.SetUri(phr.Url) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + if phr.Body != nil { + jsonBytes, jsonErr := json.Marshal(phr.Body) + if jsonErr != nil { + return jsonErr + } + // fmt.Println(string(jsonBytes)) + bodyObj, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + req.SetBody(bodyObj) + } + + if phr.Params != nil { + req.SetParams(phr.Params) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + if phr.Result != nil { + if err := resp.ParseJsonBody(phr.Result); err != nil { + return err + } + } + return nil +} + +func Get(cli bce.Client, ghr *GetHttpReq) error { + req := &bce.BceRequest{} + req.SetUri(ghr.Url) + req.SetMethod(http.GET) + + if ghr.Params != nil { + req.SetParams(ghr.Params) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + if err := resp.ParseJsonBody(ghr.Result); err != nil { + return err + } + return nil +} + +func Delete(cli bce.Client, dhr *PostHttpReq) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + + return PostOrPut(cli, dhr, req) +} diff --git a/bce-sdk-go/services/bec/api/model.go b/bce-sdk-go/services/bec/api/model.go new file mode 100644 index 0000000..0b1524b --- /dev/null +++ b/bce-sdk-go/services/bec/api/model.go @@ -0,0 +1,1736 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +type V1VolumeMount struct { + Name string `json:"name,omitempty"` + MountPath string `json:"mountPath,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` + MountPropagation string `json:"mountPropagation,omitempty"` + SubPath string `json:"subPath,omitempty"` +} + +type V1ContainerPort struct { + Protocol string `json:"protocol,omitempty"` + ContainerPort int `json:"containerPort,omitempty"` + HostIP string `json:"hostIP,omitempty"` + HostPort int `json:"hostPort,omitempty"` + Name string `json:"name,omitempty"` +} + +type V1ConfigMapKeySelector struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + Optional bool `json:"optional,omitempty"` +} + +type V1ObjectFieldSelector struct { + ApiVersion string `json:"apiVersion,omitempty"` + FieldPath string `json:"fieldPath,omitempty"` +} + +type V1ResourceFieldSelector struct { + ContainerName string `json:"containerName,omitempty"` + Divisor string `json:"divisor,omitempty"` + Resource string `json:"resource,omitempty"` +} + +type V1SecretKeySelector struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + Optional string `json:"optional,omitempty"` +} + +type V1EnvVarSource struct { + ConfigMapKeyRef *V1ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + FieldRef *V1ObjectFieldSelector `json:"fieldRef,omitempty"` + ResourceFieldRef *V1ResourceFieldSelector `json:"resourceFieldRef,omitempty"` + SecretKeyRef *V1SecretKeySelector `json:"secretKeyRef,omitempty"` +} + +type V1EnvVar struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + ValueFrom *V1EnvVarSource `json:"valueFrom,omitempty"` +} + +type ImageRegistrySecret struct { + Name string `json:"name,omitempty"` +} + +type EmptyDir struct { + Name string `json:"name,omitempty"` +} + +type ConfigFile EmptyDir + +type Secret EmptyDir + +type VolumeClaimTemplates struct { + Name string `json:"name,omitempty"` + StorageSize int `json:"storageSize,omitempty"` + DiskType string `json:"diskType,omitempty"` +} + +type Volume struct { + EmptyDir *[]EmptyDir `json:"emptyDir,omitempty"` + ConfigMap *[]ConfigFile `json:"configMap,omitempty"` + Secret *[]Secret `json:"secret,omitempty"` + VolumeClaimTemplates *[]VolumeClaimTemplates `json:"volumeClaimTemplates,omitempty"` +} + +type Tag struct { + TagKey string `json:"tagKey,omitempty"` + TagValue string `json:"tagValue,omitempty"` +} + +type Region string + +const ( + RegionCentralChina Region = "CENTRAL_CHINA" + RegionEastChina Region = "EAST_CHINA" + RegionNorthChina Region = "NORTH_CHINA" + RegionSouthChina Region = "SOUTH_CHINA" + RegionNorthEast Region = "NORTH_EAST" + RegionNorthWest Region = "NORTH_WEST" + RegionSouthWest Region = "SOUTH_WEST" +) + +type ServiceProvider string + +const ( + ServiceChinaMobile ServiceProvider = "CHINA_MOBILE" + ServiceChinaUnicom ServiceProvider = "CHINA_UNICOM" + ServiceChinaTelecom ServiceProvider = "CHINA_TELECOM" + ServiceTripleLine ServiceProvider = "TRIPLE_LINE" +) + +type DeploymentInstance struct { + Region Region `json:"region,omitempty"` + ServiceProvider ServiceProvider `json:"serviceProvider,omitempty"` + Replicas int `json:"replicas,omitempty"` + City string `json:"city,omitempty"` + RegionId string `json:"regionId,omitempty"` + NetworkType string `json:"networkType,omitempty"` + VpcId string `json:"vpcId,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + SubServiceProviders []string `json:"subServiceProviders,omitempty"` +} + +type ResourceBriefVo struct { + ServiceId string `json:"serviceId"` + ServiceName string `json:"serviceName"` + ResourceId string `json:"resourceId"` + ResourceName string `json:"resourceName"` + Labels map[string]string `json:"labels"` + TotalCpu int `json:"totalCpu"` + TotalMem int `json:"totalMem"` + TotalGpu int `json:"totalGpu"` + TotalPods int `json:"totalPods"` + RunningPods int `json:"runningPods"` + TotalDeploy int `json:"totalDeploy"` + IngressBandwidth string `json:"ingressBandwidth"` + DeployInstance DeploymentInstance `json:"deployInstance"` + ImageList []string `json:"imageList"` + Containers []ContainerDetails `json:"containers"` + ImageRegistrySecrets []ImageRegistrySecret `json:"imageRegistrySecrets"` +} + +type DeploymentResourceBriefVo struct { + ServiceId string `json:"serviceId"` + ServiceName string `json:"serviceName"` + ResourceId string `json:"resourceId"` + ResourceName string `json:"resourceName"` + TotalPods int `json:"totalPods"` + RunningPods int `json:"runningPods"` + DeployInstance DeploymentInstance `json:"deployInstance"` + Containers []ContainerDetails `json:"containers"` + PodBriefVos []PodBriefVo `json:"podBriefVos"` + CreateTime string `json:"createTime"` + LastUpdateTime string `json:"lastUpdateTime"` +} +type PodBriefVo struct { + ServiceId string `json:"serviceId"` + DeploymentName string `json:"deploymentName"` + DeploymentId string `json:"deploymentId"` + PodName string `json:"podName"` + Region string `json:"region"` + City string `json:"city"` + RegionName string `json:"regionName"` + CityName string `json:"cityName"` + Country string `json:"country"` + CountryName string `json:"countryName"` + RegionId string `json:"regionId"` + Labels map[string]string `json:"labels"` + Status string `json:"status"` + Cpu int `json:"cpu"` + Mem int `json:"mem"` + IngressBandwidth string `json:"ingressBandwidth"` + PublicIp string `json:"publicIp"` + Ipv6PublicIp string `json:"ipv6PublicIp"` + InternalIp string `json:"internalIp"` + ImageList []string `json:"imageList"` + Containers []ContainerDetails `json:"containers"` +} + +type PodDetailVo struct { + PodId string `json:"podId"` + Region string `json:"region"` + City string `json:"city"` + RegionName string `json:"regionName"` + CityName string `json:"cityName"` + Country string `json:"country"` + CountryName string `json:"countryName"` + RegionId string `json:"regionId"` + Labels map[string]string `json:"labels"` + Status string `json:"status"` + Cpu int `json:"cpu"` + Mem int `json:"mem"` + IngressBandwidth string `json:"ingressBandwidth"` + PublicIp string `json:"publicIp"` + PodIp string `json:"podIp"` + Ipv6PublicIp string `json:"ipv6PublicIp"` + InternalIp string `json:"internalIp"` + ImageList []string `json:"imageList"` + PodDataStorage int `json:"podDataStorage"` + PodEventDetails []PodEventDetails `json:"podEventDetails"` + Containers []ContainerDetails `json:"containers"` + ContainerStatuses []V1ContainerStatus `json:"containerStatuses"` + CreateTime string `json:"createTime"` +} +type PodEventDetails struct { + EventName string `json:"eventName"` + EventType string `json:"eventType"` + EventMessage string `json:"eventMessage"` +} +type V1ContainerStatus struct { + ContainerID string `json:"containerID"` + Image string `json:"image"` + ImageID string `json:"imageID"` + Name string `json:"name"` + Ready bool `json:"ready"` + RestartCount int `json:"restartCount"` + LastState V1ContainerState `json:"lastState"` + State V1ContainerState `json:"state"` +} +type V1ContainerState struct { + Running V1ContainerStateRunning `json:"running"` + Terminated V1ContainerStateTerminated `json:"terminated"` + Waiting V1ContainerStateWaiting `json:"waiting"` +} +type V1ContainerStateRunning struct { + StartedAt int `json:"startedAt"` +} +type V1ContainerStateTerminated struct { + ContainerID string `json:"containerID"` + ExitCode int `json:"exitCode"` + Signal int `json:"signal"` + StartedAt int `json:"startedAt"` + FinishedAt int `json:"finishedAt"` + Message string `json:"message"` + Reason string `json:"reason"` +} +type V1ContainerStateWaiting struct { + Message string `json:"message"` + Reason string `json:"reason"` +} + +type UpdateDeploymentReplicasRequest struct { + Replicas int `json:"replicas,omitempty"` +} + +type ServiceDetailsVo struct { + ServiceId string `json:"serviceId"` + ServiceName string `json:"serviceName"` + Status string `json:"status"` + TotalCpu int `json:"totalCpu"` + TotalMem int `json:"totalMem"` + TotalGpu int `json:"totalGpu"` + TotalDisk int `json:"totalDisk"` + TotalPods int `json:"totalPods"` + RunningPods int `json:"runningPods"` + RegionSize int `json:"regionSize"` + TagsMap []Tag `json:"tagsMap"` + DeployInstances []DeploymentInstance `json:"deployInstances"` + ResourceBriefVos []ResourceBriefVo `json:"resourceBriefVos"` + ImageRegistrySecrets []ImageRegistrySecret `json:"imageRegistrySecrets"` + LogCollectDetail LogCollectDetail `json:"logCollectDetail"` + CreateTime string `json:"createTime"` + LastUpdateTime string `json:"lastUpdateTime"` +} + +type ServiceBriefVo struct { + ServiceId string `json:"serviceId"` + ServiceName string `json:"serviceName"` + Level string `json:"level"` + Status string `json:"status"` + TotalCpu int `json:"totalCpu"` + TotalMen int `json:"totalMem"` + TotalGpu int `json:"totalGpu"` + TotalDisk int `json:"totalDisk"` + Regions int `json:"regions"` + TotalPods int `json:"totalPods"` + RunningPods int `json:"runningPods"` + TagsMap []Tag `json:"tagMap"` + DeployInstances []DeploymentInstance `json:"deployInstances"` + CreateTime string `json:"createTime"` + LastUpdateTime string `json:"lastUpdateTime"` +} + +type ContainerDetails struct { + Name string `json:"name,omitempty"` + ImageVersion string `json:"imageVersion,omitempty"` + ImageAddress string `json:"imageAddress,omitempty"` + Memory int `json:"memory,omitempty"` + Cpu int `json:"cpu,omitempty"` + Gpu int `json:"gpu,omitempty"` + WorkingDir string `json:"workingDir,omitempty"` + Commands []string `json:"commands,omitempty"` + Args []string `json:"args,omitempty"` + VolumeMounts []V1VolumeMount `json:"volumeMounts,omitempty"` + Ports []V1ContainerPort `json:"ports,omitempty"` + Envs []V1EnvVar `json:"envs,omitempty"` +} + +type LogCollectDetail struct { + ServiceId string `json:"serviceId,omitempty"` + LogCollect bool `json:"logCollect,omitempty"` + LogPath string `json:"logPath,omitempty"` + JsonAnalysis bool `json:"jsonAnalysis,omitempty"` + PushLog bool `json:"pushLog,omitempty"` + Standard bool `json:"standard,omitempty"` + Custom bool `json:"custom,omitempty"` + LogOutputType string `json:"logOutputType,omitempty"` + EsIP string `json:"esIP,omitempty"` + EsPort int `json:"esPort,omitempty"` + EsIndex string `json:"esIndex,omitempty"` + Encrypted bool `json:"encrypted,omitempty"` + EsUserName string `json:"esUserName,omitempty"` + EsUserPassword string `json:"esUserPassword,omitempty"` +} + +type CreateServiceArgs struct { + ServiceName string `json:"serviceName,omitempty"` + PaymentMethod string `json:"paymentMethod,omitempty"` + ContainerGroupName string `json:"containerGroupName,omitempty"` + Containers *[]ContainerDetails `json:"containers,omitempty"` + ImageRegistrySecrets *[]ImageRegistrySecret `json:"imageRegistrySecrets,omitempty"` + Volumes *Volume `json:"volumes,omitempty"` + NeedPublicIp bool `json:"needPublicIp,omitempty"` + NeedIpv6PublicIp bool `json:"needIpv6PublicIp,omitempty"` + Bandwidth int `json:"bandwidth,omitempty"` + Tags *[]Tag `json:"tags,omitempty"` + DeployInstances *[]DeploymentInstance `json:"deployInstances,omitempty"` + LogCollectDetail *LogCollectDetail `json:"logCollectDetail,omitempty"` +} + +type CreateServiceResult struct { + Details ServiceBriefVo `json:"details"` + Result bool `json:"result"` + Action string `json:"action"` +} + +type OrderModel struct { + OrderBy string `json:"orderBy"` + Order string `json:"order"` +} + +type ListServiceResult struct { + Result []ServiceBriefVo `json:"result"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type ListPodResult struct { + Result []PodBriefVo `json:"result"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type MetricsType string + +const ( + MetricsTypeCpu MetricsType = "CPU" + MetricsTypeMemory MetricsType = "MEMORY" + MetricsTypeBandwidthReceive MetricsType = "BANDWIDTH_RECEIVE" + MetricsTypeBandwidthTransmit MetricsType = "BANDWIDTH_TRANSMIT" + MetricsTypeTrafficReceive MetricsType = "TRAFFIC_RECEIVE" + MetricsTypeTrafficTransmit MetricsType = "TRAFFIC_TRANSMIT" + + MetricsTypeNodeBwReceive MetricsType = "NODE_BW_RECEIVE" + MetricsTypeNodeBwTransmit MetricsType = "NODE_BW_TRANSMIT" + MetricsTypeNodeLbBwReceive MetricsType = "NODE_LB_BW_RECEIVE" + MetricsTypeNodeLbBwTransmit MetricsType = "NODE_LB_BW_TRANSMIT" + + MetricsTypeRequestNum MetricsType = "REQUEST_NUMBER" + MetricsTypeRequestRate MetricsType = "REQUEST_RATE" + MetricsTypeRequestDelay MetricsType = "REQUEST_DELAY" + + MetricsTypeUnknown MetricsType = "UNKNOWN" +) + +type Metric struct { + TimeInSecond int `json:"timeInSecond"` + Value float64 `json:"value"` +} + +type ServiceMetricsResult struct { + Metrics []Metric `json:"metrics"` + MaxValue float64 `json:"maxValue"` + AvgValue float64 `json:"avgValue"` + TotalValue float64 `json:"totalValue"` +} + +type GetServiceArgs struct { + ServiceId string +} + +type ServiceAction string + +const ( + ServiceActionStart ServiceAction = "start" + ServiceActionStop ServiceAction = "stop" +) + +type ServiceActionResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type UpdateServiceType string + +const ( + UpdateServiceTypeName UpdateServiceType = "NAME" + UpdateServiceTypeReplicas UpdateServiceType = "REPLICAS" + UpdateServiceTypeNameResource UpdateServiceType = "RESOURCE" +) + +type UpdateServiceArgs struct { + Type UpdateServiceType `json:"type,omitempty"` + DeployInstances *[]DeploymentInstance `json:"deployInstances,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + NeedIpv6PublicIp string `json:"needIpv6PublicIp,omitempty"` + Containers *[]ContainerDetails `json:"containers,omitempty"` + ImageRegistrySecrets *[]ImageRegistrySecret `json:"imageRegistrySecrets,omitempty"` + Bandwidth int `json:"bandwidth,omitempty"` +} + +type UpdateServiceResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details []ServiceBriefVo `json:"details"` +} + +type ServiceBatchOperateArgs struct { + IdList []string `json:"idList,omitempty"` + Action string `json:"action,omitempty"` +} + +type OperationVo struct { + ResourceId string `json:"resourceId"` + Success bool `json:"success"` + Error string `json:"error"` +} + +type ServiceBatchOperateResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details []OperationVo `json:"details"` +} + +type ListDeploymentArgs struct { + DeploymentID string `json:"deploymentID"` +} + +type Networks struct { + NetType string `json:"netType,omitempty"` + NetName string `json:"netName,omitempty"` + NicIndex int `json:"nicIndex,omitempty"` + EniId string `json:"eniId,omitempty"` + Mac string `json:"mac,omitempty"` + Ipv4 *IpAddress `json:"ipv4,omitempty"` + Ipv6 *IpAddress `json:"ipv6,omitempty"` + ReserveIps []string `json:"reserveIps,omitempty"` +} +type IpAddress struct { + Ip string `json:"ip,omitempty"` + Gw string `json:"gw,omitempty"` + Cidr string `json:"cidr,omitempty"` + Mask string `json:"mask,omitempty"` +} + +type NetworkConfig struct { + NodeType string `json:"nodeType,omitempty"` //NoneType + NetworksList *[]Networks `json:"networksList,omitempty"` +} + +type GpuRequest struct { + Type string `json:"type,omitempty"` + Num int `json:"num,omitempty"` +} + +type DiskType string + +const ( + DiskTypeNVME DiskType = "NVME" + DiskTypeSATA DiskType = "SATA" + DiskTypeCDSHDD DiskType = "CDS_HDD" + DiskTypeCDSSSD DiskType = "CDS_SSD" + DiskTypeRBDSSD DiskType = "RBD_SSD" + DiskTypeHDDPASSTHROUGH4T DiskType = "HDD_PASSTHROUGH_4T" + DiskTypeSSDPASSTHROUGH4T DiskType = "SSD_PASSTHROUGH_4T" +) + +type VolumeConfig struct { + Name string `json:"name,omitempty"` + VolumeType DiskType `json:"volumeType,omitempty"` + SizeInGB int `json:"sizeInGB,omitempty"` + PvcName string `json:"pvcName,omitempty"` + PassthroughCode string `json:"passthroughCode,omitempty"` +} + +type SystemVolumeConfig struct { + VolumeType DiskType `json:"volumeType,omitempty"` + SizeInGB int `json:"sizeInGB,omitempty"` + Name string `json:"name,omitempty"` + PvcName string `json:"pvcName,omitempty"` +} + +type DnsConfig struct { + DnsType string `json:"dnsType,omitempty"` + DnsAddress string `json:"dnsAddress,omitempty"` +} + +type KeyConfig struct { + Type string `json:"type,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + BccKeyPairIdList []string `json:"bccKeyPairIdList,omitempty"` +} + +type CreateVmServiceArgs struct { + ServiceName string `json:"serviceName,omitempty"` + VmName string `json:"vmName,omitempty"` + NeedPublicIp bool `json:"needPublicIp,omitempty"` + Bandwidth int `json:"bandwidth,omitempty"` + DeployInstances *[]DeploymentInstance `json:"deployInstances,omitempty"` + DnsConfig *DnsConfig `json:"dnsConfig,omitempty"` + Spec string `json:"spec,omitempty"` + Cpu int `json:"cpu,omitempty"` + Memory int `json:"memory,omitempty"` + ImageId string `json:"imageId,omitempty"` + ImageType ImageType `json:"imageType,omitempty"` + NeedIpv6PublicIp bool `json:"needIpv6PublicIp,omitempty"` + SystemVolume *SystemVolumeConfig `json:"systemVolume,omitempty"` + DataVolumeList *[]VolumeConfig `json:"dataVolumeList,omitempty"` + KeyConfig *KeyConfig `json:"keyConfig,omitempty"` + DisableIntranet bool `json:"disableIntranet,omitempty"` + DisableCloudInit bool `json:"disableCloudInit,omitempty"` + NetworkConfigList *[]NetworkConfig `json:"networkConfigList,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + Hostname string `json:"hostname,omitempty"` + DeploysetIdList []string `json:"deploysetIdList,omitempty"` + PaymentMethod string `json:"paymentMethod,omitempty"` + Gpu *GpuRequest `json:"gpu,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + TemplateId string `json:"templateId,omitempty"` +} + +type ImageDetail struct { + Id string `json:"id"` + ImageId string `json:"imageId"` + Name string `json:"name"` + + NameFri string `json:"nameFri"` + ImageType string `json:"imageType"` + SnapshotId string `json:"snapshotId"` + Cpu int `json:"cpu"` + Memory int `json:"memory"` + OsType string `json:"osType"` + OsVersion string `json:"osVersion"` + OsName string `json:"osName"` + OsBuild string `json:"osBuild"` + OsLang string `json:"osLang"` + DiskSize int `json:"diskSize"` + + CreateTime string `json:"createTime"` + Status string `json:"status"` + MinMem int `json:"minMem"` + MinCpu int `json:"minCpu"` + MinDiskGb int `json:"minDiskGb"` + Desc string `json:"desc"` + OsArch string `json:"osArch"` + EphemeralSize int `json:"ephemeralSize"` + ImageDescription string `json:"imageDescription"` + ShareToUserNumLimit int `json:"shareToUserNumLimit"` + SharedToUserNum int `json:"sharedToUserNum"` + FpgaType string `json:"fpgaType"` +} + +type VmInstanceIdVo struct { + VmId string `json:"vmId"` + VmName string `json:"vmName"` + Region string `json:"region"` + RegionId string `json:"regionId"` + City string `json:"city"` + ServiceProvider string `json:"serviceProvider"` +} + +type ResourceStatus string + +const ( + ResourceStatusStarting = "STARTING" + ResourceStatusRunning = "RUNNING" + ResourceStatusException = "EXCEPTION" + ResourceStatusFailed = "FAILED" + ResourceStatusUnknown = "UNKNOWN" + ResourceStatusTerminated = "TERMINATED" + ResourceStatusWaiting = "WAITING" + ResourceStatusStop = "STOP" + ResourceStatusStopping = "STOPPING" + ResourceStatusTerminating = "TERMINATING" + ResourceStatusNormal = "NORMAL" + // part of status for vm instant + ResourceStatusCreating = "CREATING" + ResourceStatusStopped = "STOPPED" + ResourceStatusRestarting = "RESTARTING" + ResourceStatusReinstalling = "REINSTALLING" + ResourceStatusImaging = "IMAGING" + // part of status for lb + ResourceStatusPending = "PENDING" + ResourceStatusBinding = "BINDING" +) + +type VmServiceBriefVo struct { + ServiceId string `json:"serviceId"` + ServiceName string `json:"serviceName"` + Status string `json:"status"` + TotalCpu int `json:"totalCpu"` + TotalMem int `json:"totalMem"` + TotalDisk int `json:"totalDisk"` + TotalRootDisk int `json:"totalRootDisk"` + Regions int `json:"regions"` + DeployInstances []DeploymentInstance `json:"deployInstances"` + TotalInstances int `json:"totalInstances"` + RunningInstances int `json:"runningInstances"` + OsImage ImageDetail `json:"osImage"` + CreateTime string `json:"createTime"` + TotalGpu int `json:"totalGpu"` + Instances []VmInstanceIdVo `json:"instances"` +} + +type CreateVmServiceResult struct { + Details VmServiceBriefVo `json:"details"` + Result bool `json:"result"` + Action string `json:"action"` +} + +type DeleteVmServiceArgs struct { + ServiceId string `json:"serviceId"` +} + +type DeleteVmServiceResult struct { + Details map[string]string `json:"details"` + Result bool `json:"result"` + Action string `json:"action"` +} + +type UpdateVmType string + +const ( + UpdateVmTypeServiceName UpdateVmType = "serviceName" + UpdateVmTypeVmName UpdateVmType = "vmName" + UpdateVmPassWord UpdateVmType = "password" + UpdateVmReplicas UpdateVmType = "replicas" + UpdateVmResource UpdateVmType = "resource" + UpdateVmSecurityGroup UpdateVmType = "securityGroup" + UpdateVmHostname UpdateVmType = "hostname" +) + +type UpdateBecVmForm struct { + Type UpdateVmType `json:"type,omitempty"` + Cpu int `json:"cpu,omitempty"` + Memory int `json:"memory,omitempty"` + NeedRestart bool `json:"needRestart,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + ImageId string `json:"imageId,omitempty"` + Bandwidth int `json:"bandwidth,omitempty"` + ImageType ImageType `json:"imageType,omitempty"` + VmName string `json:"vmName,omitempty"` + Hostname string `json:"hostname,omitempty"` + VmId string `json:"vmId,omitempty"` + DataVolumeList *[]VolumeConfig `json:"dataVolumeList,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` + SystemVolume *SystemVolumeConfig `json:"systemVolume,omitempty"` + KeyConfig *KeyConfig `json:"keyConfig,omitempty"` + DnsConfig *DnsConfig `json:"dnsConfig,omitempty"` + NeedIpv6PublicIp bool `json:"needIpv6PublicIp"` + NetworkConfigList *[]NetworkConfig `json:"networkConfigList,omitempty"` +} + +type UpdateVmServiceArgs struct { + UpdateBecVmForm + ServiceName string `json:"serviceName,omitempty"` + DeployInstances *[]DeploymentInstance `json:"deployInstances,omitempty"` + ReplicaTemplate ReplicaTemplate `json:"replicaTemplate,omitempty"` +} +type ReplicaTemplate struct { + Type string `json:"type,omitempty"` + TemplateId string `json:"templateId,omitempty"` +} +type UpdateVmServiceResult struct { + Details VmServiceBriefVo `json:"details"` + Result bool `json:"result"` + Action string `json:"action"` +} + +type ListVmServiceArgs struct { + KeywordType string `json:"keywordType,omitempty"` + Keyword string `json:"keyword,omitempty"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Order string `json:"order,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + Status string `json:"status,omitempty"` + Region string `json:"region,omitempty"` + OsName string `json:"osName,omitempty"` + ServiceId string `json:"serviceId,omitempty"` +} + +type ListVmServiceResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []VmServiceBriefVo `json:"result"` +} + +type GetVmServiceDetailArgs struct { + ServiceId string `json:"serviceId"` +} + +type VmServiceDetailsVo struct { + VmServiceBriefVo + Bandwidth string `json:"bandwidth"` + TotalBandwidth string `json:"totalBandwidth"` + DataVolumeList []VolumeConfig `json:"dataVolumeList"` + SystemVolumeList []VolumeConfig `json:"systemVolumeList"` +} + +type VmServiceAction string + +const ( + VmServiceActionStart VmServiceAction = "start" + VmServiceActionStop VmServiceAction = "stop" +) + +type VmServiceActionResult struct { + Details map[string]string `json:"details"` + Result bool `json:"result"` + Action string `json:"action"` +} + +type VmServiceBatchActionResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details []OperationVo `json:"details"` +} + +type VmServiceBatchAction string + +const ( + VmServiceBatchStart VmServiceBatchAction = "start" + VmServiceBatchStop VmServiceBatchAction = "stop" +) + +type VmServiceBatchActionArgs struct { + IdList []string `json:"idList,omitempty"` + Action VmServiceBatchAction `json:"action,omitempty"` +} + +type CreateVmImageArgs struct { + VmId string `json:"vmId,omitempty"` + Name string `json:"name,omitempty"` +} + +type CreateVmImageResult struct { + Success bool `json:"success"` + Result string `json:"result"` +} + +type VmImageOperateResult struct { + Success bool `json:"success"` + Result bool `json:"result"` +} + +type UpdateVmImageArgs struct { + Name string `json:"name,omitempty"` +} + +type ListVmImageArgs struct { + KeywordType string `json:"keywordType"` + Keyword string `json:"keyword"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Order string `json:"order,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + Status string `json:"status"` + Region string `json:"region"` + OsName string `json:"osName"` + ServiceId string `json:"serviceId"` + Type string `json:"type,omitempty"` +} + +type VmImageVo struct { + ImageId string `json:"imageId"` + Status string `json:"status"` + BccImageId string `json:"bccImageId"` + Name string `json:"name"` + AccountId string `json:"accountId"` + ImageType string `json:"imageType"` + SystemDisk int `json:"systemDisk"` + OsType string `json:"osType"` + OsVersion string `json:"osVersion"` + OsName string `json:"osName"` + OsBuild string `json:"osBuild"` + OsLang string `json:"osLang"` + OsArch string `json:"osArch"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type ListVmImageResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []VmImageVo `json:"result"` +} + +type CreateBlbArgs struct { + LbType string `json:"lbType,omitempty"` + PaymentMethod string `json:"paymentMethod,omitempty"` + Region Region `json:"region,omitempty"` + City string `json:"city,omitempty"` + ServiceProvider ServiceProvider `json:"serviceProvider,omitempty"` + RegionId string `json:"regionId,omitempty"` + SubServiceProviders []string `json:"subServiceProviders,omitempty"` + NetworkType string `json:"networkType,omitempty"` + VpcId string `json:"vpcId,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + BlbName string `json:"blbName,omitempty"` + NeedPublicIp bool `json:"needPublicIp,omitempty"` + BandwidthInMbpsLimit int `json:"bandwidthInMbpsLimit,omitempty"` + Tags *[]Tag `json:"tags,omitempty"` + Listeners *[]Listeners `json:"listeners,omitempty"` +} + +type BatchCreateBlbArgs struct { + LbType string `json:"lbType,omitempty"` + PaymentMethod string `json:"paymentMethod,omitempty"` + RegionSelection string `json:"regionSelection,omitempty"` + DeployInstances *[]DeploymentInstance `json:"deployInstances,omitempty"` + BlbName string `json:"blbName,omitempty"` + NeedPublicIp bool `json:"needPublicIp,omitempty"` + BandwidthInMbpsLimit int `json:"bandwidthInMbpsLimit,omitempty"` + Tags *[]Tag `json:"tags,omitempty"` + Listeners *[]Listeners `json:"listeners,omitempty"` +} + +type Protocol string + +const ( + ProtocolTcp Protocol = "TCP" + ProtocolUdp Protocol = "UDP" + ProtocolHttp Protocol = "HTTP" + ProtocolHttps Protocol = "HTTPS" + ProtocolSsl Protocol = "SSL" +) + +type Listeners struct { + Protocol Protocol `json:"protocol,omitempty"` + Port int `json:"port,omitempty"` + BackendPort int `json:"backendPort,omitempty"` + KeepaliveTimeout int `json:"keepaliveTimeout,omitempty"` + Scheduler LbMode `json:"scheduler,omitempty"` + EnableCipTTM bool `json:"enableCipTTM,omitempty"` + EnableVipTTM bool `json:"enableVipTTM,omitempty"` + + // health check config + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + HealthCheckRetry int `json:"healthCheckRetry,omitempty"` + HealthCheckTimeout int `json:"healthCheckTimeout,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` + HealthCheckType string `json:"healthCheckType,omitempty"` +} + +type BlbInstanceVo struct { + BlbId string `json:"blbId"` + BlbName string `json:"blbName"` + Status string `json:"status"` + LbType string `json:"lbType"` + Region Region `json:"region"` + ServiceProvider ServiceProvider `json:"serviceProvider"` + City string `json:"city"` + RegionId string `json:"regionId"` + PublicIp string `json:"publicIp"` + CmPublicIP string `json:"cmPublicIP"` + CtPublicIP string `json:"ctPublicIP"` + UnPublicIP string `json:"unPublicIP"` + InternalIp string `json:"internalIp"` + Ports []Listeners `json:"ports"` + PodCount int `json:"podCount"` + BandwidthInMbpsLimit int `json:"bandwidthInMbpsLimit"` + CreateTime string `json:"createTime"` +} + +type CreateBlbResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details BlbInstanceVo `json:"details"` +} + +type DeleteBlbResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type GetBlbListResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []BlbInstanceVo `json:"result"` +} + +type UpdateBlbArgs struct { + BlbName string `json:"blbName,omitempty"` + BandwidthInMbpsLimit int `json:"bandwidthInMbpsLimit,omitempty"` + Type string `json:"type,omitempty"` +} + +type UpdateBlbResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details BlbInstanceVo `json:"details"` +} + +type LbMode string + +const ( + LbModeWrr LbMode = "wrr" + LbModeMinConn LbMode = "minconn" + LbModeSrch LbMode = "srch" +) + +type BlbMonitorArgs struct { + FrontendPort *Port `json:"frontendPort,omitempty"` + BackendPort int `json:"backendPort,omitempty"` + LbMode LbMode `json:"lbMode,omitempty"` + KeepaliveTimeout int `json:"keepaliveTimeout,omitempty"` + HealthCheck *HealthCheck `json:"healthCheck,omitempty"` + EnableCipTTM bool `json:"enableCipTTM,omitempty"` + EnableVipTTM bool `json:"enableVipTTM,omitempty"` +} + +type BlbMonitorResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type BlbMonitorListResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []Listeners `json:"result"` +} + +type Port struct { + Protocol Protocol `json:"protocol,omitempty"` + Port int `json:"port,omitempty"` +} + +type Stats struct { + Health bool `json:"health"` + Port int `json:"port"` + Protocol Protocol `json:"protocol"` +} + +type BlbBackendPodBriefVo struct { + PodName string `json:"podName"` + PodStatus string `json:"podStatus"` + PodIp string `json:"podIp"` + BackendPort []Stats `json:"backendPort"` + Weight int `json:"weight"` +} + +type GetBlbBackendPodListResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []BlbBackendPodBriefVo `json:"result"` +} + +type BatchCreateBlbResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details []BlbInstanceVo `json:"details"` +} + +type BatchDeleteBlbResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details []OperationVo `json:"details"` +} + +type PortGroup struct { + Port int `json:"port,omitempty"` + BackendPort int `json:"backendPort,omitempty"` +} +type HealthCheck struct { + TimeoutInSeconds int `json:"timeoutInSeconds,omitempty"` + IntervalInSeconds int `json:"intervalInSeconds,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckString *string `json:"healthCheckString"` + HealthCheckType string `json:"healthCheckType,omitempty"` +} +type BatchCreateBlbMonitorArg struct { + Protocol Protocol `json:"protocol,omitempty"` + PortGroups *[]PortGroup `json:"portGroups,omitempty"` + LbMode LbMode `json:"lbMode,omitempty"` + KeepaliveTimeout int `json:"keepaliveTimeout,omitempty"` + HealthCheck *HealthCheck `json:"healthCheck,omitempty"` + EnableCipTTM bool `json:"enableCipTTM,omitempty"` + EnableVipTTM bool `json:"enableVipTTM,omitempty"` +} + +type BatchCreateBlbMonitorResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type ListRequest struct { + KeywordType string `json:"keywordType"` + Keyword string `json:"keyword"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Order string `json:"order,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + Status string `json:"status,omitempty"` + Region string `json:"region,omitempty"` + OsName string `json:"osName,omitempty"` + ServiceId string `json:"serviceId,omitempty"` + City string `json:"city,omitempty"` + ServiceProvider ServiceProvider `json:"serviceProvider,omitempty"` +} + +type KeyPair struct { + KeyPairId string `json:"keyPairId"` + Name string `json:"name"` +} + +type VmInstanceDetailsVo struct { + VmInstanceBriefVo + RootDiskSize int `json:"rootDiskSize"` + DataStorage int `json:"dataStorage"` + DataVolumeList []VolumeConfig `json:"dataVolumeList"` + SystemVolume SystemVolumeConfig `json:"systemVolume"` + BccKeyPairList []KeyPair `json:"bccKeyPairList"` + RackId string `json:"rackId,omitempty"` + HostId string `json:"hostId,omitempty"` + SwitchId string `json:"switchId"` + PrivateIps []string `json:"privateIps"` +} + +type LogicPageVmInstanceResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []VmInstanceDetailsVo `json:"result"` +} + +type GetNodeVmInstanceListResult struct { + Result []VmInstanceBriefVo `json:"result"` + Success bool `json:"success"` +} + +type ActionInfoVo struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} +type DeleteDeploymentActionInfoVo struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string][]string `json:"details"` +} + +type ImageType string + +const ( + ImageTypeBcc ImageType = "bcc" + ImageTypeBec ImageType = "bec" +) + +type UpdateVmInstanceArgs struct { + VmId string `json:"vmId,omitempty"` + Type string `json:"type,omitempty"` + Spec string `json:"spec,omitempty"` + Cpu int `json:"cpu,omitempty"` + Memory int `json:"memory,omitempty"` + NeedRestart bool `json:"needRestart,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + ImageId string `json:"imageId,omitempty"` + Hostname string `json:"hostname,omitempty"` + Bandwidth int `json:"bandwidth,omitempty"` + ImageType ImageType `json:"imageType,omitempty"` + VmName string `json:"vmName,omitempty"` + DataVolumeList *[]VolumeConfig `json:"dataVolumeList,omitempty"` + SystemVolume *SystemVolumeConfig `json:"systemVolume,omitempty"` + KeyConfig *KeyConfig `json:"keyConfig,omitempty"` + DnsConfig *DnsConfig `json:"dnsConfig,omitempty"` + NeedIpv6PublicIp bool `json:"needIpv6PublicIp"` + NetworkConfig *NetworkConfigUpdateVmInstance `json:"networkConfig,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds,omitempty"` +} + +type NetworkConfigUpdateVmInstance struct { + NeedPrivateNetwork bool `json:"needPrivateNetwork"` + NeedPublicNetwork bool `json:"needPublicNetwork"` + PrivateNetworkName string `json:"privateNetworkName,omitempty"` + PublicNetworkName string `json:"publicNetworkName,omitempty"` + PublicNetworkChinaMobileName string `json:"publicNetworkChinaMobileName,omitempty"` + PublicNetworkChinaUnicomName string `json:"publicNetworkChinaUnicomName,omitempty"` + PublicNetworkChinaTelecomName string `json:"publicNetworkChinaTelecomName,omitempty"` +} + +type IpInfo struct { + ServiceProvider ServiceProvider `json:"serviceProvider"` + Ip string `json:"ip"` + Ipv6 string `json:"ipv6"` +} + +type BindSecurityGroupInstances struct { + Instances []InstancesBinding `json:"instances"` +} + +type InstancesBinding struct { + InstanceId string `json:"instanceId"` + SecurityGroupIds []string `json:"securityGroupIds"` +} +type BindSecurityGroupInstancesResponse struct { + Action string `json:"action"` + Result bool `json:"result"` + Details []OperationVo `json:"details"` +} + +type IpPackageVo struct { + PublicIp string `json:"publicIp"` + Ipv6PublicIp string `json:"ipv6PublicIp"` + InternalIp string `json:"internalIp"` + MultiplePublicIp []IpInfo `json:"multiplePublicIp"` + ServiceProvider ServiceProvider `json:"serviceProvider"` +} + +type VmInstanceBriefVo struct { + IpPackageVo + VmId string `json:"vmId"` + Uuid string `json:"uuid"` + VmName string `json:"vmName"` + Status string `json:"status"` + Spec string `json:"spec"` + Cpu int `json:"cpu"` + Mem int `json:"mem"` + Gpu int `json:"gpu"` + Region Region `json:"region"` + City string `json:"city"` + RegionId string `json:"regionId"` + NeedPublicIp bool `json:"needPublicIp"` + NeedIpv6PublicIp bool `json:"needIpv6PublicIp"` + Bandwidth string `json:"bandwidth"` + OsImage ImageDetail `json:"osImage"` + ServiceId string `json:"serviceId"` + CreateTime string `json:"createTime"` + SecurityGroups []SecurityGroup `json:"securityGroups"` + Vpc Vpc `json:"vpc"` + deploysetList []DeploySetVo `json:"deploysetList"` + Hostname string `json:"hostname"` + Dns string `json:"dns"` +} + +type DeploySetVo struct { + DeploysetId string `json:"deploysetId"` + Name string `json:"name"` +} + +type SecurityGroup struct { + Id string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` +} + +type Vpc struct { + VpcId string `json:"vpcId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + Description string `json:"description"` + subnet Subnet `json:"subnet"` +} + +type Subnet struct { + SubnetId string `json:"subnetId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + Description string `json:"description"` +} + +type UpdateVmDeploymentResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details VmInstanceBriefVo `json:"details"` +} + +type ReinstallVmInstanceArg struct { + AdminPass string `json:"adminPass,omitempty"` + ImageId string `json:"imageId,omitempty"` + ImageType ImageType `json:"imageType,omitempty"` + ResetDataDisk bool `json:"resetDataDisk,omitempty"` + KeyConfig *KeyConfig `json:"keyConfig,omitempty"` +} + +type ReinstallVmInstanceResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details VmInstanceBriefVo `json:"details"` +} + +type VmInstanceBatchOperateAction string + +const ( + VmInstanceBatchOperateStart VmInstanceBatchOperateAction = "start" + VmInstanceBatchOperateStop VmInstanceBatchOperateAction = "stop" + VmInstanceBatchOperateRestart VmInstanceBatchOperateAction = "restart" +) + +type OperateVmDeploymentResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type VmConfigResult struct { + Cpu int `json:"cpu"` + Mem int `json:"mem"` + Region Region `json:"region"` + ServiceProvider ServiceProvider `json:"serviceProvider"` + City string `json:"city"` + RegionId string `json:"regionId"` + NeedPublicIp bool `json:"needPublicIp"` + Bandwidth string `json:"bandwidth"` + OsImage ImageDetail `json:"osImage"` + DataVolumeList []VolumeConfig `json:"dataVolumeList"` + SystemVolume SystemVolumeConfig `json:"systemVolume"` +} + +type Backends struct { + Name string `json:"name,omitempty"` + Ip string `json:"ip,omitempty"` + Weight int `json:"weight,omitempty"` +} + +type LbDeployPo struct { + ServiceName string `json:"serviceName"` + DeploymentName string `json:"deploymentName"` + CustomOrigName string `json:"customOrigName"` + ServiceId string `json:"serviceId"` + DeploymentType string `json:"deploymentType"` + Region Region `json:"region"` + ServiceProvider ServiceProvider `json:"serviceProvider"` + City string `json:"city"` + Replicas int `json:"replicas"` + PodCpu int `json:"podCpu"` + PodMemory int `json:"podMemory"` + PodGpu int `json:"podGpu"` + PodDataStorage string `json:"podDataStorage"` + Sata int `json:"sata"` + Nvme int `json:"nvme"` + DataDiskNum int `json:"dataDiskNum"` + PodIpRequired bool `json:"podIpRequired"` + Backends []Backends `json:"backends"` +} + +type GetBlbBackendBindingStsListResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []LbDeployPo `json:"result"` +} + +type BlbBindingForm struct { + DeploymentId string `json:"deploymentId,omitempty"` + DefaultWeight int `json:"defaultWeight,omitempty"` + PodWeight *[]Backends `json:"podWeight,omitempty"` +} +type CreateBlbBindingArgs struct { + BindingForms *[]BlbBindingForm `json:"bindingForms,omitempty"` +} + +type DeleteBlbBindPodArgs struct { + PodWeightList *[]Backends `json:"podWeightList,omitempty"` + DeploymentIds []string `json:"deploymentIds,omitempty"` +} + +type DeleteBlbBindPodResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type CreateBlbBindingResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} +type UpdateBindPodWeightArgs struct { + PodWeightList *[]Backends `json:"podWeightList,omitempty"` + DeploymentIds []string `json:"deploymentIds,omitempty"` +} + +type UpdateBindPodWeightResult struct { + Result bool `json:"result"` + Action string `json:"action"` + Details map[string]string `json:"details"` +} + +type CreateVmPrivateIpForm struct { + SecondaryPrivateIpAddressCount int `json:"secondaryPrivateIpAddressCount,omitempty"` + PrivateIps []string `json:"privateIps,omitempty"` +} + +type DeleteVmPrivateIpForm struct { + PrivateIps []string `json:"privateIps,omitempty"` +} + +type IpamResultVo struct { + Success bool `json:"success"` + ErrCode string `json:"errCode"` + ErrMsg string `json:"errMsg"` + Ips []string `json:"ips"` + ErrIPs []string `json:"errIPs"` +} + +type VmPrivateIpResult struct { + Result IpamResultVo `json:"result"` + Success bool `json:"success"` +} + +type ServiceProviderInfo struct { + ServiceProvider ServiceProvider `json:"serviceProvider"` + Name string `json:"name"` + RegionId string `json:"regionId"` + Capability []string `json:"capability"` +} + +type CityInfo struct { + City string `json:"city"` + Name string `json:"name"` + ServiceProviderList []ServiceProviderInfo `json:"serviceProviderList"` +} + +type RegionInfo struct { + Region Region `json:"region"` + Name string `json:"name"` + Country string `json:"country"` + CountryName string `json:"countryName"` + CityList []CityInfo `json:"cityList"` +} + +type GetBecAvailableNodeInfoVoResult struct { + RegionList []RegionInfo `json:"regionList,omitempty"` + NodeSum int `json:"nodeSum,omitempty"` +} +type UpdateVmDeploySetArgs struct { + InstanceId string `json:"instanceId,omitempty"` + DeploysetIdList []string `json:"deploysetIdList,omitempty"` +} +type DeleteVmDeploySetArgs struct { + DeploysetId string `json:"deploysetId,omitempty"` + InstanceIdList []string `json:"instanceIdList,omitempty"` +} +type CreateDeploySetArgs struct { + Name string `json:"name,omitempty"` + Desc string `json:"desc,omitempty"` +} +type CreateDeploySetResponseArgs struct { + DeploysetIdList []string `json:"deploysetIdList,omitempty"` +} + +type LogicPageDeploySetResult struct { + Orders []OrderModel `json:"orders"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []DeploySetDetails `json:"result"` +} + +type DeploySetDetails struct { + DeploysetId string `json:"deploysetId"` + Name string `json:"name"` + Desc string `json:"desc"` + InstanceCount int `json:"instanceCount"` + InstanceTotal int `json:"instanceTotal"` + CreateTime string `json:"createTime"` + NodeInstanceStatisList []NodeInstanceStatis `json:"nodeInstanceStatisList"` +} + +type NodeInstanceStatis struct { + RegionId string `json:"regionId"` + InstanceCount int `json:"instanceCount"` + InstanceTotal int `json:"instanceTotal"` + InstanceIds []string `json:"instanceIds"` +} + +type CreateAppBlbRequest struct { + Desc string `json:"desc,omitempty"` + Name string `json:"name,omitempty"` + RegionId string `json:"regionId,omitempty"` + SubServiceProviders []string `json:"subServiceProviders,omitempty"` + NeedPublicIp bool `json:"needPublicIp,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + VpcId string `json:"vpcId,omitempty"` +} + +type CreateAppBlbResponse struct { + Desc string `json:"desc"` + Name string `json:"name"` + BlbId string `json:"blbId"` +} +type ModifyBecBlbRequest struct { + Desc string `json:"desc,omitempty"` + Name string `json:"name,omitempty"` +} + +type AppBlbDetails struct { + Address string `json:"address"` + BlbId string `json:"blbId"` + Cidr string `json:"cidr"` + CreateTime string `json:"createTime"` + Desc string `json:"desc"` + Listener []AppBlbListener `json:"listener"` + Name string `json:"name"` + PublicIp string `json:"publicIp"` + RegionId string `json:"regionId"` + Status string `json:"status"` + SubnetCidr string `json:"subnetCidr"` + SubnetId string `json:"subnetId"` + VpcId string `json:"vpcId"` + SubnetName string `json:"subnetName"` + VpcName string `json:"vpcName"` +} +type AppBlbListener struct { + Port string `json:"port"` + Type string `json:"type"` +} + +type AppBlbListResponse struct { + BlbList []AppBlbDetails `json:"blbList"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type MarkerRequest struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type CreateBecAppBlbTcpListenerRequest struct { + ListenerPort int `json:"listenerPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` +} + +type CreateBecAppBlbUdpListenerRequest struct { + ListenerPort int `json:"listenerPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` +} + +type UpdateBecAppBlbTcpListenerRequest struct { + Scheduler string `json:"scheduler,omitempty"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` +} + +type UpdateBecAppBlbUdpListenerRequest struct { + Scheduler string `json:"scheduler,omitempty"` + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` +} + +type GetBecAppBlbListenerRequest struct { + ListenerPort int `json:"listenerPort,omitempty"` + MarkerRequest +} + +type GetBecAppBlbTcpListenerResponse struct { + IsTruncated bool `json:"isTruncated"` + ListenerList []AppBlbLTcpListenerDetail `json:"listenerList"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} +type GetBecAppBlbUdpListenerResponse struct { + IsTruncated bool `json:"isTruncated"` + ListenerList []AppBlbUdpListenerDetail `json:"listenerList"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type AppBlbLTcpListenerDetail struct { + Scheduler string `json:"scheduler"` + ListenerPort int `json:"listenerPort"` + TcpSessionTimeout int `json:"tcpSessionTimeout"` +} + +type AppBlbUdpListenerDetail struct { + Scheduler string `json:"scheduler"` + ListenerPort int `json:"listenerPort"` + UdpSessionTimeout int `json:"udpSessionTimeout"` +} + +type DeleteBlbListenerRequest struct { + PortTypeList []PortTypeList `json:"portTypeList,omitempty"` +} +type PortTypeList struct { + Port int `json:"port,omitempty"` + Type string `json:"type,omitempty"` +} +type UpdateBlbIpGroupRequest struct { + Desc string `json:"desc,omitempty"` + Name string `json:"name,omitempty"` + IpGroupId string `json:"ipGroupId,omitempty"` +} +type CreateBlbIpGroupRequest struct { + Desc string `json:"desc,omitempty"` + Name string `json:"name,omitempty"` + MemberList []BlbIpGroupMember `json:"memberList,omitempty"` +} +type BlbIpGroupMember struct { + Ip string `json:"ip,omitempty"` + Port int `json:"port,omitempty"` + Weight int `json:"weight,omitempty"` +} +type CreateBlbIpGroupResponse struct { + Desc string `json:"desc"` + Name string `json:"name"` + Id string `json:"id"` +} +type GetBlbIpGroupListRequest struct { + ExactlyMatch bool `json:"exactlyMatch,omitempty"` + Name string `json:"name,omitempty"` + MarkerRequest +} +type GetBlbIpGroupListResponse struct { + IsTruncated bool `json:"isTruncated"` + AppIpGroupList []AppIpGroupDetail `json:"appIpGroupList"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type AppIpGroupDetail struct { + BackendPolicyList []BackendPolicy `json:"backendPolicyList"` + Id string `json:"id"` + Desc string `json:"desc"` + Name string `json:"name"` +} +type BackendPolicy struct { + HealthCheck string `json:"healthCheck"` + HealthCheckHost string `json:"healthCheckHost"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + HealthCheckUrlPath string `json:"healthCheckUrlPath"` + HealthCheckDownRetry int `json:"healthCheckDownRetry"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond"` + HealthCheckPort int `json:"healthCheckPort"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckUpRetry int `json:"healthCheckUpRetry"` + Id string `json:"id"` + Type string `json:"type"` + UdpHealthCheckString string `json:"udpHealthCheckString"` +} +type DeleteBlbIpGroupRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` +} +type CreateBlbIpGroupBackendPolicyRequest struct { + HealthCheck string `json:"healthCheck,omitempty"` + HealthCheckHost string `json:"healthCheckHost,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + IpGroupId string `json:"ipGroupId,omitempty"` + Type string `json:"type,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} +type CreateBlbIpGroupBackendPolicyResponse struct { + Id string `json:"id"` +} +type UpdateBlbIpGroupBackendPolicyRequest struct { + HealthCheckHost string `json:"healthCheckHost,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + HealthCheckUrlPath string `json:"healthCheckUrlPath,omitempty"` + HealthCheckDownRetry int `json:"healthCheckDownRetry,omitempty"` + HealthCheckIntervalInSecond int `json:"healthCheckIntervalInSecond,omitempty"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckUpRetry int `json:"healthCheckUpRetry,omitempty"` + IpGroupId string `json:"ipGroupId,omitempty"` + Id string `json:"id,omitempty"` + UdpHealthCheckString string `json:"udpHealthCheckString,omitempty"` +} +type GetBlbIpGroupPolicyListRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + MarkerRequest +} +type GetBlbIpGroupPolicyListResponse struct { + IsTruncated bool `json:"isTruncated"` + BackendPolicyList []BackendPolicy `json:"backendPolicyList"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} +type DeleteBlbIpGroupBackendPolicyRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + BackendPolicyIdList []string `json:"backendPolicyIdList,omitempty"` +} +type CreateBlbIpGroupMemberRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + MemberList []BlbIpGroupMember `json:"memberList,omitempty"` +} +type CreateBlbIpGroupMemberResponse struct { + MemberList []BlbIpGroupMemberResponse `json:"memberList"` +} +type BlbIpGroupMemberResponse struct { + Ip string `json:"ip"` + MemberId string `json:"memberId"` + Port int `json:"port"` + Weight int `json:"weight"` +} +type UpdateBlbIpGroupMemberRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + MemberList []UpdateBlbIpGroupMember `json:"memberList,omitempty"` +} +type UpdateBlbIpGroupMember struct { + MemberId string `json:"memberId,omitempty"` + Port int `json:"port,omitempty"` + Weight int `json:"weight,omitempty"` +} +type GetBlbIpGroupMemberListRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + MarkerRequest +} +type GetBlbIpGroupMemberListResponse struct { + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + MemberList []BlbIpGroupMemberDetail `json:"memberList"` +} +type BlbIpGroupMemberDetail struct { + MemberId string `json:"memberId"` + Ip string `json:"ip"` + Port int `json:"port"` + Weight int `json:"weight"` +} +type DeleteBlbIpGroupBackendMemberRequest struct { + IpGroupId string `json:"ipGroupId,omitempty"` + MemberIdList []string `json:"memberIdList,omitempty"` +} +type CreateAppBlbPoliciesRequest struct { + ListenerPort int `json:"listenerPort,omitempty"` + Type string `json:"type,omitempty"` + AppPolicyVos []AppPolicyVo `json:"appPolicyVos,omitempty"` +} +type AppPolicyVo struct { + AppIpGroupId string `json:"appIpGroupId,omitempty"` + Desc string `json:"desc,omitempty"` + Priority int `json:"priority,omitempty"` +} +type GetBlbListenerPolicyRequest struct { + Port int `json:"port,omitempty"` + Type string `json:"type,omitempty"` + MarkerRequest +} +type GetBlbListenerPolicyResponse struct { + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + PolicyList []BlbListenerPolicy `json:"policyList"` +} +type BlbListenerPolicy struct { + AppIpGroupId string `json:"appIpGroupId"` + AppIpGroupName string `json:"appIpGroupName"` + Desc string `json:"desc"` + Type string `json:"type"` + Id string `json:"id"` + FrontendPort int `json:"frontendPort"` + Priority int `json:"priority"` + RuleList []BlbListenerPolicyRule `json:"ruleList"` +} +type BlbListenerPolicyRule struct { + Key string `json:"key"` + Value string `json:"value"` +} +type DeleteAppBlbPoliciesRequest struct { + Type string `json:"type,omitempty"` + Port int `json:"port,omitempty"` + PolicyIdList []string `json:"policyIdList,omitempty"` +} diff --git a/bce-sdk-go/services/bec/api/util.go b/bce-sdk-go/services/bec/api/util.go new file mode 100644 index 0000000..acf97a2 --- /dev/null +++ b/bce-sdk-go/services/bec/api/util.go @@ -0,0 +1,172 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BEC service +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + URI_PREFIX_V2 = bce.URI_PREFIX + "v2" + + DEFAULT_BEC_DOMAIN = "bec." + bce.DEFAULT_REGION + bce.DEFAULT_DOMAIN + + REQUEST_SERVICE_URL = URI_PREFIX + "/service" + REQUEST_POD_URL = URI_PREFIX + "/pod" + + REQUEST_VM_URL = URI_PREFIX + "/vm" + "/service" + + DEPLOY_SET_URL = URI_PREFIX + "/deployset" + + DEPLOYMENT_URL = URI_PREFIX + "/deployment" + + REQUEST_VM_MONITOR_URL = URI_PREFIX + "/monitor" + "/vm" + + SERVICE_MONITOR_URL = URI_PREFIX + "/monitor" + "/service" + "/sts" + + DEPLOYMENT_MONITOR_URL = URI_PREFIX + "/monitor" + "/deployment" + POD_MONITOR_URL = URI_PREFIX + "/monitor" + "/pod" + + REQUEST_VM_SERVICE_MONITOR_URL = URI_PREFIX + "/monitor" + "/service" + "/vm" + + REQUEST_VM_IMAGE_URL = URI_PREFIX + "/vm" + "/image" + + REQUEST_LOADBALANCER_URL = URI_PREFIX + "/blb" + + REQUEST_LOADBALANCER_URL_V2 = URI_PREFIX_V2 + "/appblb" + + REQUEST_LOADBALANCER_MONITOR_URL = URI_PREFIX + "/monitor" + "/lb" + + REQUEST_VM_INSTANCE_URL = URI_PREFIX + "/vm/instance" + + REQUEST_NODE_URL = URI_PREFIX + "/node" +) + +/* +var ( + MetricType = map[string]string{ + "cpu": "cpu", + "memory": "memory", + "bandwidth_receive": "bandwidth_receive", + "bandwidth_transmit": "bandwidth_transmit", + "traffic_receive": "traffic_receive", + "traffic_transmit": "traffic_transmit", + + "node_bw_receive": "node_bw_receive", + "node_bw_transmit": "node_bw_transmit", + "node_lb_bw_receive": "node_lb_bw_receive", + "node_lb_bw_transmit": "node_lb_bw_transmit", + "request_num": "request_num", + "request_rate": "request_rate", + "request_delay": "request_delay", + "unknown": "unknown", + } +) +*/ + +func GetServiceURI() string { + return REQUEST_SERVICE_URL +} + +func GetServiceMetricsURI(serviceId string) string { + return SERVICE_MONITOR_URL + "/" + serviceId +} +func GetDeploymentMetricsURI(deploymentId string) string { + return DEPLOYMENT_MONITOR_URL + "/" + deploymentId +} +func GetPodMetricsURI(podId string) string { + return POD_MONITOR_URL + "/" + podId +} +func GetServiceDetailURI(serviceId string) string { + return REQUEST_SERVICE_URL + "/" + serviceId +} +func GetDeploymentDetailURI(deploymentId string) string { + return DEPLOYMENT_URL + "/" + deploymentId +} + +func GetStartServiceURI(serviceId, action string) string { + return REQUEST_SERVICE_URL + "/" + serviceId + "/" + action +} + +func GetDeleteServiceURI(serviceId string) string { + return REQUEST_SERVICE_URL + "/" + serviceId +} + +func GetUpdateServiceURI(serviceId string) string { + return REQUEST_SERVICE_URL + "/" + serviceId +} + +func GetBachServiceOperateURI() string { + return REQUEST_SERVICE_URL + "/batch/operate" +} + +func GetBachServiceDeleteURI() string { + return REQUEST_SERVICE_URL + "/batch/delete" +} + +func GetVmURI() string { + return REQUEST_VM_URL +} + +func GetDeploySetURI() string { + return DEPLOY_SET_URL +} + +func GetAppBlbURI() string { + return REQUEST_LOADBALANCER_URL_V2 +} + +func GetVmServiceActionURI(serviceId, action string) string { + return REQUEST_VM_URL + "/" + serviceId + "/" + action +} + +func GetVmImageURI() string { + return REQUEST_VM_IMAGE_URL +} + +func GetLoadBalancerURI() string { + return REQUEST_LOADBALANCER_URL +} + +func GetLoadBalancerMonitorURI() string { + return REQUEST_LOADBALANCER_MONITOR_URL +} + +func GetVmMonitorURI() string { + return REQUEST_VM_MONITOR_URL +} + +func GetVmServiceMonitorURI() string { + return REQUEST_VM_SERVICE_MONITOR_URL +} + +func GetLoadBalancerBatchURI() string { + return REQUEST_LOADBALANCER_URL + "/batch" +} + +func GetVmServiceMetricsURI(serviceId, metricsType string) string { + return REQUEST_VM_URL + "/" + serviceId + "/metrics/" + metricsType +} + +func GetVmInstanceURI() string { + return REQUEST_VM_INSTANCE_URL +} + +func GetNodeInfoURI() string { + return REQUEST_NODE_URL +} diff --git a/bce-sdk-go/services/bec/appBlb.go b/bce-sdk-go/services/bec/appBlb.go new file mode 100644 index 0000000..5342ebc --- /dev/null +++ b/bce-sdk-go/services/bec/appBlb.go @@ -0,0 +1,698 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/services/bec/api" + "strconv" +) + +// CreateAppBlb - create app lb with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb +// +// RETURNS: +// - *CreateAppBlbResponse: the result of create app lb +// - error: nil if ok otherwise the specific error +func (c *Client) CreateAppBlb(clientToken string, args *api.CreateAppBlbRequest) (*api.CreateAppBlbResponse, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + + result := &api.CreateAppBlbResponse{} + req := &api.PostHttpReq{Url: api.GetAppBlbURI(), Result: result, Body: args, Params: params} + err := api.Post(c, req) + + return result, err +} + +// UpdateAppBlb - update app lb with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb +// +// RETURNS: +// - *CreateAppBlbResponse: the result of create app lb +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateAppBlb(clientToken, blbId string, args *api.ModifyBecBlbRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId, Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// GetAppBlbList - get app lb list with the specific parameters +// +// PARAMS: +// - args: the arguments to get app lb list +// +// RETURNS: +// - *AppBlbListResponse: the result of app lb list +// - error: nil if ok otherwise the specific error +func (c *Client) GetAppBlbList(args *api.MarkerRequest) (*api.AppBlbListResponse, error) { + + params := make(map[string]string) + if args.Marker != "" { + params["marker"] = args.Marker + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + res := &api.AppBlbListResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI(), Params: params, Result: res} + err := api.Get(c, req) + + return res, err +} + +// GetAppBlbDetails - get app lb detail with the specific parameters +// +// PARAMS: +// - blbId: the arguments to get app lb details +// +// RETURNS: +// - *AppBlbListResponse: the result of app lb detail +// - error: nil if ok otherwise the specific error +func (c *Client) GetAppBlbDetails(blbId string) (*api.AppBlbDetails, error) { + + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + res := &api.AppBlbDetails{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId, Result: res} + err := api.Get(c, req) + return res, err +} + +// DeleteAppBlbInstance - delete app lb with the specific parameters +// +// PARAMS: +// - blbId: the arguments to delete app lb +// +// RETURNS: +// - *AppBlbListResponse: the result of app lb detail +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppBlbInstance(blbId, clientToken string) error { + + if blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId, Params: params} + err := api.Delete(c, req) + return err +} + +// CreateTcpListener - create app lb tcp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb tcp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of create app lb tcp listener +// - error: nil if ok otherwise the specific error +func (c *Client) CreateTcpListener(clientToken, blbId string, args *api.CreateBecAppBlbTcpListenerRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/TCPlistener", Body: args, Params: params} + err := api.Post(c, req) + + return err +} + +// UpdateTcpListener - update app lb tcp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb tcp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of update app lb tcp listener +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateTcpListener(clientToken, blbId, listenerPort string, args *api.UpdateBecAppBlbTcpListenerRequest) error { + if args == nil || blbId == "" || listenerPort == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + params["listenerPort"] = listenerPort + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/TCPlistener", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// GetTcpListener - get app lb tcp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to get app lb tcp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of get app lb tcp listener +// - error: nil if ok otherwise the specific error +func (c *Client) GetTcpListener(blbId string, args *api.GetBecAppBlbListenerRequest) (*api.GetBecAppBlbTcpListenerResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.ListenerPort != 0 { + params["listenerPort"] = strconv.Itoa(args.ListenerPort) + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + if args.Marker != "" { + params["marker"] = args.Marker + } + res := &api.GetBecAppBlbTcpListenerResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/TCPlistener", Params: params, Result: res} + err := api.Get(c, req) + + return res, err +} + +// CreateUdpListener - create app lb tcp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb udp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of create app lb udp listener +// - error: nil if ok otherwise the specific error +func (c *Client) CreateUdpListener(clientToken, blbId string, args *api.CreateBecAppBlbUdpListenerRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/UDPlistener", Body: args, Params: params} + err := api.Post(c, req) + + return err +} + +// UpdateUdpListener - update app lb udp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb udp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of update app lb udp listener +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateUdpListener(clientToken, blbId, listenerPort string, args *api.UpdateBecAppBlbUdpListenerRequest) error { + if args == nil || blbId == "" || listenerPort == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + params["listenerPort"] = listenerPort + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/UDPlistener", Body: args, Params: params} + err := api.Put(c, req) + return err +} + +// GetUdpListener - get app lb udp listener with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to get app lb udp listener +// +// RETURNS: +// - *CreateAppBlbResponse: the result of get app lb udp listener +// - error: nil if ok otherwise the specific error +func (c *Client) GetUdpListener(blbId string, args *api.GetBecAppBlbListenerRequest) (*api.GetBecAppBlbUdpListenerResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.ListenerPort != 0 { + params["listenerPort"] = strconv.Itoa(args.ListenerPort) + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + if args.Marker != "" { + params["marker"] = args.Marker + } + res := &api.GetBecAppBlbUdpListenerResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/UDPlistener", Params: params, Result: res} + err := api.Get(c, req) + + return res, err +} + +// DeleteAppBlbListener - delete app lb listener with the specific parameters +// +// PARAMS: +// - blbId: the arguments to delete app lb listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteAppBlbListener(blbId, clientToken string, args *api.DeleteBlbListenerRequest) error { + + if blbId == "" || args == nil { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + params["batchdelete"] = "" + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/listener", Params: params, Body: args} + err := api.Put(c, req) + return err +} + +// CreateIpGroup - create app lb ip group with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb ip group +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip group +// - error: nil if ok otherwise the specific error +func (c *Client) CreateIpGroup(clientToken, blbId string, args *api.CreateBlbIpGroupRequest) (*api.CreateBlbIpGroupResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + res := &api.CreateBlbIpGroupResponse{} + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup", Body: args, Params: params, Result: res} + err := api.Post(c, req) + + return res, err +} + +// UpdateIpGroup - update app lb ip group with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb ip group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateIpGroup(clientToken, blbId string, args *api.UpdateBlbIpGroupRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// GetIpGroup - get app lb ip group with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to get app lb ip group +// +// RETURNS: +// - *api.GetBlbIpGroupListResponse the result of app lb ip group +// - error: nil if ok otherwise the specific error +func (c *Client) GetIpGroup(blbId string, args *api.GetBlbIpGroupListRequest) (*api.GetBlbIpGroupListResponse, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.Name != "" { + params["name"] = args.Name + } + if args.ExactlyMatch != false { + params["exactlyMatch"] = strconv.FormatBool(args.ExactlyMatch) + } else { + params["exactlyMatch"] = strconv.FormatBool(false) + } + if args.Marker != "" { + params["maker"] = args.Marker + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + res := &api.GetBlbIpGroupListResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup", Params: params, Result: res} + err := api.Get(c, req) + + return res, err +} + +// DeleteIpGroup - delete app lb ip group with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to delete app lb ip group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteIpGroup(clientToken, blbId string, args *api.DeleteBlbIpGroupRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + params["delete"] = "" + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// CreateIpGroupPolicy - create app lb ip group Policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb ip group Policy +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip group Policy +// - error: nil if ok otherwise the specific error +func (c *Client) CreateIpGroupPolicy(clientToken, blbId string, args *api.CreateBlbIpGroupBackendPolicyRequest) (*api.CreateBlbIpGroupBackendPolicyResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + res := &api.CreateBlbIpGroupBackendPolicyResponse{} + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/backendpolicy", Body: args, Params: params, Result: res} + err := api.Post(c, req) + + return res, err +} + +// UpdateIpGroupPolicy - update app lb ip group Policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb ip group Policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateIpGroupPolicy(clientToken, blbId string, args *api.UpdateBlbIpGroupBackendPolicyRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/backendpolicy", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// GetIpGroupPolicyList - update app lb ip group Policy list with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb ip group Policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) GetIpGroupPolicyList(blbId string, args *api.GetBlbIpGroupPolicyListRequest) (*api.GetBlbIpGroupPolicyListResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.IpGroupId != "" { + params["ipGroupId"] = args.IpGroupId + } + + if args.Marker != "" { + params["maker"] = args.Marker + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + res := &api.GetBlbIpGroupPolicyListResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/backendpolicy", Result: res, Params: params} + err := api.Get(c, req) + + return res, err +} + +// DeleteIpGroupPolicy - delete app lb ip group policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to delete app lb ip group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteIpGroupPolicy(clientToken, blbId string, args *api.DeleteBlbIpGroupBackendPolicyRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/backendpolicy", Body: args, Params: params} + err := api.Delete(c, req) + return err +} + +// CreateIpGroupMember - create app lb ip group member with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb ip group member +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip group member +// - error: nil if ok otherwise the specific error +func (c *Client) CreateIpGroupMember(clientToken, blbId string, args *api.CreateBlbIpGroupMemberRequest) (*api.CreateBlbIpGroupMemberResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + res := &api.CreateBlbIpGroupMemberResponse{} + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/member", Body: args, Params: params, Result: res} + err := api.Post(c, req) + + return res, err +} + +// UpdateIpGroupMember - update app lb ip group member with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb ip group member +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip group member +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateIpGroupMember(clientToken, blbId string, args *api.UpdateBlbIpGroupMemberRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/member", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// GetIpGroupMemberList - get app lb ip group member list with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to update app lb ip group member +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip group member +// - error: nil if ok otherwise the specific error +func (c *Client) GetIpGroupMemberList(blbId string, args *api.GetBlbIpGroupMemberListRequest) (*api.GetBlbIpGroupMemberListResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.IpGroupId != "" { + params["ipGroupId"] = args.IpGroupId + } + + if args.Marker != "" { + params["maker"] = args.Marker + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + res := &api.GetBlbIpGroupMemberListResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/member", Result: res, Params: params} + err := api.Get(c, req) + + return res, err +} + +// DeleteIpGroupMember - delete app lb ip group member with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to delete app lb ip group member +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteIpGroupMember(clientToken, blbId string, args *api.DeleteBlbIpGroupBackendMemberRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + params["delete"] = "" + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/ipgroup/member", Body: args, Params: params} + err := api.Put(c, req) + + return err +} + +// CreateListenerPolicy - create app lb listener policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to create app lb ip listener policy +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb iplistener policy +// - error: nil if ok otherwise the specific error +func (c *Client) CreateListenerPolicy(clientToken, blbId string, args *api.CreateAppBlbPoliciesRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/policys", Body: args, Params: params} + err := api.Post(c, req) + + return err +} + +// GetListenerPolicy - get app lb listener policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to get app lb ip listener policy +// +// RETURNS: +// - *api.CreateBlbIpGroupResponse the result of app lb ip listener policy +// - error: nil if ok otherwise the specific error +func (c *Client) GetListenerPolicy(blbId string, args *api.GetBlbListenerPolicyRequest) (*api.GetBlbListenerPolicyResponse, error) { + if args == nil || blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if args.Type != "" { + params["type"] = args.Type + } + if args.Port != 0 { + params["port"] = strconv.Itoa(args.Port) + } + + if args.Marker != "" { + params["maker"] = args.Marker + } + if args.MaxKeys != 0 { + params["maxKeys"] = strconv.Itoa(args.MaxKeys) + } + res := &api.GetBlbListenerPolicyResponse{} + req := &api.GetHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/policys", Result: res, Params: params} + err := api.Get(c, req) + + return res, err +} + +// DeleteListenerPolicy - delete app lb listener policy with the specific parameters +// +// PARAMS: +// - clientToken:idempotent token,an ASCII string no longer than 64 bits,unnecessary +// - args: the arguments to delete app lb listener policy +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteListenerPolicy(clientToken, blbId string, args *api.DeleteAppBlbPoliciesRequest) error { + if args == nil || blbId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + params["batchdelete"] = "" + if clientToken != "" { + params["clientToken"] = clientToken + } + req := &api.PostHttpReq{Url: api.GetAppBlbURI() + "/" + blbId + "/policys", Body: args, Params: params} + err := api.Put(c, req) + + return err +} diff --git a/bce-sdk-go/services/bec/appBlb_test.go b/bce-sdk-go/services/bec/appBlb_test.go new file mode 100644 index 0000000..a8624d0 --- /dev/null +++ b/bce-sdk-go/services/bec/appBlb_test.go @@ -0,0 +1,276 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +////////////////////////////////////////////// +// deploy set test +////////////////////////////////////////////// + +func TestCreateAppBlb(t *testing.T) { + getReq := &api.CreateAppBlbRequest{ + Name: "wcw_test_applb", + Desc: "wcw-test", + RegionId: "cn-hangzhou-cm", + NeedPublicIp: true, + SubnetId: "sbn-tafnx9dd", + VpcId: "vpc-wljmvzmt", + } + res, err := CLIENT.CreateAppBlb("testCreateAppBlb", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateAppBlb(t *testing.T) { + getReq := &api.ModifyBecBlbRequest{ + Name: "wcw_test_applb", + Desc: "wcw-test1", + } + err := CLIENT.UpdateAppBlb("testUpdateAppBlb", "lb-zo8wibx1", getReq) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetAppBlbList(t *testing.T) { + getReq := &api.MarkerRequest{} + res, err := CLIENT.GetAppBlbList(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestGetAppBlbDetails(t *testing.T) { + + res, err := CLIENT.GetAppBlbDetails("applb-cn-hangzhou-cm-h9nh3vpe") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteAppBlbInstance(t *testing.T) { + + err := CLIENT.DeleteAppBlbInstance("applb-cn-hangzhou-cm-h9nh3vpe", "") + ExpectEqual(t.Errorf, nil, err) +} +func TestCreateTcpListener(t *testing.T) { + getReq := &api.CreateBecAppBlbTcpListenerRequest{ + ListenerPort: 80, + Scheduler: "RoundRobin", + TcpSessionTimeout: 1000, + } + err := CLIENT.CreateTcpListener("testCreateTcpListener", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestCreateUdpListener(t *testing.T) { + getReq := &api.CreateBecAppBlbUdpListenerRequest{ + ListenerPort: 80, + Scheduler: "RoundRobin", + UdpSessionTimeout: 1000, + } + err := CLIENT.CreateUdpListener("testCreateTcpListener", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestUpdateTcpListener(t *testing.T) { + getReq := &api.UpdateBecAppBlbTcpListenerRequest{ + Scheduler: "RoundRobin", + TcpSessionTimeout: 800, + } + err := CLIENT.UpdateTcpListener("testUpdateTcpListener", "applb-cn-hangzhou-cm-h9nh3vpe", "80", getReq) + ExpectEqual(t.Errorf, nil, err) +} + +func TestUpdateUdpListener(t *testing.T) { + getReq := &api.UpdateBecAppBlbUdpListenerRequest{ + Scheduler: "RoundRobin", + UdpSessionTimeout: 800, + } + err := CLIENT.UpdateUdpListener("testUpdateUdpListener", "applb-cn-hangzhou-cm-h9nh3vpe", "80", getReq) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetTcpListener(t *testing.T) { + getReq := &api.GetBecAppBlbListenerRequest{ + ListenerPort: 80, + } + res, err := CLIENT.GetTcpListener("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetUdpListener(t *testing.T) { + getReq := &api.GetBecAppBlbListenerRequest{ + ListenerPort: 80, + } + res, err := CLIENT.GetUdpListener("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeleteAppBlbListener(t *testing.T) { + getReq := &api.DeleteBlbListenerRequest{ + PortTypeList: []api.PortTypeList{ + { + Port: 80, + Type: "TCP", + }, + { + Port: 80, + Type: "UDP", + }, + }, + } + err := CLIENT.DeleteAppBlbListener("applb-cn-hangzhou-cm-h9nh3vpe", "deleteApplbInstance", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestCreateIpGroup(t *testing.T) { + getReq := &api.CreateBlbIpGroupRequest{ + Name: "wcw-testIpGroup", + Desc: "wcw-test", + } + res, err := CLIENT.CreateIpGroup("testIpGroup", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +// bec_ip_group-ukadxdrq +func TestUpdateIpGroup(t *testing.T) { + getReq := &api.UpdateBlbIpGroupRequest{ + Name: "wcw-testIpGroupupdate", + Desc: "wcw-testupdate", + IpGroupId: "bec_ip_group-ukadxdrq", + } + err := CLIENT.UpdateIpGroup("testIpGroup", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestGetIpGroup(t *testing.T) { + getReq := &api.GetBlbIpGroupListRequest{} + res, err := CLIENT.GetIpGroup("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeleteIpGroup(t *testing.T) { + getReq := &api.DeleteBlbIpGroupRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + } + err := CLIENT.DeleteIpGroup("testDeleteIpGroup", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestCreateIpGroupPolicy(t *testing.T) { + getReq := &api.CreateBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + Type: "TCP", + HealthCheck: "TCP", + HealthCheckPort: 80, + HealthCheckTimeoutInSecond: 10, + HealthCheckIntervalInSecond: 3, + HealthCheckDownRetry: 4, + HealthCheckUpRetry: 5, + } + res, err := CLIENT.CreateIpGroupPolicy("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestUpdateIpGroupPolicy(t *testing.T) { + getReq := &api.UpdateBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + Id: "bec_ip_group_policy-yodpsqqr", + HealthCheckPort: 80, + } + err := CLIENT.UpdateIpGroupPolicy("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestGetIpGroupPolicy(t *testing.T) { + getReq := &api.GetBlbIpGroupPolicyListRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + } + res, err := CLIENT.GetIpGroupPolicyList("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeleteIpGroupPolicy(t *testing.T) { + getReq := &api.DeleteBlbIpGroupBackendPolicyRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + BackendPolicyIdList: []string{"bec_ip_group_policy-yodpsqqr"}, + } + err := CLIENT.DeleteIpGroupPolicy("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + +} +func TestCreateIpGroupMember(t *testing.T) { + getReq := &api.CreateBlbIpGroupMemberRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + MemberList: []api.BlbIpGroupMember{ + { + Ip: "172.16.240.25", + Port: 90, + Weight: 100, + }, + }, + } + res, err := CLIENT.CreateIpGroupMember("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestUpdateIpGroupMember(t *testing.T) { + getReq := &api.UpdateBlbIpGroupMemberRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + MemberList: []api.UpdateBlbIpGroupMember{ + { + MemberId: "bec_ip_member-ouiinabp", + Port: 8080, + Weight: 100, + }, + }, + } + err := CLIENT.UpdateIpGroupMember("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestGetIpGroupMemberList(t *testing.T) { + getReq := &api.GetBlbIpGroupMemberListRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + } + res, err := CLIENT.GetIpGroupMemberList("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeleteIpGroupMember(t *testing.T) { + getReq := &api.DeleteBlbIpGroupBackendMemberRequest{ + IpGroupId: "bec_ip_group-ukadxdrq", + MemberIdList: []string{"bec_ip_member-ouiinabp"}, + } + err := CLIENT.DeleteIpGroupMember("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + +} +func TestCreateListenerPolicy(t *testing.T) { + getReq := &api.CreateAppBlbPoliciesRequest{ + ListenerPort: 80, + AppPolicyVos: []api.AppPolicyVo{ + { + AppIpGroupId: "bec_ip_group-ukadxdrq", + Priority: 1, + Desc: "wcw-test", + }, + }, + } + err := CLIENT.CreateListenerPolicy("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetListenerPolicy(t *testing.T) { + getReq := &api.GetBlbListenerPolicyRequest{ + Port: 80, + } + res, err := CLIENT.GetListenerPolicy("applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeleteListenerPolicy(t *testing.T) { + getReq := &api.DeleteAppBlbPoliciesRequest{ + Port: 80, + PolicyIdList: []string{ + "bec_policy-scr9cwtk", + }, + } + err := CLIENT.DeleteListenerPolicy("", "applb-cn-hangzhou-cm-h9nh3vpe", getReq) + ExpectEqual(t.Errorf, nil, err) + +} diff --git a/bce-sdk-go/services/bec/client.go b/bce-sdk-go/services/bec/client.go new file mode 100644 index 0000000..78e72ff --- /dev/null +++ b/bce-sdk-go/services/bec/client.go @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// Client of BEC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the BEC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = api.DEFAULT_BEC_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} diff --git a/bce-sdk-go/services/bec/deploySet.go b/bce-sdk-go/services/bec/deploySet.go new file mode 100644 index 0000000..4eab59e --- /dev/null +++ b/bce-sdk-go/services/bec/deploySet.go @@ -0,0 +1,175 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateDeploySet - create deploy set with the specific parameters +// +// PARAMS: +// - args: the arguments to create a deploy set +// +// RETURNS: +// - *CreateDeploySetResponseArgs: the result of create deploy set +// - error: nil if ok otherwise the specific error +func (c *Client) CreateDeploySet(args *api.CreateDeploySetArgs) (*api.CreateDeploySetResponseArgs, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateDeploySetResponseArgs{} + req := &api.PostHttpReq{Url: api.GetDeploySetURI() + "/create", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// UpdateVmInstanceDeploySet - update vm instance deploy set with the specific parameters +// +// PARAMS: +// - args: the arguments to update a vm instance deploy set +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateVmInstanceDeploySet(args *api.UpdateVmDeploySetArgs) error { + if args == nil { + return fmt.Errorf("please set argments") + } + req := &api.PostHttpReq{Url: api.GetDeploySetURI() + "/updateRelation", Body: args} + err := api.Post(c, req) + return err +} + +// DeleteVmInstanceFromDeploySet - remove vm instances from deploy set with the specific parameters +// +// PARAMS: +// - args: the arguments to remove vm instances from deploy set +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVmInstanceFromDeploySet(args *api.DeleteVmDeploySetArgs) error { + if args == nil { + return fmt.Errorf("please set argments") + } + req := &api.PostHttpReq{Url: api.GetDeploySetURI() + "/delRelation", Body: args} + err := api.Post(c, req) + return err +} + +// UpdateDeploySet - update deploy set with the specific parameters +// +// PARAMS: +// - args: the arguments to update deploy set +// +// RETURNS: +// - *ListVmServiceResult: the result of get vm services +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateDeploySet(deploySetId string, args *api.CreateDeploySetArgs) error { + if args == nil || deploySetId == "" { + return fmt.Errorf("please set argments") + } + params := make(map[string]string) + params["modifyAttribute"] = "" + req := &api.PostHttpReq{Url: api.GetDeploySetURI() + "/" + deploySetId, Body: args, Params: params} + err := api.Put(c, req) + return err +} + +// GetDeploySetList - get deploy set list with the specific parameters +// RETURNS: +// - *LogicPageDeploySetResult: the result of deploy set list +// - error: nil if ok otherwise the specific error +func (c *Client) GetDeploySetList(args *api.ListRequest) (*api.LogicPageDeploySetResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if args.PageSize != 0 { + params["pageSize"] = strconv.Itoa(args.PageSize) + } + if args.PageNo != 0 { + params["pageNo"] = strconv.Itoa(args.PageNo) + } + if args.KeywordType != "" { + params["keywordType"] = args.KeywordType + } + if args.Keyword != "" { + params["keyword"] = args.Keyword + } + + result := &api.LogicPageDeploySetResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetDeploySetURI() + "/list"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetDeploySetDetail - get vm service detail with the specific parameters +// +// PARAMS: +// - deploySetId: deploy set id +// +// RETURNS: +// - *DeploySetDetails: the result of deploy set detail +// - error: nil if ok otherwise the specific error +func (c *Client) GetDeploySetDetail(deploySetId string) (*api.DeploySetDetails, error) { + if deploySetId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.DeploySetDetails{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetDeploySetURI() + "/" + deploySetId). + WithResult(result). + Do() + return result, err +} + +// DeleteDeploySet - delete deploy set with the specific parameters +// +// PARAMS: +// - deploySetId: deploy set id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteDeploySet(deploySetId string) error { + if deploySetId == "" { + return fmt.Errorf("please set argments") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(api.GetDeploySetURI() + "/" + deploySetId). + Do() + + return err +} diff --git a/bce-sdk-go/services/bec/deploySet_test.go b/bce-sdk-go/services/bec/deploySet_test.go new file mode 100644 index 0000000..d3a2368 --- /dev/null +++ b/bce-sdk-go/services/bec/deploySet_test.go @@ -0,0 +1,66 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +////////////////////////////////////////////// +// deploy set test +////////////////////////////////////////////// + +func TestCreateDeploySet(t *testing.T) { + getReq := &api.CreateDeploySetArgs{ + Name: "wcw_test", + Desc: "wcw-test", + } + res, err := CLIENT.CreateDeploySet(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateDeploySet(t *testing.T) { + getReq := &api.CreateDeploySetArgs{ + Name: "wcw_test", + Desc: "wcw-test-gosdk", + } + err := CLIENT.UpdateDeploySet("dset-y4tumnel", getReq) + ExpectEqual(t.Errorf, nil, err) +} +func TestGetDeploySetList(t *testing.T) { + getReq := &api.ListRequest{} + res, err := CLIENT.GetDeploySetList(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestGetDeploySetDetail(t *testing.T) { + res, err := CLIENT.GetDeploySetDetail("dset-y4tumnel") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestUpdateVmInstanceDeploySet(t *testing.T) { + getReq := &api.UpdateVmDeploySetArgs{ + InstanceId: "vm-dstkrmda-cn-langfang-ct-4thbz", + DeploysetIdList: []string{"dset-y4tumnel"}, + } + err := CLIENT.UpdateVmInstanceDeploySet(getReq) + ExpectEqual(t.Errorf, nil, err) + +} + +func TestDeleteVmInstanceFromDeploySet(t *testing.T) { + getReq := &api.DeleteVmDeploySetArgs{ + DeploysetId: "dset-y4tumnel", + InstanceIdList: []string{"vm-dstkrmda-cn-langfang-ct-4thbz"}, + } + err := CLIENT.DeleteVmInstanceFromDeploySet(getReq) + ExpectEqual(t.Errorf, nil, err) + +} + +func TestDeleteDeploySet(t *testing.T) { + err := CLIENT.DeleteDeploySet("dset-y4tumnel") + ExpectEqual(t.Errorf, nil, err) + +} diff --git a/bce-sdk-go/services/bec/image.go b/bce-sdk-go/services/bec/image.go new file mode 100644 index 0000000..8f19ec8 --- /dev/null +++ b/bce-sdk-go/services/bec/image.go @@ -0,0 +1,151 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BOS service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateVmImage - create a vm image +// +// PARAMS: +// - args: the create vm image args +// +// RETURNS: +// - *api.CreateVmImageResult: the result image +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVmImage(args *api.CreateVmImageArgs) (*api.CreateVmImageResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateVmImageResult{} + req := &api.PostHttpReq{Url: api.GetVmImageURI(), Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// DeleteVmImage - delete a vm image +// +// PARAMS: +// - args: the delete vm image args, spec vmId list +// +// RETURNS: +// - *api.VmImageOperateResult: the result image delete +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVmImage(args []string) (*api.VmImageOperateResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmImageOperateResult{} + params := make(map[string]string) + params["imageIdList"] = strings.Join(args, ",") + req := &api.PostHttpReq{Url: api.GetVmImageURI(), Result: result, Params: params} + err := api.Delete(c, req) + + return result, err +} + +// UpdateVmImage - update a vm image +// +// PARAMS: +// - imageId: image id +// - args: the update vm image args +// +// RETURNS: +// - *api.VmImageOperateResult: the result image update +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateVmImage(imageId string, args *api.UpdateVmImageArgs) (*api.VmImageOperateResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmImageOperateResult{} + req := &api.PostHttpReq{Url: api.GetVmImageURI() + "/" + imageId, Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// ListVmImage - image list +// +// PARAMS: +// - args: the vm image list args +// +// RETURNS: +// - *api.ListVmImageResult: the list of vm images +// - error: nil if ok otherwise the specific error +func (c *Client) ListVmImage(args *api.ListVmImageArgs) (*api.ListVmImageResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if args.PageNo != 0 { + params["PageNo"] = strconv.Itoa(args.PageNo) + } + if args.PageSize != 0 { + params["PageSize"] = strconv.Itoa(args.PageSize) + } + if args.Order != "" { + params["Order"] = args.Order + } + if args.OrderBy != "" { + params["OrderBy"] = args.OrderBy + } + if args.Type != "" { + params["Type"] = args.Type + } + if args.KeywordType != "" { + params["keywordType"] = args.KeywordType + } + if args.Keyword != "" { + params["keyword"] = args.Keyword + } + if args.Status != "" { + params["status"] = args.Status + } + if args.Region != "" { + params["region"] = args.Region + } + if args.OsName != "" { + params["osName"] = args.OsName + } + if args.ServiceId != "" { + params["serviceId"] = args.ServiceId + } + + result := &api.ListVmImageResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmImageURI()). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/bec/image_test.go b/bce-sdk-go/services/bec/image_test.go new file mode 100644 index 0000000..7839cba --- /dev/null +++ b/bce-sdk-go/services/bec/image_test.go @@ -0,0 +1,39 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +////////////////////////////////////////////// +// image API +////////////////////////////////////////////// + +func TestCreateVmImage(t *testing.T) { + getReq := &api.CreateVmImageArgs{VmId: "vm-dstkrmda-cn-jinan-cm-235ew", Name: "wcw-test"} + res, err := CLIENT.CreateVmImage(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateVmImage(t *testing.T) { + getReq := &api.UpdateVmImageArgs{Name: "wcw-test1"} + res, err := CLIENT.UpdateVmImage("im-dqoicjmz", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestListVmImage(t *testing.T) { + getReq := &api.ListVmImageArgs{OsName: "CentOS", Status: "IMAGING"} + res, err := CLIENT.ListVmImage(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteVmImage(t *testing.T) { + req := []string{"im-dqoicjmz"} + res, err := CLIENT.DeleteVmImage(req) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} diff --git a/bce-sdk-go/services/bec/init_test.go b/bce-sdk-go/services/bec/init_test.go new file mode 100644 index 0000000..ccf76b7 --- /dev/null +++ b/bce-sdk-go/services/bec/init_test.go @@ -0,0 +1,68 @@ +package bec + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" +) + +var CLIENT *Client + +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + fmt.Printf("init \n") + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 6; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} diff --git a/bce-sdk-go/services/bec/loadBalancer.go b/bce-sdk-go/services/bec/loadBalancer.go new file mode 100644 index 0000000..fe42f08 --- /dev/null +++ b/bce-sdk-go/services/bec/loadBalancer.go @@ -0,0 +1,588 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateBlb - create lb +// +// PARAMS: +// - args: the lb create args +// +// RETURNS: +// - *api.CreateBlbResult: the create lb result +// - error: nil if ok otherwise the specific error +func (c *Client) CreateBlb(args *api.CreateBlbArgs) (*api.CreateBlbResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateBlbResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI(), Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// DeleteBlb - delete lb +// +// PARAMS: +// - blbId: lb id +// +// RETURNS: +// - *api.DeleteBlbResult: delete lb result +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteBlb(blbId string) (*api.DeleteBlbResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.DeleteBlbResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(api.GetLoadBalancerURI() + "/" + blbId). + WithResult(result). + Do() + + return result, err +} + +// GetBlbList - get lb list +// +// PARAMS: +// - lbType: lb type +// - order: list order +// - orderBy: order by +// - keyword: the key word +// - keywordType: key word type +// - status: lb status +// - region: lb's region +// - pageNo: page NO +// - pageSize: page size +// +// RETURNS: +// - *api.GetBlbListResult: the list of lb +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbList(lbType, order, orderBy, keyword, keywordType, status, region string, + pageNo, pageSize int) (*api.GetBlbListResult, error) { + + params := make(map[string]string) + if order != "" { + params["order"] = order + } + if orderBy != "" { + params["orderBy"] = orderBy + } + if keyword != "" { + params["keyword"] = keyword + } + if keywordType != "" { + params["keywordType"] = keywordType + } + if status != "" { + params["status"] = status + } + if region != "" { + params["region"] = region + } + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + if lbType != "" { + params["lbType"] = lbType + } + + result := &api.GetBlbListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI()). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetBlbDetail - lb detail +// +// PARAMS: +// - blbId: lb id +// +// RETURNS: +// - *api.BlbInstanceVo: lb info +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbDetail(blbId string) (*api.BlbInstanceVo, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BlbInstanceVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId). + WithResult(result). + Do() + + return result, err +} + +// UpdateBlb - update lb +// +// PARAMS: +// - blbId: lb id +// - args: the vm image list args +// +// RETURNS: +// - *api.UpdateBlbResult: update lb result +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateBlb(blbId string, args *api.UpdateBlbArgs) (*api.UpdateBlbResult, error) { + if blbId == "" || args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.UpdateBlbResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId, Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// CreateBlbMonitorPort - create lb monitor port +// +// PARAMS: +// - blbId: lb id +// - args: create lb monitor port args +// +// RETURNS: +// - *api.BlbMonitorResult: create lb monitor port result +// - error: nil if ok otherwise the specific error +func (c *Client) CreateBlbMonitorPort(blbId string, args *api.BlbMonitorArgs) (*api.BlbMonitorResult, error) { + if blbId == "" || args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BlbMonitorResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/monitor", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// DeleteBlbMonitorPort - delete lb monitor port +// +// PARAMS: +// - blbId: lb id +// - args: delete lb monitor port args +// +// RETURNS: +// - *api.BlbMonitorResult: delete lb monitor result +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteBlbMonitorPort(blbId string, args *[]api.Port) (*api.BlbMonitorResult, error) { + if blbId == "" || args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BlbMonitorResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/monitor", Body: args, Result: result} + err := api.Delete(c, req) + + return result, err +} + +// GetBlbMonitorPortList - get lb's monitor port list +// +// PARAMS: +// - blbId: lb id +// - pageNo: page no +// - pageSize: page size +// +// RETURNS: +// - *api.BlbMonitorListResult: the list of lb monitor ports +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbMonitorPortList(blbId string, pageNo, pageSize int) (*api.BlbMonitorListResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + params := make(map[string]string) + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + result := &api.BlbMonitorListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId + "/monitor"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// UpdateBlbMonitorPort - update lb monitor port +// +// PARAMS: +// - blbId: lb id +// - args: monitor info args +// +// RETURNS: +// - *api.BlbMonitorResult: update lb monitor result +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateBlbMonitorPort(blbId string, args *api.BlbMonitorArgs) (*api.BlbMonitorResult, error) { + if blbId == "" || args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BlbMonitorResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/monitor", Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// GetBlbMonitorPortDetails - get lb monitor port detail +// +// PARAMS: +// - blbId: lb id +// - protocol: protocol +// - port: port +// +// RETURNS: +// - *api.BlbMonitorArgs: lb monitor info result +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbMonitorPortDetails(blbId string, protocol api.Protocol, port int) (*api.BlbMonitorArgs, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if protocol != "" { + params["protocol"] = string(protocol) + } + if port != 0 { + params["port"] = strconv.Itoa(port) + } + result := &api.BlbMonitorArgs{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId + "/monitor/port"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// BatchCreateBlb - batch create lb +// +// PARAMS: +// - args: batch create lb args +// +// RETURNS: +// - *api.BatchCreateBlbResult: the result of batch create lb +// - error: nil if ok otherwise the specific error +func (c *Client) BatchCreateBlb(args *api.BatchCreateBlbArgs) (*api.BatchCreateBlbResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BatchCreateBlbResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/batch/create", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// BatchDeleteBlb - batch delete lb +// +// PARAMS: +// - blbIdList: the list of lb +// +// RETURNS: +// - *api.BatchDeleteBlbResult: the result of batch delete lb +// - error: nil if ok otherwise the specific error +func (c *Client) BatchDeleteBlb(blbIdList []string) (*api.BatchDeleteBlbResult, error) { + if blbIdList == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BatchDeleteBlbResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/batch/delete", Result: result, Body: blbIdList} + err := api.Post(c, req) + + return result, err +} + +// BatchCreateBlbMonitor - batch create lb monitor +// +// PARAMS: +// - blbId: lb id +// - args: batch create lb monitor args +// +// RETURNS: +// - *api.BatchCreateBlbMonitorResult: the result of batch create lb nonitor +// - error: nil if ok otherwise the specific error +func (c *Client) BatchCreateBlbMonitor(blbId string, args *api.BatchCreateBlbMonitorArg) (*api.BatchCreateBlbMonitorResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.BatchCreateBlbMonitorResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerBatchURI() + "/create/" + blbId + "/monitor", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// GetBlbBackendPodList - get lb backend list +// +// PARAMS: +// - blbId: lb id +// - pageNo: page NO +// - pageSize: page size +// +// RETURNS: +// - *api.GetBlbBackendPodListResult: the result of lb backend list +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbBackendPodList(blbId string, pageNo, pageSize int) (*api.GetBlbBackendPodListResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + result := &api.GetBlbBackendPodListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId + "/binded"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetBlbBackendBindingStsList - get lb backend's statefulset list +// +// PARAMS: +// - blbId: lb id +// - keyword: the key word +// - keywordType: key word type +// - pageNo: page NO +// - pageSize: page size +// +// RETURNS: +// - *api.GetBlbBackendBindingStsListResult: the list of sts result +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbBackendBindingStsList(blbId string, pageNo, pageSize int, keywordType, keyword string) (*api.GetBlbBackendBindingStsListResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + if keywordType != "" { + params["keywordType"] = keywordType + } + + if keyword != "" { + params["keyword"] = keyword + } + + result := &api.GetBlbBackendBindingStsListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId + "/binding"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetBlbBindingPodListWithSts - image list +// +// PARAMS: +// - blbId: lb id +// - stsName: sts name +// +// RETURNS: +// - *api.Backends: the list of backend +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbBindingPodListWithSts(blbId, stsName string) (*[]api.Backends, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if stsName != "" { + params["stsName"] = stsName + } + + result := &[]api.Backends{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerURI() + "/" + blbId + "/bindingpod"). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// CreateBlbBinding - create lb binding +// +// PARAMS: +// - blbId: lb id +// - args: create lb binding args +// +// RETURNS: +// - *api.CreateBlbBindingResult: the result of lb binding +// - error: nil if ok otherwise the specific error +func (c *Client) CreateBlbBinding(blbId string, args *api.CreateBlbBindingArgs) (*api.CreateBlbBindingResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateBlbBindingResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/binding", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// DeleteBlbBindPod - delete lb bind pod +// +// PARAMS: +// - blbId: lb id +// - args: delete lb bind pod args +// +// RETURNS: +// - *api.DeleteBlbBindPodResult: the result of delete lb pod +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteBlbBindPod(blbId string, args *api.DeleteBlbBindPodArgs) (*api.DeleteBlbBindPodResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.DeleteBlbBindPodResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/binded", Result: result, Body: args} + err := api.Delete(c, req) + + return result, err +} + +// UpdateBlbBindPodWeight - update bind pod weight +// +// PARAMS: +// - blbId: lb id +// - args: update bind pod weight args +// +// RETURNS: +// - *api.UpdateBindPodWeightResult: the result of update bind pod weight +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateBlbBindPodWeight(blbId string, args *api.UpdateBindPodWeightArgs) (*api.UpdateBindPodWeightResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.UpdateBindPodWeightResult{} + req := &api.PostHttpReq{Url: api.GetLoadBalancerURI() + "/" + blbId + "/binded", Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// GetBlbMetrics - get lb metrics +// +// PARAMS: +// - blbId: lb id +// - ipType: ip type +// - port: port +// - serviceProviderStr: service Provider +// - offsetInSeconds: offset Seconds +// - metricsType: metrics Type +// +// RETURNS: +// - *api.ServiceMetricsResult: the list of vm images +// - error: nil if ok otherwise the specific error +func (c *Client) GetBlbMetrics(blbId, ipType, port, serviceProviderStr string, start, end, stepInMin int, metricsType api.MetricsType) (*api.ServiceMetricsResult, error) { + if blbId == "" { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + params["blbId"] = blbId + if port != "" { + params["port"] = port + } + + if serviceProviderStr != "" { + params["serviceProvider"] = serviceProviderStr + } + + if ipType != "" { + params["ipType"] = ipType + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + + if end != 0 { + params["end"] = strconv.Itoa(end) + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetLoadBalancerMonitorURI() + "/" + blbId). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/bec/loadBalancer_test.go b/bce-sdk-go/services/bec/loadBalancer_test.go new file mode 100644 index 0000000..d805589 --- /dev/null +++ b/bce-sdk-go/services/bec/loadBalancer_test.go @@ -0,0 +1,180 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// //////////////////////////////////////////// +// Loadbalancer API +// //////////////////////////////////////////// +func TestCreateBlb(t *testing.T) { + getReq := &api.CreateBlbArgs{ + BlbName: "gosdk-test", + RegionId: "cn-hangzhou-cm", + LbType: "vm", + NetworkType: "vpc", + } + res, err := CLIENT.CreateBlb(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateBlbVpc(t *testing.T) { + getReq := &api.CreateBlbArgs{ + BlbName: "gosdk-test1", + RegionId: "cn-baoding-ix", + LbType: "vm", + NetworkType: "vpc", + SubServiceProviders: []string{"cm"}, + } + res, err := CLIENT.CreateBlb(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteBlb(t *testing.T) { + res, err := CLIENT.DeleteBlb("xxxx") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbList(t *testing.T) { + res, err := CLIENT.GetBlbList("vm", "desc", "createTime", "", "", + "", "", 1, 100) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbDetail(t *testing.T) { + res, err := CLIENT.GetBlbDetail("applb-cn-hangzhou-cm-wkfdcbin") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateBlb(t *testing.T) { + getReq := &api.UpdateBlbArgs{BlbName: "applb-cn-hangzhou-cm-wkfdcbin", BandwidthInMbpsLimit: 1000, Type: "bandwidth"} + res, err := CLIENT.UpdateBlb("applb-cn-hangzhou-cm-wkfdcbin", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateBlbMonitorPort(t *testing.T) { + + str := "" + t.Logf("%v", &str) + getReq := &api.BlbMonitorArgs{LbMode: api.LbModeWrr, FrontendPort: &api.Port{Protocol: api.ProtocolTcp, Port: 80}, + BackendPort: 80, HealthCheck: &api.HealthCheck{HealthCheckString: &str, HealthCheckType: "tcp", + HealthyThreshold: 1000, UnhealthyThreshold: 1000, TimeoutInSeconds: 50, IntervalInSeconds: 3}} + res, err := CLIENT.CreateBlbMonitorPort("applb-cn-hangzhou-cm-wkfdcbin", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteBlbMonitorPort(t *testing.T) { + req := []api.Port{{Protocol: api.ProtocolUdp, Port: 81}} + res, err := CLIENT.DeleteBlbMonitorPort("applb-cn-hangzhou-cm-3vkqupsp", &req) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbMonitorPortList(t *testing.T) { + res, err := CLIENT.GetBlbMonitorPortList("applb-cn-hangzhou-cm-wkfdcbin", 1, 100) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateBlbMonitorPort(t *testing.T) { + str := "" + getReq := &api.BlbMonitorArgs{LbMode: api.LbModeWrr, FrontendPort: &api.Port{Protocol: api.ProtocolTcp, Port: 80}, + BackendPort: 8090, HealthCheck: &api.HealthCheck{HealthCheckString: &str, HealthCheckType: "tcp", + HealthyThreshold: 1000, UnhealthyThreshold: 1000, TimeoutInSeconds: 60, IntervalInSeconds: 3}} + res, err := CLIENT.UpdateBlbMonitorPort("applb-cn-hangzhou-cm-wkfdcbin", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbMonitorPortDetails(t *testing.T) { + res, err := CLIENT.GetBlbMonitorPortDetails("applb-cn-hangzhou-cm-3vkqupsp", api.ProtocolTcp, 80) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbBackendPodList(t *testing.T) { + res, err := CLIENT.GetBlbBackendPodList("applb-cn-langfang-ct-whak2ian", 0, 0) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbBackendBindingStsList(t *testing.T) { + res, err := CLIENT.GetBlbBackendBindingStsList("applb-cn-langfang-ct-whak2ian", 0, 0, "", "") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbBindingPodListWithSts(t *testing.T) { + res, err := CLIENT.GetBlbBindingPodListWithSts("lb-pclalzin", "sts-loy3f9tk-2-t-langfang") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateBlbBinding(t *testing.T) { + getReq := &api.CreateBlbBindingArgs{BindingForms: &[]api.BlbBindingForm{ + api.BlbBindingForm{DeploymentId: "vm-xqjitfy1-cn-langfang-ct", PodWeight: &[]api.Backends{ + api.Backends{Name: "vm-xqjitfy1-cn-langfang-ct-d1f7x", Ip: "172.16.8.22", Weight: 100}}, + }}} + res, err := CLIENT.CreateBlbBinding("lb-jgvg4pbz", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteBlbBindPod(t *testing.T) { + getReq := &api.DeleteBlbBindPodArgs{PodWeightList: &[]api.Backends{ + api.Backends{Name: "vm-xqjitfy1-cn-langfang-ct-d1f7x", Ip: "172.16.8.22", Weight: 100}}, + DeploymentIds: []string{"vvm-xqjitfy1-cn-langfang-ct"}} + res, err := CLIENT.DeleteBlbBindPod("lb-jgvg4pbz", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateBlbBindPodWeight(t *testing.T) { + getReq := &api.UpdateBindPodWeightArgs{PodWeightList: &[]api.Backends{ + api.Backends{Name: "vm-xxxxx", Ip: "172.16.xx.xx", Weight: 10}}, + DeploymentIds: []string{"vmrs-xxxxxx"}} + res, err := CLIENT.UpdateBlbBindPodWeight("lb-xxxxx", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetBlbMetrics(t *testing.T) { + res, err := CLIENT.GetBlbMetrics("applb-cn-langfang-ct-lpcfbjv6", "extranet", "", "", + 1657613108, 1660291508, 1, api.MetricsTypeTrafficReceive) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBatchCreateBlb(t *testing.T) { + getReq := &api.BatchCreateBlbArgs{BlbName: "xxxx-test", DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{Region: api.RegionSouthChina, Replicas: 1, City: "GUANGZHOU", ServiceProvider: api.ServiceChinaUnicom}, + }, LbType: "vm"} + res, err := CLIENT.BatchCreateBlb(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBatchDeleteBlb(t *testing.T) { + res, err := CLIENT.BatchDeleteBlb([]string{"lb-xxxx", "lb-xxxx-2", "lb-xxxx-3"}) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBatchCreateBlbMonitor(t *testing.T) { + str := "\\00\\01\\01\\00\\01\\00\\00\\00\\00\\00\\00\\05baidu\\03com\\00\\00\\01\\00\\01" + getReq := &api.BatchCreateBlbMonitorArg{Protocol: api.ProtocolUdp, LbMode: api.LbModeWrr, HealthCheck: &api.HealthCheck{HealthCheckString: &str, HealthCheckType: "udp", + HealthyThreshold: 1000, UnhealthyThreshold: 1000, TimeoutInSeconds: 60, IntervalInSeconds: 3}, PortGroups: &[]api.PortGroup{api.PortGroup{ + Port: 82, BackendPort: 82}, api.PortGroup{Port: 443, BackendPort: 443}}} + res, err := CLIENT.BatchCreateBlbMonitor("applb-cn-hangzhou-cm-3vkqupsp", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} diff --git a/bce-sdk-go/services/bec/node.go b/bce-sdk-go/services/bec/node.go new file mode 100644 index 0000000..74daa19 --- /dev/null +++ b/bce-sdk-go/services/bec/node.go @@ -0,0 +1,45 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// GetBecAvailableNodeInfoVo - get available node +// +// PARAMS: +// - args: the type +// +// RETURNS: +// - *api.GetBecAvailableNodeInfoVoResult: get available node +// - error: nil if ok otherwise the specific error +func (c *Client) GetBecAvailableNodeInfoVo(getType string) (*api.GetBecAvailableNodeInfoVoResult, error) { + if getType == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.GetBecAvailableNodeInfoVoResult{} + req := &api.GetHttpReq{Url: api.GetNodeInfoURI() + "/type/" + getType, Result: result} + err := api.Get(c, req) + + return result, err +} diff --git a/bce-sdk-go/services/bec/node_test.go b/bce-sdk-go/services/bec/node_test.go new file mode 100644 index 0000000..0cfe86c --- /dev/null +++ b/bce-sdk-go/services/bec/node_test.go @@ -0,0 +1,14 @@ +package bec + +import ( + "testing" +) + +// //////////////////////////////////////////// +// node API +// //////////////////////////////////////////// +func TestGetBecAvailableNodeInfoVo(t *testing.T) { + res, err := CLIENT.GetBecAvailableNodeInfoVo("vm") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} diff --git a/bce-sdk-go/services/bec/scenario_test.go b/bce-sdk-go/services/bec/scenario_test.go new file mode 100644 index 0000000..29c7ce4 --- /dev/null +++ b/bce-sdk-go/services/bec/scenario_test.go @@ -0,0 +1,305 @@ +package bec + +import ( + "fmt" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// //////////////////////////////////////////// +// vmService API +// //////////////////////////////////////////// +// 定义参数 城市、运营商中文名、镜像名称、服务名称 +func TestVmServiceSce(t *testing.T) { + cityName := "杭州" + serviceProviderName := "移动" + image := "fyy-test" + name := "bec-tst" + //创建无实例虚机服务,并获取服务ID + getReq := &api.CreateVmServiceArgs{ServiceName: name} + resVm, err := CLIENT.CreateVmService(getReq) + serviceId := resVm.Details.ServiceId + // 根据城市与运营商中文名称获取城市与运营商编号还有REGION编号 + var city string + var provider api.ServiceProvider + var region api.Region + resNode, err := CLIENT.GetBecAvailableNodeInfoVo("vm") + if err == nil { + for _, v := range resNode.RegionList { + for _, cityInfo := range v.CityList { + if cityInfo.Name == cityName { + for _, providerInfo := range cityInfo.ServiceProviderList { + if providerInfo.Name == serviceProviderName { + city = cityInfo.City + provider = providerInfo.ServiceProvider + region = v.Region + } + } + } + } + } + } + + // 根据镜像名称获取镜像ID + imageReq := &api.ListVmImageArgs{KeywordType: "name", Keyword: image} + res, err := CLIENT.ListVmImage(imageReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + return + } + if len(res.Result) == 0 { + err = fmt.Errorf("no such image name %s", image) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", "no such image name") + return + } + imageId := res.Result[0].ImageId + + // 创建虚机,并获取虚机ID + createVmiReq := &api.CreateVmServiceArgs{ImageId: imageId, Cpu: 1, Memory: 2, NeedPublicIp: true, Bandwidth: 50, + DeployInstances: &[]api.DeploymentInstance{api.DeploymentInstance{City: city, Region: region, + ServiceProvider: provider, Replicas: 1}}, AdminPass: "xxxxxx111xxB@", ImageType: api.ImageTypeBec} + createVmiRes, err := CLIENT.CreateVmServiceInstance(serviceId, createVmiReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + return + } + + vmiId := createVmiRes.Details.Instances[0].VmId + + // 每隔10秒获取一次虚机创建状态,并打印虚机状态,直到虚机进入运行中 + var stateRes *api.VmInstanceDetailsVo + ticker := time.NewTicker(10 * time.Second) + errTime := 3 + in := true + for in { + select { + case <-ticker.C: + //stateRes, err := CLIENT.GetVmServiceDetail(vmId) + stateRes, err = CLIENT.GetVirtualMachine(vmiId) + if err != nil && errTime > 0 { + errTime-- + continue + } + if errTime == 0 { + in = false + break + } + fmt.Println("vm status is ", stateRes.Status) + if stateRes != nil && stateRes.Status == api.ResourceStatusRunning { + in = false + break + } + } + } + + // 打印指定虚机的内外网IP、虚机名称、ID + vmStatusRes, err := CLIENT.GetVirtualMachine(vmiId) + fmt.Printf("ip:%s, internalIp:%s, vmName:%s, vmId: %s\n", vmStatusRes.PublicIp, + vmStatusRes.InternalIp, vmStatusRes.VmName, vmStatusRes.VmId) + + // 添加虚机辅助IP + crateVmPrivateIpReq := &api.CreateVmPrivateIpForm{SecondaryPrivateIpAddressCount: 1} + _, err = CLIENT.CreateVmPrivateIp(vmiId, crateVmPrivateIpReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + return + } + + // 删除虚机辅助IP + GetVmRes, err := CLIENT.GetVirtualMachine(vmiId) + privateIp := GetVmRes.PrivateIps + deleteVmPrivateIpReq := &api.DeleteVmPrivateIpForm{PrivateIps: privateIp} + delRes, err := CLIENT.DeleteVmPrivateIp(vmiId, deleteVmPrivateIpReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", delRes) + + // 重置虚机密码 + updateVmReq := &api.UpdateVmInstanceArgs{Type: "password", KeyConfig: &api.KeyConfig{Type: "password", AdminPass: "12345asdf@"}} + updateVmRes, err := CLIENT.UpdateVmInstance(vmiId, updateVmReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", updateVmRes) + + // 每隔10秒获取一次虚机创建状态,并打印虚机状态,直到虚机进入运行中 + ticker = time.NewTicker(10 * time.Second) + errTime = 3 + in = true + for in { + select { + case <-ticker.C: + //stateRes, err := CLIENT.GetVmServiceDetail(vmId) + stateRes, err = CLIENT.GetVirtualMachine(vmiId) + if err != nil && errTime > 0 { + errTime-- + continue + } + if errTime == 0 { + in = false + break + } + fmt.Println("vm status is ", stateRes.Status) + if stateRes != nil && stateRes.Status == api.ResourceStatusRunning { + in = false + break + } + } + } + + // 关机虚机,每隔10秒获取一次虚机创建状态,并打印虚机状态,直到虚机关机 + operateVmRes, err := CLIENT.OperateVmDeployment(vmiId, api.VmInstanceBatchOperateStop) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", operateVmRes) + return + } + + var vmStateRes *api.VmInstanceDetailsVo + ticker = time.NewTicker(10 * time.Second) + errTime = 3 + in = true + for in { + select { + case <-ticker.C: + //stateRes, err := CLIENT.GetVmServiceDetail(vmId) + vmStateRes, err = CLIENT.GetVirtualMachine(vmiId) + if err != nil && errTime > 0 { + errTime-- + continue + } + if errTime == 0 { + in = false + break + } + fmt.Println("vm status is ", vmStateRes.Status) + if vmStateRes != nil && vmStateRes.Status == api.ResourceStatusStopped { + in = false + break + } + } + } + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", vmStateRes) + // 删除虚机 + deleteVmRes, err := CLIENT.DeleteVmInstance(vmiId) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", deleteVmRes) + // 删除服务 + deleteVmServiceRes, err := CLIENT.DeleteVmService(serviceId) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", deleteVmServiceRes) +} + +// 创建LocalDNS虚机 +func TestCreateVmServiceWithLocalDns(t *testing.T) { + getReq := &api.CreateVmServiceArgs{ServiceName: "xxxxxxx@-local", ImageId: "im-dikfttnj-3-u-guangzhou", AdminPass: "x123xxx@", + SystemVolume: &api.SystemVolumeConfig{VolumeType: api.DiskTypeNVME}, Cpu: 1, Memory: 2, + DeployInstances: &[]api.DeploymentInstance{api.DeploymentInstance{City: "HANGZHOU", Region: api.RegionEastChina, + ServiceProvider: api.ServiceChinaMobile, Replicas: 1}}, ImageType: api.ImageTypeBec, KeyConfig: &api.KeyConfig{Type: "password", AdminPass: "xxxx123@"}, + DnsConfig: &api.DnsConfig{DnsType: "LOCAL"}} + res, err := CLIENT.CreateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +// 创建密钥对虚机 +func TestCreateVmServiceWithKeypair(t *testing.T) { + getReq := &api.CreateVmServiceArgs{ServiceName: "xxxxxxx@-key", ImageId: "im-dikfttnj-3-u-guangzhou", + SystemVolume: &api.SystemVolumeConfig{VolumeType: api.DiskTypeNVME}, Cpu: 1, Memory: 2, + DeployInstances: &[]api.DeploymentInstance{api.DeploymentInstance{City: "HANGZHOU", Region: api.RegionEastChina, + ServiceProvider: api.ServiceChinaMobile, Replicas: 1}}, ImageType: api.ImageTypeBec, KeyConfig: &api.KeyConfig{Type: "bccKeyPair", BccKeyPairIdList: []string{"k-r4FmM6flink"}}} + res, err := CLIENT.CreateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBlbScenario(t *testing.T) { + // 创建负载均衡器 + // 每隔10秒获取一次负载均衡器状态,并打印负载均衡器状态与IP,直到创建完成 + getReq := &api.CreateBlbArgs{BlbName: "xxxx-test", Region: api.RegionEastChina, + City: "HANGZHOU", LbType: "vm", ServiceProvider: api.ServiceChinaMobile} + res, err := CLIENT.CreateBlb(getReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + return + } + + lbId := res.Details.BlbId + var stateRes *api.BlbInstanceVo + ticker := time.NewTicker(10 * time.Second) + errTime := 3 + in := true + for in { + select { + case <-ticker.C: + //stateRes, err := CLIENT.GetVmServiceDetail(vmId) + stateRes, err = CLIENT.GetBlbDetail(lbId) + if err != nil && errTime > 0 { + errTime-- + continue + } + if errTime == 0 { + in = false + break + } + fmt.Printf("lb status is %s, ip is %s", stateRes.Status, stateRes.PublicIp) + if stateRes != nil && stateRes.Status == api.ResourceStatusRunning { + in = false + break + } + } + } + + // 创建TCP监听 + str := "" + blbMonitorReq := &api.BlbMonitorArgs{LbMode: api.LbModeWrr, FrontendPort: &api.Port{Protocol: api.ProtocolTcp, Port: 80}, + BackendPort: 80, HealthCheck: &api.HealthCheck{HealthCheckString: &str, HealthCheckType: "tcp", + HealthyThreshold: 1000, UnhealthyThreshold: 1000, TimeoutInSeconds: 60, IntervalInSeconds: 3}} + blbMonitorRes, err := CLIENT.CreateBlbMonitorPort(lbId, blbMonitorReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", blbMonitorRes) + return + } + + vmiId := "vm-brcvrwvt-1-m-hangzhou-ehkrm" + // 按虚机添加后端服务器 + createBlbBindingReq := &api.CreateBlbBindingArgs{BindingForms: &[]api.BlbBindingForm{ + api.BlbBindingForm{DeploymentId: vmiId, PodWeight: &[]api.Backends{ + api.Backends{Name: vmiId, Weight: 100}}, + }}} + createBlbBindingRes, err := CLIENT.CreateBlbBinding(lbId, createBlbBindingReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", createBlbBindingRes) + return + } + + // 查询负载均衡器状态,打印负载均衡器后端服务器 + getBlbBackendPodRes, err := CLIENT.GetBlbBackendPodList(lbId, 0, 0) + if len(getBlbBackendPodRes.Result) > 0 { + for _, v := range getBlbBackendPodRes.Result { + fmt.Println("backend rs is ", v.PodName) + } + } + + // 从后端服务器删除虚机 + deleteBlbBindPodReq := &api.DeleteBlbBindPodArgs{PodWeightList: &[]api.Backends{ + api.Backends{Name: vmiId}}} + deleteBlbBindPodRes, err := CLIENT.DeleteBlbBindPod(lbId, deleteBlbBindPodReq) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", deleteBlbBindPodRes) + } + + // 删除负载均衡器 + deleteRes, err := CLIENT.DeleteBlb(lbId) + if err != nil { + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", deleteRes) + } +} diff --git a/bce-sdk-go/services/bec/service.go b/bce-sdk-go/services/bec/service.go new file mode 100644 index 0000000..a800249 --- /dev/null +++ b/bce-sdk-go/services/bec/service.go @@ -0,0 +1,490 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateService - create a container service with the specific parameters +// +// PARAMS: +// - args: the arguments to create a container service +// +// RETURNS: +// - *CreateClusterResult: the result of create a container service +// - error: nil if ok otherwise the specific error +func (c *Client) CreateService(args *api.CreateServiceArgs) (*api.CreateServiceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateServiceResult{} + req := &api.PostHttpReq{Url: api.GetServiceURI() + "/create", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// ListService - list container service with the specific parameters +// +// PARAMS: +// - pageNo: page No +// - pageSize: page Size +// - keywordType: keyword Type +// - keyword: keyword +// - order: order +// - orderBy: orderBy +// - status: status +// +// RETURNS: +// - *ListServiceResult: the result of list container service +// - error: nil if ok otherwise the specific error +func (c *Client) ListService(pageNo, pageSize int, keywordType, keyword, order, orderBy, status string) (*api.ListServiceResult, error) { + + params := make(map[string]string) + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if keyword != "" { + params["keyword"] = keyword + } + if keywordType != "" { + params["keywordType"] = keywordType + } + if status != "" { + params["status"] = status + } + if order != "" { + params["order"] = order + } + if orderBy != "" { + params["orderBy"] = orderBy + } + + result := &api.ListServiceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetServiceURI()). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetService - get container service with the specific parameters +// +// PARAMS: +// - serviceId: the service id +// +// RETURNS: +// - *ServiceBriefVo: the result of get container service +// - error: nil if ok otherwise the specific error +func (c *Client) GetService(serviceId string) (*api.ServiceDetailsVo, error) { + result := &api.ServiceDetailsVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetServiceDetailURI(serviceId)). + WithResult(result). + Do() + + return result, err +} + +// ServiceAction - operate service with the specific parameters +// +// PARAMS: +// - serviceId: the service id +// - action: operate action +// +// RETURNS: +// - *ServiceActionResult: the result of operate service +// - error: nil if ok otherwise the specific error +func (c *Client) ServiceAction(serviceId string, action api.ServiceAction) (*api.ServiceActionResult, error) { + result := &api.ServiceActionResult{} + req := &api.PostHttpReq{Url: api.GetStartServiceURI(serviceId, string(action)), Result: result, Body: nil} + err := api.Put(c, req) + + return result, err +} + +// UpdateService - update service with the specific parameters +// +// PARAMS: +// - serviceId: the service id +// - args: the arguments to update service +// +// RETURNS: +// - *UpdateServiceResult: the result of update service +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateService(serviceId string, args *api.UpdateServiceArgs) (*api.UpdateServiceResult, error) { + if args == nil { + args = &api.UpdateServiceArgs{} + } + + result := &api.UpdateServiceResult{} + req := &api.PostHttpReq{Url: api.GetUpdateServiceURI(serviceId), Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// DeleteService - delete service with the specific parameters +// +// PARAMS: +// - serviceId: the service id +// +// RETURNS: +// - *ServiceActionResult: the result of delete service +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteService(serviceId string) (*api.ServiceActionResult, error) { + result := &api.ServiceActionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(api.GetDeleteServiceURI(serviceId)). + WithResult(result). + Do() + + return result, err +} + +// GetServiceMetrics - get service metrics with the specific parameters +// +// PARAMS: +// - serviceId: the service id +// - metricsType: metrics Type +// - serviceProviderStr: service Provider +// - offsetInSeconds: offset Seconds +// +// RETURNS: +// - *ServiceMetricsResult: the result of get service metrics +// - error: nil if ok otherwise the specific error +func (c *Client) GetServiceMetrics(serviceId string, metricsType api.MetricsType, serviceProviderStr api.ServiceProvider, start, end, stepInMin int) (*api.ServiceMetricsResult, error) { + params := make(map[string]string) + if serviceProviderStr != "" { + params["serviceProvider"] = string(serviceProviderStr) + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + if serviceId != "" { + params["serviceId"] = string(serviceId) + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if end != 0 { + params["end"] = strconv.Itoa(end) + } + + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetServiceMetricsURI(serviceId)). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// ServiceBatchOperate - batch operate service with the specific parameters +// +// PARAMS: +// - args: the arguments to batch operate service +// +// RETURNS: +// - *ServiceBatchOperateResult: the result of batch operate service +// - error: nil if ok otherwise the specific error +func (c *Client) ServiceBatchOperate(args *api.ServiceBatchOperateArgs) (*api.ServiceBatchOperateResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + if args.Action != "start" && args.Action != "stop" { + return nil, fmt.Errorf("action is start|stop, please check") + } + + result := &api.ServiceBatchOperateResult{} + req := &api.PostHttpReq{Url: api.GetBachServiceOperateURI(), Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// ServiceBatchDelete - batch delete service with the specific parameters +// +// PARAMS: +// - args: the arguments to batch delete service +// +// RETURNS: +// - *ServiceBatchOperateResult: the result of batch delete service +// - error: nil if ok otherwise the specific error +func (c *Client) ServiceBatchDelete(args *[]string) (*api.ServiceBatchOperateResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.ServiceBatchOperateResult{} + req := &api.PostHttpReq{Url: api.GetBachServiceDeleteURI(), Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// GetPodDeployment - get pod deployment with the specific parameters +// +// PARAMS: +// - deploymentId: the deploymentId id +// +// RETURNS: +// - *DeploymentResourceBriefVo: the result of get pod deployment +// - error: nil if ok otherwise the specific error +func (c *Client) GetPodDeployment(deploymentId string) (*api.DeploymentResourceBriefVo, error) { + result := &api.DeploymentResourceBriefVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetDeploymentDetailURI(deploymentId)). + WithResult(result). + Do() + + return result, err +} + +// GetPodDeploymentMetrics - get Pod Deployment metrics with the specific parameters +// +// PARAMS: +// - deploymentId: the pod deployment id +// - metricsType: metrics Type +// - serviceProviderStr: service Provider +// - offsetInSeconds: offset Seconds +// +// RETURNS: +// - *ServiceMetricsResult: the result of get Pod Deployment metrics +// - error: nil if ok otherwise the specific error +func (c *Client) GetPodDeploymentMetrics(deploymentId string, metricsType api.MetricsType, serviceProviderStr api.ServiceProvider, start, end, stepInMin int) (*api.ServiceMetricsResult, error) { + params := make(map[string]string) + if serviceProviderStr != "" { + params["serviceProvider"] = string(serviceProviderStr) + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + if deploymentId != "" { + params["deploymentId"] = deploymentId + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if end != 0 { + params["end"] = strconv.Itoa(end) + } + + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetDeploymentMetricsURI(deploymentId)). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// UpdatePodDeploymentReplicas - update pod deployment replicas with the specific parameters +// +// PARAMS: +// - deploymentId: the deploymentId id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdatePodDeploymentReplicas(deploymentId string, args *api.UpdateDeploymentReplicasRequest) (*api.ActionInfoVo, error) { + if args == nil || deploymentId == "" { + return nil, fmt.Errorf("please set argments") + } + res := &api.ActionInfoVo{} + req := &api.PostHttpReq{Url: api.DEPLOYMENT_URL + "/" + deploymentId, Result: res, Body: args} + err := api.Put(c, req) + return res, err +} + +// DeletePodDeployment - delete pod deployment with the specific parameters +// +// PARAMS: +// - deploymentIDs: the deployment id array +// +// RETURNS: +// - *ServiceActionResult: the result of delete service +// - error: nil if ok otherwise the specific error +func (c *Client) DeletePodDeployment(args *[]string) (*api.DeleteDeploymentActionInfoVo, error) { + result := &api.DeleteDeploymentActionInfoVo{} + req := &api.PostHttpReq{Url: api.DEPLOYMENT_URL, Body: args, Result: result} + err := api.Delete(c, req) + return result, err +} + +// GetPodList - list pod with the specific parameters +// +// PARAMS: +// - pageNo: page No +// - pageSize: page Size +// - keyword: keyword +// - order: order +// - orderBy: orderBy +// +// RETURNS: +// - *ListPodResult: the result of list pod +// - error: nil if ok otherwise the specific error +func (c *Client) GetPodList(pageNo, pageSize int, keyword, order, orderBy, serviceId, deploymentId string) (*api.ListPodResult, error) { + + params := make(map[string]string) + if pageNo != 0 { + params["pageNo"] = strconv.Itoa(pageNo) + } + if pageSize != 0 { + params["pageSize"] = strconv.Itoa(pageSize) + } + if keyword != "" { + params["keyword"] = keyword + } + if order != "" { + params["order"] = order + } + if orderBy != "" { + params["orderBy"] = orderBy + } + if serviceId != "" { + params["serviceId"] = serviceId + } + if deploymentId != "" { + params["deploymentId"] = deploymentId + } + + result := &api.ListPodResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.REQUEST_POD_URL). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetPodMetrics - get Pod metrics with the specific parameters +// +// PARAMS: +// - deploymentId: the pod deployment id +// - metricsType: metrics Type +// - serviceProviderStr: service Provider +// - offsetInSeconds: offset Seconds +// +// RETURNS: +// - *ServiceMetricsResult: the result of get Pod Deployment metrics +// - error: nil if ok otherwise the specific error +func (c *Client) GetPodMetrics(podId string, metricsType api.MetricsType, serviceProviderStr api.ServiceProvider, start, end, stepInMin int) (*api.ServiceMetricsResult, error) { + params := make(map[string]string) + if serviceProviderStr != "" { + params["serviceProvider"] = string(serviceProviderStr) + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + if podId != "" { + params["podId"] = podId + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if end != 0 { + params["end"] = strconv.Itoa(end) + } + + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetPodMetricsURI(podId)). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetPodDetail - get pod detail with the specific parameters +// +// PARAMS: +// - podId: pod id +// +// RETURNS: +// - *ListPodResult: the result of list pod +// - error: nil if ok otherwise the specific error +func (c *Client) GetPodDetail(podId string) (*api.PodDetailVo, error) { + + if podId == "" { + return nil, fmt.Errorf("please set argments") + } + result := &api.PodDetailVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.REQUEST_POD_URL + "/" + podId). + WithResult(result). + Do() + + return result, err +} + +// RestartPod - restart pod with the specific parameters +// +// PARAMS: +// - podId: pod id +// +// RETURNS: +// - *ListPodResult: the result of restart pod +// - error: nil if ok otherwise the specific error +func (c *Client) RestartPod(podId string) error { + + if podId == "" { + return fmt.Errorf("please set argments") + } + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(api.REQUEST_POD_URL + "/" + podId + "/restart"). + Do() + + return err +} diff --git a/bce-sdk-go/services/bec/service_test.go b/bce-sdk-go/services/bec/service_test.go new file mode 100644 index 0000000..c7ab8d1 --- /dev/null +++ b/bce-sdk-go/services/bec/service_test.go @@ -0,0 +1,161 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// //////////////////////////////////////////// +// service API +// //////////////////////////////////////////// +func TestListService(t *testing.T) { + res, err := CLIENT.ListService(1, 100, + "", "", "", "", "") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetService(t *testing.T) { + res, err := CLIENT.GetService("s-f9ngbkbc") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateService(t *testing.T) { + getReq := &api.CreateServiceArgs{ServiceName: "xxxx-1-test", + PaymentMethod: "postpay", ContainerGroupName: "cg1", + Bandwidth: 100, + NeedPublicIp: false, + Containers: &[]api.ContainerDetails{ + api.ContainerDetails{ + Name: "container01", + Cpu: 1, + Memory: 2, + ImageAddress: "hub.baidubce.com/public/mysql", + ImageVersion: "5.7", + Commands: []string{"sh", + "-c", + "echo OK!&& sleep 3660"}, + VolumeMounts: []api.V1VolumeMount{ + api.V1VolumeMount{ + MountPath: "/temp", + Name: "emptydir01", + }, + }}, + }, + DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{ + Replicas: 1, + RegionId: "cn-langfang-ct", + }, + }, + Volumes: &api.Volume{ + EmptyDir: &[]api.EmptyDir{ + api.EmptyDir{Name: "emptydir01"}, + }, + }, + Tags: &[]api.Tag{ + api.Tag{ + TagKey: "a", + TagValue: "1"}, + }, + } + res, err := CLIENT.CreateService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetServiceMetrics(t *testing.T) { + res, err := CLIENT.GetServiceMetrics("s-f9ngbkbc", api.MetricsTypeMemory, "", 1661270400, 1661356800, 0) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateService(t *testing.T) { + getReq := &api.UpdateServiceArgs{ServiceName: "s-f9ngbkbc", Type: api.UpdateServiceTypeReplicas, DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{Region: api.RegionEastChina, Replicas: 1, City: "HANGZHOU", ServiceProvider: api.ServiceChinaMobile}, + }} + res, err := CLIENT.UpdateService("s-f9ngbkbc", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestStopService(t *testing.T) { + res, err := CLIENT.ServiceAction("s-f9ngbkbc", api.ServiceActionStop) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestStartService(t *testing.T) { + res, err := CLIENT.ServiceAction("s-f9ngbkbc", api.ServiceActionStart) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteService(t *testing.T) { + res, err := CLIENT.DeleteService("xxxx-1") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestClient_ServiceBatchOperate(t *testing.T) { + getReq := &api.ServiceBatchOperateArgs{IdList: []string{"xxxx-1", "xxx-2"}, Action: "start"} + res, err := CLIENT.ServiceBatchOperate(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + +} + +func TestClient_ServiceBatchDelete(t *testing.T) { + getReq := &[]string{"xxxx-1", "xxx-2"} + res, err := CLIENT.ServiceBatchDelete(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetPodDeployment(t *testing.T) { + res, err := CLIENT.GetPodDeployment("sts-f9ngbkbc-cn-langfang-ct-uxe4z") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestGetPodDeploymentMetrics(t *testing.T) { + res, err := CLIENT.GetPodDeploymentMetrics("sts-f9ngbkbc-cn-langfang-ct-uxe4z", api.MetricsTypeMemory, "", 1661270400, 1661356800, 0) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestUpdatePodDeploymentReplicas(t *testing.T) { + getReq := &api.UpdateDeploymentReplicasRequest{ + Replicas: 2, + } + res, err := CLIENT.UpdatePodDeploymentReplicas("sts-f9ngbkbc-cn-langfang-ct-uxe4z", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestDeletePodDeployment(t *testing.T) { + getReq := &[]string{"sts-f9ngbkbc-cn-jinan-un-0cloi"} + res, err := CLIENT.DeletePodDeployment(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetPodList(t *testing.T) { + res, err := CLIENT.GetPodList(1, 100, + "", "", "", "", "") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestGetPodMetrics(t *testing.T) { + res, err := CLIENT.GetPodMetrics("sts-f9ngbkbc-cn-langfang-ct-uxe4z-0", api.MetricsTypeMemory, "", 1661270400, 1661356800, 0) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestGetPodDetail(t *testing.T) { + res, err := CLIENT.GetPodDetail("sts-f9ngbkbc-cn-langfang-ct-uxe4z-0") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} +func TestRestartPod(t *testing.T) { + err := CLIENT.RestartPod("sts-f9ngbkbc-cn-langfang-ct-uxe4z-0") + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/bec/vm.go b/bce-sdk-go/services/bec/vm.go new file mode 100644 index 0000000..302f826 --- /dev/null +++ b/bce-sdk-go/services/bec/vm.go @@ -0,0 +1,271 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateVmService - create vm service with the specific parameters +// +// PARAMS: +// - args: the arguments to create a vm service +// +// RETURNS: +// - *CreateVmServiceResult: the result of create vm service +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVmService(args *api.CreateVmServiceArgs) (*api.CreateVmServiceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.CreateVmServiceResult{} + req := &api.PostHttpReq{Url: api.GetVmURI(), Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// UpdateVmService - update vm service with the specific parameters +// +// PARAMS: +// - args: the arguments to update a vm service +// +// RETURNS: +// - *UpdateVmServiceResult: the result of update vm service +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateVmService(serviceId string, args *api.UpdateVmServiceArgs) (*api.UpdateVmServiceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.UpdateVmServiceResult{} + req := &api.PostHttpReq{Url: api.GetVmURI() + "/" + serviceId, Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// GetVmServiceList - get vm services with the specific parameters +// +// PARAMS: +// - args: the arguments to get vm services +// +// RETURNS: +// - *ListVmServiceResult: the result of get vm services +// - error: nil if ok otherwise the specific error +func (c *Client) GetVmServiceList(args *api.ListVmServiceArgs) (*api.ListVmServiceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if args.PageSize != 0 { + params["pageSize"] = strconv.Itoa(args.PageSize) + } + if args.PageNo != 0 { + params["pageNo"] = strconv.Itoa(args.PageNo) + } + if args.OrderBy != "" { + params["orderBy"] = args.OrderBy + } + if args.Order != "" { + params["order"] = args.Order + } + if args.KeywordType != "" { + params["keywordType"] = args.KeywordType + } + if args.Keyword != "" { + params["keyword"] = args.Keyword + } + if args.Status != "" { + params["status"] = args.Status + } + if args.Region != "" { + params["region"] = args.Region + } + if args.OsName != "" { + params["osName"] = args.OsName + } + if args.ServiceId != "" { + params["serviceId"] = args.ServiceId + } + + result := &api.ListVmServiceResult{} + req := &api.GetHttpReq{Url: api.GetVmURI(), Result: result, Params: params} + err := api.Get(c, req) + + return result, err +} + +// GetVmServiceDetail - get vm service detail with the specific parameters +// +// PARAMS: +// - serviceId: vm service id +// +// RETURNS: +// - *VmServiceDetailsVo: the result of vm service detail +// - error: nil if ok otherwise the specific error +func (c *Client) GetVmServiceDetail(serviceId string) (*api.VmServiceDetailsVo, error) { + if serviceId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmServiceDetailsVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmURI() + "/" + serviceId). + WithResult(result). + Do() + return result, err +} + +// GetVmServiceMetrics - get vm service metrics with the specific parameters +// +// PARAMS: +// - serviceId: service id +// - serviceProviderStr: service Provider +// - offsetInSeconds: offset Seconds +// - metricsType: metrics Type +// +// RETURNS: +// - *ServiceMetricsResult: the result of get vm service metrics +// - error: nil if ok otherwise the specific error +func (c *Client) GetVmServiceMetrics(serviceId, serviceProviderStr string, start, end, stepInMin int, metricsType api.MetricsType) (*api.ServiceMetricsResult, error) { + + params := make(map[string]string) + params["serviceId"] = serviceId + if serviceProviderStr != "" { + params["serviceProvider"] = serviceProviderStr + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if end != 0 { + params["end"] = strconv.Itoa(end) + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmServiceMonitorURI() + "/" + serviceId). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// VmServiceAction - operate vm service with the specific parameters +// +// PARAMS: +// - serviceId: service id +// - action: operation action +// +// RETURNS: +// - *VmServiceActionResult: the result of operate vm service +// - error: nil if ok otherwise the specific error +func (c *Client) VmServiceAction(serviceId string, action api.VmServiceAction) (*api.VmServiceActionResult, error) { + if serviceId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmServiceActionResult{} + req := &api.PostHttpReq{Url: api.GetVmServiceActionURI(serviceId, string(action)), Result: result, Body: nil} + err := api.Put(c, req) + + return result, err +} + +// DeleteVmService - delete a vm service with the specific parameters +// +// PARAMS: +// - serviceId: service id +// +// RETURNS: +// - *VmServiceActionResult: the result of delete vm service +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVmService(serviceId string) (*api.VmServiceActionResult, error) { + if serviceId == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmServiceActionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(api.GetVmURI() + "/" + serviceId). + WithResult(result). + Do() + + return result, err +} + +// BatchDeleteVmService - batch delete vm service with the specific parameters +// +// PARAMS: +// - serviceIds: service id list +// +// RETURNS: +// - *VmServiceBatchActionResult: the result of batch delete service id list +// - error: nil if ok otherwise the specific error +func (c *Client) BatchDeleteVmService(serviceIds *[]string) (*api.VmServiceBatchActionResult, error) { + if serviceIds == nil { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmServiceBatchActionResult{} + req := &api.PostHttpReq{Url: api.GetVmURI() + "/batch/delete", Result: result, Body: serviceIds} + err := api.Post(c, req) + + return result, err +} + +// BatchOperateVmService - batch operate vm service with the specific parameters +// +// PARAMS: +// - args: the arguments to batch operate vm service +// +// RETURNS: +// - *VmServiceBatchActionResult: the result of batch operate vm service +// - error: nil if ok otherwise the specific error +func (c *Client) BatchOperateVmService(args *api.VmServiceBatchActionArgs) (*api.VmServiceBatchActionResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + if args.Action != "start" && args.Action != "stop" { + return nil, fmt.Errorf("action is start|stop, please check") + } + + result := &api.VmServiceBatchActionResult{} + req := &api.PostHttpReq{Url: api.GetVmURI() + "/batch/operate", Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} diff --git a/bce-sdk-go/services/bec/vmInstance.go b/bce-sdk-go/services/bec/vmInstance.go new file mode 100644 index 0000000..8328d0b --- /dev/null +++ b/bce-sdk-go/services/bec/vmInstance.go @@ -0,0 +1,335 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BEC service + +// Package bec defines the BEC services of BCE. The supported APIs are all defined in sub-package + +package bec + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// CreateVmServiceInstance - create service vm instance with the specific parameters +// +// PARAMS: +// - serviceId: service Id +// - args: the arguments to create service vm instance +// +// RETURNS: +// - *CreateVmServiceResult: the result of create service instance +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVmServiceInstance(serviceId string, args *api.CreateVmServiceArgs) (*api.CreateVmServiceResult, error) { + if serviceId == "" || args == nil { + return nil, fmt.Errorf("please set argments") + } + result := &api.CreateVmServiceResult{} + req := &api.PostHttpReq{Url: api.GetVmURI() + "/" + serviceId + "/instance", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// GetVmInstanceList - get vm list with the specific parameters +// +// PARAMS: +// - args: the arguments to get vm list +// +// RETURNS: +// - *LogicPageVmInstanceResult: the result of get vm list +// - error: nil if ok otherwise the specific error +func (c *Client) GetVmInstanceList(args *api.ListRequest) (*api.LogicPageVmInstanceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set argments") + } + + params := make(map[string]string) + if args.PageSize != 0 { + params["pageSize"] = strconv.Itoa(args.PageSize) + } + if args.PageNo != 0 { + params["pageNo"] = strconv.Itoa(args.PageNo) + } + if args.OrderBy != "" { + params["orderBy"] = args.OrderBy + } + if args.Order != "" { + params["order"] = args.Order + } + if args.KeywordType != "" { + params["keywordType"] = args.KeywordType + } + if args.Keyword != "" { + params["keyword"] = args.Keyword + } + if args.Status != "" { + params["status"] = args.Status + } + if args.Region != "" { + params["region"] = args.Region + } + if args.OsName != "" { + params["osName"] = args.OsName + } + if args.ServiceId != "" { + params["serviceId"] = args.ServiceId + } + + result := &api.LogicPageVmInstanceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmInstanceURI()). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetVirtualMachine - get vm with the specific parameters +// +// PARAMS: +// - vmID: vm id +// +// RETURNS: +// - *VmInstanceDetailsVo: the result of get vm +// - error: nil if ok otherwise the specific error +func (c *Client) GetVirtualMachine(vmID string) (*api.VmInstanceDetailsVo, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmInstanceDetailsVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmInstanceURI() + "/" + vmID). + WithResult(result). + Do() + + return result, err +} + +// DeleteVmInstance - delete vm instance with the specific parameters +// +// PARAMS: +// - vmID: vm id +// +// RETURNS: +// - *ActionInfoVo: the result of delete vm instance +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVmInstance(vmID string) (*api.ActionInfoVo, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.ActionInfoVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(api.GetVmInstanceURI() + "/" + vmID). + WithResult(result). + Do() + + return result, err +} + +// UpdateVmInstance - update vm with the specific parameters +// +// PARAMS: +// - vmID: vm id +// - args: the arguments to update vm +// +// RETURNS: +// - *UpdateVmDeploymentResult: the result of update vm +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateVmInstance(vmID string, args *api.UpdateVmInstanceArgs) (*api.UpdateVmDeploymentResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.UpdateVmDeploymentResult{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/" + vmID, Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// BindSecurityGroup - bind vm instance to security group +// +// PARAMS: +// - action: bind +// - args: the arguments to update vm +// +// RETURNS: +// - *UpdateVmDeploymentResult: the result of update vm +// - error: nil if ok otherwise the specific error +func (c *Client) BindSecurityGroup(action string, args *api.BindSecurityGroupInstances) (*api.BindSecurityGroupInstancesResponse, error) { + + result := &api.BindSecurityGroupInstancesResponse{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/securityGroup" + "/" + action, Result: result, Body: args} + err := api.Put(c, req) + return result, err +} + +// ReinstallVmInstance - reinstall vm instance with the specific parameters +// +// PARAMS: +// - vmID: vm id +// - args: the arguments to reinstall vm instance +// +// RETURNS: +// - *ReinstallVmInstanceResult: the result of reinstall vm instance +// - error: nil if ok otherwise the specific error +func (c *Client) ReinstallVmInstance(vmID string, args *api.ReinstallVmInstanceArg) (*api.ReinstallVmInstanceResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.ReinstallVmInstanceResult{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/" + vmID + "/system/reinstall", Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} + +// OperateVmDeployment - operate vm with the specific parameters +// +// PARAMS: +// - vmID: vm id +// - action: the arguments to operate vm +// +// RETURNS: +// - *OperateVmDeploymentResult: the result of operate vm +// - error: nil if ok otherwise the specific error +func (c *Client) OperateVmDeployment(vmID string, action api.VmInstanceBatchOperateAction) (*api.OperateVmDeploymentResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.OperateVmDeploymentResult{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/" + vmID + "/" + string(action), Result: result, Body: nil} + err := api.Put(c, req) + + return result, err +} + +// GetVmInstanceMetrics - get vm metrics with the specific parameters +// +// PARAMS: +// - vmId: vm id +// - offsetInSeconds: offset Seconds +// - serviceProvider: service provider +// - metricsType: metrics Type +// RETURNS: +// - *ServiceMetricsResult: the result of get vm metrics +// - error: nil if ok otherwise the specific error + +func (c *Client) GetVmInstanceMetrics(vmId, serviceProviderStr string, start, end, stepInMin int, metricsType api.MetricsType) (*api.ServiceMetricsResult, error) { + + params := make(map[string]string) + if serviceProviderStr != "" { + params["serviceProvider"] = serviceProviderStr + } + if metricsType != "" { + params["metricsType"] = string(metricsType) + } + if start != 0 { + params["start"] = strconv.Itoa(start) + } + if end != 0 { + params["end"] = strconv.Itoa(end) + } + if stepInMin != 0 { + params["stepInMin"] = strconv.Itoa(stepInMin) + } + result := &api.ServiceMetricsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmMonitorURI() + "/" + vmId). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// GetVmConfig - get vm config with the specific parameters +// +// PARAMS: +// - vmID: vm id +// +// RETURNS: +// - *VmConfigResult: the result of get vm config +// - error: nil if ok otherwise the specific error +func (c *Client) GetVmConfig(vmID string) (*api.VmConfigResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmConfigResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(api.GetVmInstanceURI() + "/" + vmID + "/config"). + WithResult(result). + Do() + + return result, err +} + +// CreateVmPrivateIp - create vm private ip with the specific parameters +// +// PARAMS: +// - vmID: vm id +// - args: the args to create vm private ip +// +// RETURNS: +// - *VmConfigResult: the result of create vm private ip +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVmPrivateIp(vmID string, args *api.CreateVmPrivateIpForm) (*api.VmPrivateIpResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmPrivateIpResult{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/" + vmID + "/privateIp", Result: result, Body: args} + err := api.Post(c, req) + + return result, err +} + +// DeleteVmPrivateIp - delete vm private ip with the specific parameters +// +// PARAMS: +// - vmID: vm id +// - args: the args to delete vm private ip +// +// RETURNS: +// - *VmPrivateIpResult: the result of delete vm private ip +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVmPrivateIp(vmID string, args *api.DeleteVmPrivateIpForm) (*api.VmPrivateIpResult, error) { + if vmID == "" { + return nil, fmt.Errorf("please set argments") + } + + result := &api.VmPrivateIpResult{} + req := &api.PostHttpReq{Url: api.GetVmInstanceURI() + "/" + vmID + "/privateIp/release", Result: result, Body: args} + err := api.Put(c, req) + + return result, err +} diff --git a/bce-sdk-go/services/bec/vm_instance_test.go b/bce-sdk-go/services/bec/vm_instance_test.go new file mode 100644 index 0000000..4dd57e3 --- /dev/null +++ b/bce-sdk-go/services/bec/vm_instance_test.go @@ -0,0 +1,132 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +////////////////////////////////////////////// +// vmInstance API +////////////////////////////////////////////// + +func TestCreateVmServiceInstance(t *testing.T) { + // 使用vpc网络 + getReq := &api.CreateVmServiceArgs{ + Cpu: 1, Memory: 2, + NeedIpv6PublicIp: false, NeedPublicIp: true, Bandwidth: 100, + DeployInstances: &[]api.DeploymentInstance{ + { + RegionId: "cn-hangzhou-cm", + Replicas: 1, + NetworkType: "vpc", + }, + }, + ImageType: api.ImageTypeBec, + ImageId: "im-6btnw2x2", + SystemVolume: &api.SystemVolumeConfig{ + SizeInGB: 40, + VolumeType: api.DiskTypeNVME, + Name: "sys"}, + KeyConfig: &api.KeyConfig{ + Type: "bccKeyPair", + BccKeyPairIdList: []string{"k-CIg4d2cC"}, + }, + } + res, err := CLIENT.CreateVmServiceInstance("s-bu5xjidw", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateVmServiceInstanceTripleLine(t *testing.T) { + // 测试三线节点制定运营商 + getReq1 := &api.CreateVmServiceArgs{ + Cpu: 1, Memory: 2, + NeedIpv6PublicIp: false, NeedPublicIp: true, Bandwidth: 100, + DeployInstances: &[]api.DeploymentInstance{ + { + RegionId: "cn-changchun-ix", + Replicas: 1, + NetworkType: "vpc", + SubServiceProviders: []string{"cm"}, + }, + }, + ImageType: api.ImageTypeBec, + ImageId: "im-6btnw2x2", + SystemVolume: &api.SystemVolumeConfig{ + SizeInGB: 40, + VolumeType: api.DiskTypeNVME, + Name: "sys"}, + KeyConfig: &api.KeyConfig{ + Type: "bccKeyPair", + BccKeyPairIdList: []string{"k-CIg4d2cC"}, + }, + } + res1, err1 := CLIENT.CreateVmServiceInstance("s-bu5xjidw", getReq1) + ExpectEqual(t.Errorf, nil, err1) + t.Logf("%+v", res1) +} + +func TestGetVmInstanceList(t *testing.T) { + getReq := &api.ListRequest{ServiceId: "s-vrowm5qt"} + res, err := CLIENT.GetVmInstanceList(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVirtualMachine(t *testing.T) { + res, err := CLIENT.GetVirtualMachine("vm-vrowm5qt-cn-baoding-ix-dvaqv") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestOperateVmDeployment(t *testing.T) { + res, err := CLIENT.OperateVmDeployment("vm-czpgb91c-cn-langfang-ct-wgbem", api.VmInstanceBatchOperateStart) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVmInstanceMetrics(t *testing.T) { + res, err := CLIENT.GetVmInstanceMetrics("vm-2qbftgbf-cn-langfang-ct-f7p9j", "", 1660147200, 1660233600, 0, api.MetricsTypeCpu) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVmConfig(t *testing.T) { + res, err := CLIENT.GetVmConfig("vm-czpgb91c-cn-langfang-ct-wgbem") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestReinstallVmInstance(t *testing.T) { + req := &api.ReinstallVmInstanceArg{ImageId: "im-dikfxxxx", AdminPass: "1xxAxxx@", ImageType: api.ImageTypeBec, KeyConfig: &api.KeyConfig{Type: "password", AdminPass: "1xxAxxx@"}} + res, err := CLIENT.ReinstallVmInstance("vm-pxxx", req) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateVmDeployment(t *testing.T) { + req := &api.UpdateVmInstanceArgs{Type: "resource", Cpu: 2, Memory: 4, VmName: "vm-czpgb91c-cn-langfang-ct-wgbem"} + res, err := CLIENT.UpdateVmInstance("vm-czpgb91c-cn-langfang-ct-wgbem", req) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBindSecurityGroup(t *testing.T) { + req := &api.BindSecurityGroupInstances{Instances: []api.InstancesBinding{ + api.InstancesBinding{ + InstanceId: "vm-czpgb91c-cn-langfang-ct-wgbem", + SecurityGroupIds: []string{"sg-itlgtacv", "sg-219mosrn"}, + }, + }} + // action: bind or unbind + res, err := CLIENT.BindSecurityGroup("bind", req) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteVmInstance(t *testing.T) { + res, err := CLIENT.DeleteVmInstance("vm-czpgb91c-cn-langfang-ct-wgbem") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} diff --git a/bce-sdk-go/services/bec/vm_test.go b/bce-sdk-go/services/bec/vm_test.go new file mode 100644 index 0000000..fc6fb75 --- /dev/null +++ b/bce-sdk-go/services/bec/vm_test.go @@ -0,0 +1,125 @@ +package bec + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/bec/api" +) + +// //////////////////////////////////////////// +// vmService API +// //////////////////////////////////////////// +func TestCreateVmServiceOnly(t *testing.T) { + getReq := &api.CreateVmServiceArgs{ServiceName: "wcw-test"} + res, err := CLIENT.CreateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCreateVmService(t *testing.T) { + getReq := &api.CreateVmServiceArgs{KeyConfig: &api.KeyConfig{ + Type: "bccKeyPair", + BccKeyPairIdList: []string{"k-lVBDKoDj"}, + }, ImageId: "m-f0ZRR9qB", Bandwidth: 100, ImageType: api.ImageTypeBec, SystemVolume: &api.SystemVolumeConfig{SizeInGB: 40, VolumeType: api.DiskTypeNVME, Name: "sys"}, + NetworkConfigList: &[]api.NetworkConfig{api.NetworkConfig{NodeType: "SINGLE", NetworksList: &[]api.Networks{api.Networks{NetType: "INTERNAL_IP", NetName: "eth0"}, + api.Networks{NetType: "PUBLIC_IP", NetName: "eth1"}}}}, DeployInstances: &[]api.DeploymentInstance{api.DeploymentInstance{RegionId: "cn-langfang-ct", Replicas: 1, + NetworkType: "classic"}}, DisableIntranet: false, NeedPublicIp: true, NeedIpv6PublicIp: false, SecurityGroupIds: []string{"sg-219mosrn"}, + DnsConfig: &api.DnsConfig{ + DnsType: "DEFAULT", + }, Cpu: 2, Memory: 4, PaymentMethod: "postpay"} + res, err := CLIENT.CreateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestUpdateVmService(t *testing.T) { + getReq := &api.UpdateVmServiceArgs{ + UpdateBecVmForm: api.UpdateBecVmForm{ + Type: api.UpdateVmReplicas, + KeyConfig: &api.KeyConfig{ + Type: "bccKeyPair", BccKeyPairIdList: []string{"k-lVBDKoDj"}, + }, + }, DeployInstances: &[]api.DeploymentInstance{ + api.DeploymentInstance{ + RegionId: "cn-jinan-cm", + Replicas: 1, + NetworkType: "vpc", + }, + }, ReplicaTemplate: api.ReplicaTemplate{ + Type: "template", + TemplateId: "tmpl-gc4maqay", + }, + } + res, err := CLIENT.UpdateVmService("s-dstkrmda", getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVmServiceList(t *testing.T) { + getReq := &api.ListVmServiceArgs{} + res, err := CLIENT.GetVmServiceList(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVmServiceDetail(t *testing.T) { + res, err := CLIENT.GetVmServiceDetail("s-dstkrmda") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestVmServiceAction(t *testing.T) { + res, err := CLIENT.VmServiceAction("s-dstkrmda", api.VmServiceActionStart) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGetVmServiceMetrics(t *testing.T) { + res, err := CLIENT.GetVmServiceMetrics("s-mifwgtju", "", 1660147200, 1660233600, 1, api.MetricsTypeCpu) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestCrateVmPrivateIp(t *testing.T) { + // 添加虚机辅助IP + crateVmPrivateIpReq := &api.CreateVmPrivateIpForm{SecondaryPrivateIpAddressCount: 1} + res, err := CLIENT.CreateVmPrivateIp("vm-czpgb91c-cn-langfang-ct-wgbem", crateVmPrivateIpReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteVmPrivateIp(t *testing.T) { + // 删除虚机辅助IP + deleteVmPrivateIpReq := &api.DeleteVmPrivateIpForm{PrivateIps: []string{"172.18.176.54"}} + delRes, err := CLIENT.DeleteVmPrivateIp("vm-czpgb91c-cn-langfang-ct-wgbem", deleteVmPrivateIpReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", delRes) +} + +func TestBatchOperateVmServiceStop(t *testing.T) { + getReq := &api.VmServiceBatchActionArgs{IdList: []string{"s-xxxxx-1", "s-xxxxx-2"}, + Action: api.VmServiceBatchStart} + res, err := CLIENT.BatchOperateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBatchOperateVmServiceStart(t *testing.T) { + getReq := &api.VmServiceBatchActionArgs{IdList: []string{"s-bu5xjidw"}, Action: api.VmServiceBatchStop} + res, err := CLIENT.BatchOperateVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestDeleteVmService(t *testing.T) { + res, err := CLIENT.DeleteVmService("s-xxxx") + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestBatchDeleteVmService(t *testing.T) { + getReq := &[]string{"s-xxxx-1", "s-xxxx-2"} + res, err := CLIENT.BatchDeleteVmService(getReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} diff --git a/bce-sdk-go/services/bie/api/common.go b/bce-sdk-go/services/bie/api/common.go new file mode 100644 index 0000000..2dc95b9 --- /dev/null +++ b/bce-sdk-go/services/bie/api/common.go @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// commond.go - commmon and shared logic + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +type PostHttpReq struct { + Url string + Body interface{} + Result interface{} + Params map[string]string +} + +type GetHttpReq struct { + Url string + Result interface{} + Params map[string]string +} + +func Post(cli bce.Client, phr *PostHttpReq) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + + return PostOrPut(cli, phr, req) +} + +func Put(cli bce.Client, phr *PostHttpReq) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + + return PostOrPut(cli, phr, req) +} + +func PostOrPut(cli bce.Client, phr *PostHttpReq, req *bce.BceRequest) error { + req.SetUri(phr.Url) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + if phr.Body != nil { + jsonBytes, jsonErr := json.Marshal(phr.Body) + if jsonErr != nil { + return jsonErr + } + // fmt.Println(string(jsonBytes)) + bodyObj, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + req.SetBody(bodyObj) + } + + if phr.Params != nil { + req.SetParams(phr.Params) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + if phr.Result != nil { + if err := resp.ParseJsonBody(phr.Result); err != nil { + return err + } + } + return nil +} + +func Get(cli bce.Client, ghr *GetHttpReq) error { + req := &bce.BceRequest{} + req.SetUri(ghr.Url) + req.SetMethod(http.GET) + + if ghr.Params != nil { + req.SetParams(ghr.Params) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + if err := resp.ParseJsonBody(ghr.Result); err != nil { + return err + } + return nil +} diff --git a/bce-sdk-go/services/bie/api/config.go b/bce-sdk-go/services/bie/api/config.go new file mode 100644 index 0000000..031dc7c --- /dev/null +++ b/bce-sdk-go/services/bie/api/config.go @@ -0,0 +1,281 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// config.go - the config related APIs definition supported by the BIE service + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + PREFIX_V3CORE = "/v3/core" +) + +// ListConfig - list all configs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - coreid: id of the core +// +// RETURNS: +// - *ListConfigResult: the result config list +// - error: nil if ok otherwise the specific error +func ListConfig(cli bce.Client, coreid string, lcr *ListConfigReq) (*ListConfigResult, error) { + params := map[string]string{} + + if lcr.Status != "" { + params["status"] = lcr.Status + } + params["pageNo"] = strconv.Itoa(lcr.PageNo) + params["pageSize"] = strconv.Itoa(lcr.PageSize) + + url := PREFIX_V3CORE + "/" + coreid + "/config" + result := &ListConfigResult{} + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetConfig - get a config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - coreid: id of the core +// - ver: version, e.g:$LATEST +// +// RETURNS: +// - *CfgResult: the result config +// - error: nil if ok otherwise the specific error +func GetConfig(cli bce.Client, coreid string, ver string) (*CfgResult, error) { + url := PREFIX_V3CORE + "/" + coreid + "/config/" + ver + + result := &CfgResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// PubConfig - pub a config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cpr: id of the core and version +// +// RETURNS: +// - *CfgResult: the pub result +// - error: nil if ok otherwise the specific error +func PubConfig(cli bce.Client, cpr *CoreidVersion, cpb *CfgPubBody) (*CfgResult, error) { + url := PREFIX_V3CORE + "/" + cpr.Coreid + "/config/" + cpr.Version + "/publish" + result := &CfgResult{} + req := &PostHttpReq{Url: url, Body: cpb, Result: result} + err := Post(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// DeployConfig - deploy a config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - coreid: id of the core +// - ver: version, e.g:$LATEST +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeployConfig(cli bce.Client, coreid string, ver string) error { + url := PREFIX_V3CORE + "/" + coreid + "/config/" + ver + "/deploy" + + req := &PostHttpReq{Url: url} + err := Post(cli, req) + if err != nil { + return err + } + + return nil +} + +// DownloadConfig - download a config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *CfgDownloadReq: id, version, with bin or not +// +// RETURNS: +// - *CfgDownloadResult: the result +// - error: nil if ok otherwise the specific error +func DownloadConfig(cli bce.Client, cdr *CfgDownloadReq) (*CfgDownloadResult, error) { + url := PREFIX_V3CORE + "/" + cdr.Coreid + "/config/" + cdr.Version + "/download" + params := map[string]string{"withBin": strconv.FormatBool(cdr.WithBin)} + result := &CfgDownloadResult{} + req := &GetHttpReq{Url: url, Params: params, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// CreateService - create a service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *CoreidVersion: coreid, version +// - *CreateServiceReq: request parameters +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func CreateService(cli bce.Client, cv *CoreidVersion, + sr *CreateServiceReq) (*ServiceResult, error) { + url := PREFIX_V3CORE + "/" + cv.Coreid + "/config/" + cv.Version + "/service" + + result := &ServiceResult{} + req := &PostHttpReq{Url: url, Body: sr, Result: result} + err := Post(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetService - get a service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *IdVerName: coreid, version and name +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func GetService(cli bce.Client, ivn *IdVerName) (*ServiceResult, error) { + url := PREFIX_V3CORE + "/" + ivn.Coreid + "/config/" + ivn.Version + "/service/" + ivn.Name + + result := &ServiceResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// EditService - edit a service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func EditService(cli bce.Client, ivn *IdVerName, + esr *EditServiceReq) (*ServiceResult, error) { + url := PREFIX_V3CORE + "/" + ivn.Coreid + "/config/" + ivn.Version + "/service/" + ivn.Name + + result := &ServiceResult{} + req := &PostHttpReq{Url: url, Body: esr, Result: result} + err := Put(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// DeleteService - delete a service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteService(cli bce.Client, ivn *IdVerName) error { + url := PREFIX_V3CORE + "/" + ivn.Coreid + "/config/" + ivn.Version + "/service/" + ivn.Name + + req := &bce.BceRequest{} + req.SetUri(url) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +// ReorderService - reorder service after an other service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func ReorderService(cli bce.Client, ivn *IdVerName, after string) error { + url := PREFIX_V3CORE + "/" + ivn.Coreid + "/config/" + ivn.Version + "/service/" + ivn.Name + url += "/after/" + after + + req := &PostHttpReq{Url: url} + err := Post(cli, req) + if err != nil { + return err + } + + return nil +} + +// VolumeOp - attache of detach volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *CoreidVersion: coreid, version +// - *VolumeOpReq: request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func VolumeOp(cli bce.Client, cv *CoreidVersion, vor *VolumeOpReq) error { + url := PREFIX_V3CORE + "/" + cv.Coreid + "/config/" + cv.Version + "/volume" + + req := &PostHttpReq{Url: url, Body: vor} + err := Put(cli, req) + if err != nil { + return err + } + + return nil +} diff --git a/bce-sdk-go/services/bie/api/const.go b/bce-sdk-go/services/bie/api/const.go new file mode 100644 index 0000000..f4c4f12 --- /dev/null +++ b/bce-sdk-go/services/bie/api/const.go @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// const.go - define common constants +package api + +const ( + PREFIX = "/v3/group" +) diff --git a/bce-sdk-go/services/bie/api/core.go b/bce-sdk-go/services/bie/api/core.go new file mode 100644 index 0000000..6511a2c --- /dev/null +++ b/bce-sdk-go/services/bie/api/core.go @@ -0,0 +1,111 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// core.go - the core APIs definition supported by the BIE service + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +// ListCore - list all core of a group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - groupUuid: id of the group +// +// RETURNS: +// - *ListCoreResult: the result core list +// - error: nil if ok otherwise the specific error +func ListCore(cli bce.Client, groupUuid string) (*ListCoreResult, error) { + url := PREFIX + "/" + groupUuid + "/core" + + result := &ListCoreResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetCore - get a core of a group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - groupUuid: id of the group +// - coreid: id of the core +// +// RETURNS: +// - *CoreResult: the result core +// - error: nil if ok otherwise the specific error +func GetCore(cli bce.Client, groupUuid string, coreid string) (*CoreResult, error) { + url := PREFIX + "/" + groupUuid + "/core/" + coreid + + result := &CoreResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// RenewCoreAuth - renew the auth of a core +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - coreid: id of the core +// +// RETURNS: +// - *CoreInfo: the result core info +// - error: nil if ok otherwise the specific error +func RenewCoreAuth(cli bce.Client, coreid string) (*CoreInfo, error) { + url := "/v3/core/" + coreid + "/renew-auth" + + result := &CoreInfo{} + req := &PostHttpReq{Url: url, Result: result} + err := Post(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetCoreStatus - get the status of a core +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - coreid: id of the core +// +// RETURNS: +// - *CoreStatus: the status of the core +// - error: nil if ok otherwise the specific error +func GetCoreStatus(cli bce.Client, coreid string) (*CoreStatus, error) { + url := "/v3/core/" + coreid + "/online" + + result := &CoreStatus{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/bce-sdk-go/services/bie/api/group.go b/bce-sdk-go/services/bie/api/group.go new file mode 100644 index 0000000..2d9f128 --- /dev/null +++ b/bce-sdk-go/services/bie/api/group.go @@ -0,0 +1,148 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// group.go - the group APIs definition supported by the BIE service + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListGroup - list all groups of the account +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *ListGroupReq: page no, page size, and name +// +// RETURNS: +// - *ListGroupResult: the result group list structure +// - error: nil if ok otherwise the specific error +func ListGroup(cli bce.Client, lgr *ListGroupReq) (*ListGroupResult, error) { + url := PREFIX + params := map[string]string{} + if lgr.Name != "" { + params["name"] = lgr.Name + } + if lgr.PageNo != 0 { + params["pageNo"] = strconv.Itoa(lgr.PageNo) + } + if lgr.PageSize != 0 { + params["pageSize"] = strconv.Itoa(lgr.PageSize) + } + result := &ListGroupResult{} + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetGroup - get a group of the account +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - groupUuid: id of the group +// +// RETURNS: +// - *ListGroupResult: the result group list structure +// - error: nil if ok otherwise the specific error +func GetGroup(cli bce.Client, groupUuid string) (*Group, error) { + url := PREFIX + "/" + groupUuid + + result := &Group{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// CreateGroup - create a group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cgr: parameters to create group +// +// RETURNS: +// - *CreateGroupResult: the result group +// - error: nil if ok otherwise the specific error +func CreateGroup(cli bce.Client, cgr *CreateGroupReq) (*CreateGroupResult, error) { + url := PREFIX + params := map[string]string{"withCore": "true"} + + result := &CreateGroupResult{} + req := &PostHttpReq{Url: url, Result: result, Body: cgr, Params: params} + err := Post(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// EditGroup - edit a group +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - groupId: the group to edit +// - egr: parameters to update group +// +// RETURNS: +// - *Group: the result group +// - error: nil if ok otherwise the specific error +func EditGroup(cli bce.Client, groupId string, egr *EditGroupReq) (*Group, error) { + url := PREFIX + "/" + groupId + params := map[string]string{"withCore": "true"} + + result := &Group{} + req := &PostHttpReq{Url: url, Result: result, Body: egr, Params: params} + err := Put(cli, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// DeleteGroup - delete a group of the account +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - groupUuid: id of the group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteGroup(cli bce.Client, groupUuid string) error { + req := &bce.BceRequest{} + req.SetUri(PREFIX + "/" + groupUuid) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} diff --git a/bce-sdk-go/services/bie/api/image.go b/bce-sdk-go/services/bie/api/image.go new file mode 100644 index 0000000..1b766cf --- /dev/null +++ b/bce-sdk-go/services/bie/api/image.go @@ -0,0 +1,187 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// iamge.go - the docker image related APIs definition supported by the BIE service + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + PREFIX_V3MODSYS = "/v3/module/system" + PREFIX_V3MODUSR = "/v3/module/user" +) + +// ListImageSys - list all system docker images +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - ListImageReq: list request parameters +// +// RETURNS: +// - *ListImageResult: the result iamge list +// - error: nil if ok otherwise the specific error +func ListImageSys(cli bce.Client, lir *ListImageReq) (*ListImageResult, error) { + url := PREFIX_V3MODSYS + result := &ListImageResult{} + params := map[string]string{} + params["pageNo"] = strconv.Itoa(lir.PageNo) + params["pageSize"] = strconv.Itoa(lir.PageSize) + if lir.Tag != "" { + params["tag"] = lir.Tag + } + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// GetImageSys - get a system docker images +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - uuid: the image uuid +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func GetImageSys(cli bce.Client, uuid string) (*Image, error) { + url := PREFIX_V3MODSYS + "/" + uuid + result := &Image{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// ListImageUser - list all user docker images +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - ListImageReq: list request parameters +// +// RETURNS: +// - *ListImageResult: the result iamge list +// - error: nil if ok otherwise the specific error +func ListImageUser(cli bce.Client, lir *ListImageReq) (*ListImageResult, error) { + url := PREFIX_V3MODUSR + result := &ListImageResult{} + params := map[string]string{} + params["pageNo"] = strconv.Itoa(lir.PageNo) + params["pageSize"] = strconv.Itoa(lir.PageSize) + if lir.Tag != "" { + params["tag"] = lir.Tag + } + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// GetImageUser - get a user docker image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - uuid: the image uuid +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func GetImageUser(cli bce.Client, uuid string) (*Image, error) { + url := PREFIX_V3MODUSR + "/" + uuid + result := &Image{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// CreateImageUser - create a user docker image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - CreateImageReq: request parameters, name, image url, description +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func CreateImageUser(cli bce.Client, cir *CreateImageReq) (*Image, error) { + url := PREFIX_V3MODUSR + result := &Image{} + req := &PostHttpReq{Url: url, Result: result, Body: cir} + err := Post(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// EditImageUser - edit a user docker image information +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - uuid: the image uuid +// - EditImageReq: request parameter: description +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func EditImageUser(cli bce.Client, uuid string, eir *EditImageReq) (*Image, error) { + url := PREFIX_V3MODUSR + "/" + uuid + result := &Image{} + req := &PostHttpReq{Url: url, Result: result, Body: eir} + err := Put(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// DeleteImageUser - delete a user docker image +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - uuid: the image uuid +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteImageUser(cli bce.Client, uuid string) error { + url := PREFIX_V3MODUSR + "/" + uuid + req := &bce.BceRequest{} + req.SetUri(url) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} diff --git a/bce-sdk-go/services/bie/api/model.go b/bce-sdk-go/services/bie/api/model.go new file mode 100644 index 0000000..2f87025 --- /dev/null +++ b/bce-sdk-go/services/bie/api/model.go @@ -0,0 +1,411 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +type Group struct { + GroupUuid string `json:"groupUuid"` + Name string `json:"name"` + Description string `json:"description"` + Platform string `json:"platform"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type ListGroupReq struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Name string `json:"name"` +} + +type ListGroupResult struct { + TotalCountByUser int `json:"totalCountByUser"` + TotalCount int `json:"totalCount"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Result []Group `json:"result"` +} + +type CreateGroupReq struct { + Name string `json:"name"` + Description string `json:"description"` + AuthType string `json:"authType"` + Platform string `json:"platform"` +} + +type CoreAuth struct { + AuthType string `json:"authType"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + UserName string `json:"username"` + Tcp string `json:"tcp"` + Hostname string `json:"hostname"` + Ssl string `json:"ssl"` + Wss string `json:"wss"` +} + +type CoreInfo struct { + DeviceUuid string `json:"deviceUuid"` + Name string `json:"name"` + Description string `json:"description"` + AuthType string `json:"authType"` + Platform string `json:"platform"` + BinVersion string `json:"binVersion"` + CoreAuth CoreAuth `json:"coreAuth"` + DownloadUrl string `json:"downloadUrl"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type CoreResult struct { + DeviceUuid string `json:"deviceUuid"` + Name string `json:"name"` + ConfVersion string `json:"confVersion"` + Report string `json:"report"` + AuthType string `json:"authType"` + Description string `json:"description"` + Platform string `json:"platform"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type ListCoreResult struct { + Result []CoreResult `json:"result"` +} + +type CreateGroupResult struct { + GroupInfo Group `json:"groupInfo"` + CoreInfo CoreInfo `json:"coreInfo"` +} + +type EditGroupReq struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type CoreStatus struct { + IsCoreOnline bool `json:"isCoreOnline"` +} + +type Config struct { + Uuid string `json:"uuid"` + Status string `json:"status"` + Version string `json:"version"` + Description string `json:"description"` + LastDeployTime string `json:"lastDeployTime"` + CreateTime string `json:"createTime"` +} + +type ListConfigResult struct { + TotalCount int `json:"totalCount"` + Result []Config `json:"result"` + Language string `json:"language"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Region bool `json:"region"` +} + +type ListConfigReq struct { + Status string `json:"status"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type CfgService struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + ModuleUuid string `json:"moduleUuid"` + ModuleImage string `json:"moduleImage"` + ModuleName string `json:"moduleName"` + ModuleTags string `json:"moduleTags"` + ModuleCategory string `json:"moduleCategory"` + Mounts []string `json:"mounts"` + Description string `json:"description"` + CreateTime string `json:"createTime"` +} + +type CfgVolume struct { + Name string `json:"name"` + Version string `json:"version"` + HostPath string `json:"hostPath"` + Deletable bool `json:"deletable"` + CreateTime string `json:"createTime"` +} + +type CfgResult struct { + Uuid string `json:"uuid"` + Status string `json:"status"` + Version string `json:"version"` + Description string `json:"description"` + LastDeployTime string `json:"lastDeployTime"` + CreateTime string `json:"createTime"` + ConfigServices []CfgService `json:"configServices"` + ConfigVolumes []CfgVolume `json:"configVolumes"` +} + +type CoreidVersion struct { + Coreid string `json:"coreid"` + Version string `json:"version"` +} + +type CfgPubBody struct { + Description string `json:"description"` +} + +type CfgDownloadReq struct { + Coreid string `json:"coreid"` + Version string `json:"version"` + WithBin bool `json:"withBin"` +} + +type CfgDownloadResult struct { + Url string `json:"url"` + Md5 string `json:"md5"` +} + +type CfgRestart struct { + Retry map[string]interface{} `json:"restart"` +} + +type CreateServiceReq struct { + Name string `json:"name"` + ModuleUuid string `json:"moduleUuid"` + ModuleCategory string `json:"moduleCategory"` + Description string `json:"description"` + Replica int `json:"replica"` + Mounts []map[string]interface{} `json:"mounts"` + Ports []string `json:"ports"` + Args []string `json:"args"` + Env map[string]string `json:"env"` + Devs []string `json:"devs"` + Restart map[string]interface{} `json:"restart"` + Resources map[string]interface{} `json:"resources"` +} + +type ServiceResult struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + ModuleImage string `json:"moduleImage"` + ModuleName string `json:"moduleName"` + ModuleTags string `json:"moduleTags"` + ModuleCategory string `json:"moduleCategory"` + Description string `json:"description"` + Mounts []map[string]interface{} `json:"mounts"` + Ports []string `json:"ports"` + Args []string `json:"args"` + Env map[string]string `json:"env"` + Devs []string `json:"devs"` + Restart map[string]interface{} `json:"restart"` + Resources map[string]interface{} `json:"resources"` + CreateTime string `json:"createTime"` +} + +type EditServiceReq struct { + Description string `json:"description"` + Replica int `json:"replica"` + Mounts []map[string]interface{} `json:"mounts"` + Ports []string `json:"ports"` + Args []string `json:"args"` + Env map[string]string `json:"env"` + Devs []string `json:"devs"` + Restart map[string]interface{} `json:"restart"` + Resources map[string]interface{} `json:"resources"` +} + +type IdVerName struct { + Coreid string `json:"coreid"` + Version string `json:"version"` + Name string `json:"name"` +} + +type NameVersion struct { + Name string `json:"name"` + Version string `json:"version"` +} + +type VolumeOpReq struct { + Attach []NameVersion `json:"attach"` + Detach []NameVersion `json:"detach"` +} + +// Volume +type VolTemplate struct { + Name string `json:"name"` + Code string `json:"code"` + Category string `json:"category"` + Tags []string `json:"tags"` +} + +type ListVolTemplate struct { + Result []VolTemplate `json:"result"` +} + +type CreateVolReq struct { + Name string `json:"name"` + TemplateCode string `json:"templateCode"` + Description string `json:"description"` + Tags []string `json:"tags"` + HostPath string `json:"hostPath"` +} + +type VolFile struct { + FileName string `json:"fileName"` + Type string `json:"type"` + Viewable bool `json:"viewable"` + Editable bool `json:"editable"` + Deletable bool `json:"deletable"` +} + +type VolumeResult struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + Tags []string `json:"tags"` + HostPath string `json:"hostPath"` + Files []VolFile `json:"files"` + Description string `json:"description"` + Version string `json:"version"` + Template VolTemplate `json:"template"` + NewFile bool `json:"newFile"` + CleanFile bool `json:"cleanFile"` + CreateTime string `json:"createTime"` +} + +type ListVolumeReq struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Tag string `json:"tag"` + Name string `json:"name"` +} + +type ListVolumeResult struct { + TotalCount int `json:"totalCount"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Result []VolumeResult `json:"result"` +} + +type EditVolumeReq struct { + Description string `json:"description"` + Tags []string `json:"tags"` + HostPath string `json:"hostPath"` +} + +type ListVolumeVerResult struct { + Result []VolumeResult `json:"result"` +} + +type VolDownloadResult struct { + Url string `json:"url"` +} + +type CreateVolFileReq struct { + Content string `json:"content"` + FileName string `json:"fileName"` +} + +type GetVolFileReq struct { + Name string `json:"name"` + Version string `json:"version"` + FileName string `json:"fileName"` +} + +type Name2 struct { + Name string `json:"name"` + FileName string `json:"fileName"` +} + +type EditVolFileReq struct { + Content string `json:"content"` +} + +type ListVolCoreResult struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Result []struct { + ConfigVersion string `json:"configVersion"` + DeviceName string `json:"deviceName"` + DeviceUUID string `json:"deviceUuid"` + GroupUUID string `json:"groupUuid"` + VolumeName string `json:"volumeName"` + VolumeVersion string `json:"volumeVersion"` + } `json:"result"` + TotalCount int `json:"totalCount"` +} + +type ListVolCoreReq struct { + Name string `json:"name"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type EditCoreVolVerReq struct { + Jobs []EditCoreVolVerEntry `json:"jobs"` +} + +type EditCoreVolVerEntry struct { + DeviceUUID string `json:"deviceUuid"` + NewVersion string `json:"newVersion"` + OldVersion string `json:"oldVersion"` +} + +type ImportCfcReq struct { + Name string `json:"name"` + Version string `json:"version"` +} + +type ImportBosReq struct { + BosBucket string `json:"bosBucket"` + BosKey string `json:"bosKey"` +} + +// image +type ListImageReq struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Tag string `json:"tag"` +} + +type Image struct { + Category string `json:"category"` + CreateTime string `json:"createTime"` + Description string `json:"description"` + Image string `json:"image"` + Name string `json:"name"` + Tags string `json:"tags"` + UUID string `json:"uuid"` + Warehouse string `json:"warehouse"` + Replica struct { + Min string `json:"min"` + } `json:"replica"` +} + +type ListImageResult struct { + Result []Image `json:"result"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type CreateImageReq struct { + Description string `json:"description"` + Image string `json:"image"` + Name string `json:"name"` +} + +type EditImageReq struct { + Description string `json:"description"` +} diff --git a/bce-sdk-go/services/bie/api/volume.go b/bce-sdk-go/services/bie/api/volume.go new file mode 100644 index 0000000..9d317f6 --- /dev/null +++ b/bce-sdk-go/services/bie/api/volume.go @@ -0,0 +1,435 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// volume.go - the volume related APIs definition supported by the BIE service + +// Package api defines all APIs supported by the BIE service of BCE. +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + PREFIX_V3VOLTPL = "/v3/volumeTemplate" + PREFIX_V3VOL = "/v3/volume" +) + +// ListVolumeTpl - list all volume template +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListVolTemplate: the result volume template list +// - error: nil if ok otherwise the specific error +func ListVolumeTpl(cli bce.Client) (*ListVolTemplate, error) { + url := PREFIX_V3VOLTPL + result := &ListVolTemplate{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// CreateVolume - create a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - CreateVolReq: the request parameters +// +// RETURNS: +// - *VolumeResult: the result volume +// - error: nil if ok otherwise the specific error +func CreateVolume(cli bce.Client, cvr *CreateVolReq) (*VolumeResult, error) { + url := PREFIX_V3VOL + result := &VolumeResult{} + req := &PostHttpReq{Url: url, Result: result, Body: cvr} + err := Post(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// ListVolume - list a bunch of volumes +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - ListVolReq: the request parameters +// +// RETURNS: +// - *ListVolumeResult: the result list +// - error: nil if ok otherwise the specific error +func ListVolume(cli bce.Client, lvr *ListVolumeReq) (*ListVolumeResult, error) { + url := PREFIX_V3VOL + result := &ListVolumeResult{} + params := map[string]string{ + "pageNo": strconv.Itoa(lvr.PageNo), + "pageSize": strconv.Itoa(lvr.PageSize)} + if lvr.Name != "" { + params["name"] = lvr.Name + } + if lvr.Tag != "" { + params["tag"] = lvr.Tag + } + + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// GetVolume - get a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the requested volume name +// +// RETURNS: +// - *VolumeResult: the result vaolume +// - error: nil if ok otherwise the specific error +func GetVolume(cli bce.Client, name string) (*VolumeResult, error) { + url := PREFIX_V3VOL + "/" + name + result := &VolumeResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// EditVolume - edit a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the requested volume name +// - *EditVolumeReq: request parameters +// +// RETURNS: +// - *VolumeResult: the result vaolume +// - error: nil if ok otherwise the specific error +func EditVolume(cli bce.Client, name string, evr *EditVolumeReq) (*VolumeResult, error) { + url := PREFIX_V3VOL + "/" + name + result := &VolumeResult{} + req := &PostHttpReq{Url: url, Result: result, Body: evr} + err := Put(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// DeleteVolume - delete a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the requested volume name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteVolume(cli bce.Client, name string) error { + url := PREFIX_V3VOL + "/" + name + req := &bce.BceRequest{} + req.SetUri(url) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +// ListVolumeVer - list version list of a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - NameVersion: the request parameters, version can be empty +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func ListVolumeVer(cli bce.Client, nv *NameVersion) (*ListVolumeVerResult, error) { + url := PREFIX_V3VOL + "/" + nv.Name + "/version" + params := map[string]string{} + if nv.Version != "" { + params["version"] = nv.Version + } + + result := &ListVolumeVerResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// PubVolumeVer - pub version list of a volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the request volume name +// +// RETURNS: +// - *VolumeResult: the result volume +// - error: nil if ok otherwise the specific error +func PubVolumeVer(cli bce.Client, name string) (*VolumeResult, error) { + url := PREFIX_V3VOL + "/" + name + "/version" + + result := &VolumeResult{} + req := &PostHttpReq{Url: url, Result: result} + err := Post(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// DownloadVolVer - get the download address of a specific version of volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the request volume name +// - version: the request volume version +// +// RETURNS: +// - *VolDownloadResult: the result +// - error: nil if ok otherwise the specific error +func DownloadVolVer(cli bce.Client, name string, + version string) (*VolDownloadResult, error) { + url := PREFIX_V3VOL + "/" + name + "/version/" + version + "/zipfile" + + result := &VolDownloadResult{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// CreateVolFile - create a volume file +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the request volume name +// - *CreateVolFileReq: request parameters +// +// RETURNS: +// - *CreateVolFileReq: the result +// - error: nil if ok otherwise the specific error +func CreateVolFile(cli bce.Client, name string, + cvf *CreateVolFileReq) (*CreateVolFileReq, error) { + url := PREFIX_V3VOL + "/" + name + "/file" + + result := &CreateVolFileReq{} + req := &PostHttpReq{Url: url, Result: result, Body: cvf} + err := Post(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// GetVolumeFile - get a volume file +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - GetVolFileReq: volume name, version anf filename +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func GetVolumeFile(cli bce.Client, cvfr *GetVolFileReq) (*CreateVolFileReq, error) { + url := PREFIX_V3VOL + "/" + cvfr.Name + "/version/" + cvfr.Version + "/file/" + cvfr.FileName + + result := &CreateVolFileReq{} + req := &GetHttpReq{Url: url, Result: result} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// EditVolumeFile - edit a volume file +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *Name2: the requested volume name and file name +// - *EditVolFileReq: the content, the request body +// +// RETURNS: +// - *CreateVolFileReq: the result +// - error: nil if ok otherwise the specific error +func EditVolumeFile(cli bce.Client, names *Name2, + body *EditVolFileReq) (*CreateVolFileReq, error) { + url := PREFIX_V3VOL + "/" + names.Name + "/file/" + names.FileName + + result := &CreateVolFileReq{} + req := &PostHttpReq{Url: url, Result: result, Body: body} + err := Put(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// DeleteVolFile - delete a volume file +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the requested volume name +// - filename: the requested volume filename +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteVolFile(cli bce.Client, name string, filename string) error { + url := PREFIX_V3VOL + "/" + name + "/file/" + filename + req := &bce.BceRequest{} + req.SetUri(url) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +// ClearVolFile - clear all volume files +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the requested volume name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func ClearVolFile(cli bce.Client, name string) error { + url := PREFIX_V3VOL + "/" + name + "/file" + req := &bce.BceRequest{} + req.SetUri(url) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +// ListVolCore - list the cores associated with the volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - *ListVolCoreReq: the request parameters +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func ListVolCore(cli bce.Client, lvcr *ListVolCoreReq) (*ListVolCoreResult, error) { + url := PREFIX_V3VOL + "/" + lvcr.Name + "/core" + params := map[string]string{ + "pageNo": strconv.Itoa(lvcr.PageNo), + "pageSize": strconv.Itoa(lvcr.PageSize)} + + result := &ListVolCoreResult{} + req := &GetHttpReq{Url: url, Result: result, Params: params} + err := Get(cli, req) + if err != nil { + return nil, err + } + return result, nil +} + +// EditCoreVolVer - edit core version +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the volume name +// - *EditCoreVolVerReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func EditCoreVolVer(cli bce.Client, name string, + ecvr *EditCoreVolVerReq) error { + url := PREFIX_V3VOL + "/" + name + "/core" + + result := &ListVolCoreResult{} + req := &PostHttpReq{Url: url, Result: result, Body: ecvr} + err := Put(cli, req) + if err != nil { + return err + } + return nil +} + +// ImportCfc - import a CFC function into volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the volume name +// - *ImportCfcReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func ImportCfc(cli bce.Client, name string, + icr *ImportCfcReq) error { + url := PREFIX_V3VOL + "/" + name + "/cfc" + req := &PostHttpReq{Url: url, Body: icr} + err := Post(cli, req) + if err != nil { + return err + } + return nil +} + +// ImportBos - import a BOS file into volume +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: the volume name +// - *ImportBosReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func ImportBos(cli bce.Client, name string, + ibr *ImportBosReq) error { + url := PREFIX_V3VOL + "/" + name + "/bos" + req := &PostHttpReq{Url: url, Body: ibr} + err := Post(cli, req) + if err != nil { + return err + } + return nil +} diff --git a/bce-sdk-go/services/bie/client.go b/bce-sdk-go/services/bie/client.go new file mode 100644 index 0000000..8124790 --- /dev/null +++ b/bce-sdk-go/services/bie/client.go @@ -0,0 +1,628 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BIE service + +// Package bie defines the bie services of BCE. The supported APIs are all defined in sub-package +package bie + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bie/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "iotedge." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of BIE(iot edge) service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the BIE service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +////////////////////////////////////////////// +// group API +////////////////////////////////////////////// + +// ListGroup - list all groups +// +// - *ListGroupReq: page no, page size, and name +// +// RETURNS: +// - *api.ListGroupResult: the all groups +// - error: the return error if any occurs +func (c *Client) ListGroup(lgr *api.ListGroupReq) (*api.ListGroupResult, error) { + return api.ListGroup(c, lgr) +} + +// GetGroup - get a group +// +// RETURNS: +// - *api.Group: the group +// - error: the return error if any occurs +func (c *Client) GetGroup(groupUuid string) (*api.Group, error) { + return api.GetGroup(c, groupUuid) +} + +// CreateGroup - create a group +// +// PARAMS: +// - cgr: parameters to create group +// +// RETURNS: +// - *CreateGroupResult: the result group +// - error: nil if ok otherwise the specific error +func (c *Client) CreateGroup(cgr *api.CreateGroupReq) (*api.CreateGroupResult, error) { + return api.CreateGroup(c, cgr) +} + +// EditGroup - edit a group +// +// PARAMS: +// - groupId: the group to edit +// - egr: parameters to update group +// +// RETURNS: +// - *Group: the result group +// - error: nil if ok otherwise the specific error +func (c *Client) EditGroup(groupid string, egr *api.EditGroupReq) (*api.Group, error) { + return api.EditGroup(c, groupid, egr) +} + +// DeleteGroup - delete a group of the account +// +// PARAMS: +// - groupUuid: id of the group +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteGroup(groupUuid string) error { + return api.DeleteGroup(c, groupUuid) +} + +////////////////////////////////////////////// +// core API +////////////////////////////////////////////// + +// ListCore - list all core of a group +// +// PARAMS: +// - groupUuid: id of the group +// +// RETURNS: +// - *ListCoreResult: the result core list +// - error: nil if ok otherwise the specific error +func (c *Client) ListCore(groupUuid string) (*api.ListCoreResult, error) { + return api.ListCore(c, groupUuid) +} + +// GetCore - get a core of a group +// +// PARAMS: +// - groupUuid: id of the group +// - coreid: id of the core +// +// RETURNS: +// - *CoreResult: the result core +// - error: nil if ok otherwise the specific error +func (c *Client) GetCore(groupUuid string, coreid string) (*api.CoreResult, error) { + return api.GetCore(c, groupUuid, coreid) +} + +// RenewCoreAuth - renew the auth of a core +// +// PARAMS: +// - coreid: id of the core +// +// RETURNS: +// - *CoreInfo: the result core info +// - error: nil if ok otherwise the specific error +func (c *Client) RenewCoreAuth(coreid string) (*api.CoreInfo, error) { + return api.RenewCoreAuth(c, coreid) +} + +// GetCoreStatus - get the status of a core +// +// PARAMS: +// - coreid: id of the core +// +// RETURNS: +// - *CoreStatus: the status of the core +// - error: nil if ok otherwise the specific error +func (c *Client) GetCoreStatus(coreid string) (*api.CoreStatus, error) { + return api.GetCoreStatus(c, coreid) +} + +////////////////////////////////////////////// +// Config API +////////////////////////////////////////////// + +// ListConfig - list all configs +// +// PARAMS: +// - coreid: id of the core +// +// RETURNS: +// - *ListConfigResult: the result config list +// - error: nil if ok otherwise the specific error +func (c *Client) ListConfig(coreid string, + lcr *api.ListConfigReq) (*api.ListConfigResult, error) { + return api.ListConfig(c, coreid, lcr) +} + +// GetConfig - get a config +// +// PARAMS: +// - coreid: id of the core +// - ver: version, e.g:$LATEST +// +// RETURNS: +// - *CfgResult: the result config +// - error: nil if ok otherwise the specific error +func (c *Client) GetConfig(coreid string, ver string) (*api.CfgResult, error) { + return api.GetConfig(c, coreid, ver) +} + +// PubConfig - pub a config +// +// PARAMS: +// - cpr: id of the core and version +// +// RETURNS: +// - *CfgResult: the pub result +// - error: nil if ok otherwise the specific error +func (c *Client) PubConfig(cpr *api.CoreidVersion, cpb *api.CfgPubBody) (*api.CfgResult, error) { + return api.PubConfig(c, cpr, cpb) +} + +// DeployConfig - deploy a config +// +// PARAMS: +// - coreid: id of the core +// - ver: version, e.g:$LATEST +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeployConfig(coreid string, ver string) error { + return api.DeployConfig(c, coreid, ver) +} + +// DownloadConfig - download a config +// +// PARAMS: +// - *CfgDownloadReq: id, version, with bin or not +// +// RETURNS: +// - *CfgDownloadResult: the result +// - error: nil if ok otherwise the specific error +func (c *Client) DownloadConfig(cdr *api.CfgDownloadReq) (*api.CfgDownloadResult, error) { + return api.DownloadConfig(c, cdr) +} + +////////////////////////////////////////////// +// Service API +////////////////////////////////////////////// + +// CreateService - create a service +// +// PARAMS: +// - *CoreidVersion: coreid, version +// - *CreateServiceReq: request parameters +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func (c *Client) CreateService(cv *api.CoreidVersion, + sr *api.CreateServiceReq) (*api.ServiceResult, error) { + return api.CreateService(c, cv, sr) +} + +// GetService - get a service +// +// PARAMS: +// - *IdVerName: coreid, version and name +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func (c *Client) GetService(ivn *api.IdVerName) (*api.ServiceResult, error) { + return api.GetService(c, ivn) +} + +// EditService - edit a service +// +// PARAMS: +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - *ServiceResult: the result +// - error: nil if ok otherwise the specific error +func (c *Client) EditService(ivn *api.IdVerName, + esr *api.EditServiceReq) (*api.ServiceResult, error) { + return api.EditService(c, ivn, esr) +} + +// DeleteService - delete a service +// +// PARAMS: +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteService(ivn *api.IdVerName) error { + return api.DeleteService(c, ivn) +} + +// ReorderService - reorder service after an other service +// +// PARAMS: +// - *IdVerName: coreid, version, and name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) ReorderService(ivn *api.IdVerName, after string) error { + return api.ReorderService(c, ivn, after) +} + +// VolumeOp - attache of detach volumes +// +// PARAMS: +// - *CoreidVersion: coreid, version +// - *VolumeOpReq: request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) VolumeOp(cv *api.CoreidVersion, vor *api.VolumeOpReq) error { + return api.VolumeOp(c, cv, vor) +} + +// //////////////////////////////////////////// +// Volume API +// //////////////////////////////////////////// +// ListVolumeTpl - list all volume template +// +// PARAMS: +// RETURNS: +// - *ListVolTemplate: the result volume template list +// - error: nil if ok otherwise the specific error +func (c *Client) ListVolumeTpl() (*api.ListVolTemplate, error) { + return api.ListVolumeTpl(c) +} + +// CreateVolume - create a volume +// +// PARAMS: +// - CreateVolReq: the request parameters +// +// RETURNS: +// - *VolumeResult: the result volume +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVolume(cvr *api.CreateVolReq) (*api.VolumeResult, error) { + return api.CreateVolume(c, cvr) +} + +// ListVolume - list a bunch of volumes +// +// PARAMS: +// - ListVolReq: the request parameters +// +// RETURNS: +// - *ListVolumeResult: the result list +// - error: nil if ok otherwise the specific error +func (c *Client) ListVolume(lvr *api.ListVolumeReq) (*api.ListVolumeResult, error) { + return api.ListVolume(c, lvr) +} + +// GetVolume - get a volume +// +// PARAMS: +// - name: the requested volume name +// +// RETURNS: +// - *VolumeResult: the result vaolume +// - error: nil if ok otherwise the specific error +func (c *Client) GetVolume(name string) (*api.VolumeResult, error) { + return api.GetVolume(c, name) +} + +// EditVolume - edit a volume +// +// PARAMS: +// - name: the requested volume name +// - *EditVolumeReq: request parameters +// +// RETURNS: +// - *VolumeResult: the result vaolume +// - error: nil if ok otherwise the specific error +func (c *Client) EditVolume(name string, evr *api.EditVolumeReq) (*api.VolumeResult, error) { + return api.EditVolume(c, name, evr) +} + +// DeleteVolume - delete a volume +// +// PARAMS: +// - name: the requested volume name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVolume(name string) error { + return api.DeleteVolume(c, name) +} + +// ListVolumeVer - list version list of a volume +// +// PARAMS: +// - NameVersion: the request parameters, version can be empty +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func (c *Client) ListVolumeVer(nv *api.NameVersion) (*api.ListVolumeVerResult, error) { + return api.ListVolumeVer(c, nv) +} + +// PubVolumeVer - pub version list of a volume +// +// PARAMS: +// - name: the request volume name +// +// RETURNS: +// - *VolumeResult: the result volume +// - error: nil if ok otherwise the specific error +func (c *Client) PubVolumeVer(name string) (*api.VolumeResult, error) { + return api.PubVolumeVer(c, name) +} + +// DownloadVolVer - get the download address of a specific version of volume +// +// PARAMS: +// - name: the request volume name +// - version: the request volume version +// +// RETURNS: +// - *VolDownloadResult: the result +// - error: nil if ok otherwise the specific error +func (c *Client) DownloadVolVer(name string, + version string) (*api.VolDownloadResult, error) { + return api.DownloadVolVer(c, name, version) +} + +// CreateVolFile - create a volume file +// +// PARAMS: +// - name: the request volume name +// - *CreateVolFileReq: request parameters +// +// RETURNS: +// - *CreateVolFileReq: the result +// - error: nil if ok otherwise the specific error +func (c *Client) CreateVolFile(name string, + cvf *api.CreateVolFileReq) (*api.CreateVolFileReq, error) { + return api.CreateVolFile(c, name, cvf) +} + +// GetVolumeFile - get a volume file +// +// PARAMS: +// - GetVolFileReq: volume name, version anf filename +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func (c *Client) GetVolumeFile(cvfr *api.GetVolFileReq) (*api.CreateVolFileReq, error) { + return api.GetVolumeFile(c, cvfr) +} + +// EditVolumeFile - edit a volume file +// +// PARAMS: +// - *Name2: the requested volume name and file name +// - *EditVolFileReq: the content, the request body +// +// RETURNS: +// - *CreateVolFileReq: the result +// - error: nil if ok otherwise the specific error +func (c *Client) EditVolumeFile(names *api.Name2, + body *api.EditVolFileReq) (*api.CreateVolFileReq, error) { + return api.EditVolumeFile(c, names, body) +} + +// DeleteVolFile - delete a volume file +// +// PARAMS: +// - name: the requested volume name +// - filename: the requested volume filename +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteVolFile(name string, filename string) error { + return api.DeleteVolFile(c, name, filename) +} + +// ClearVolFile - clear all volume files +// +// PARAMS: +// - name: the requested volume name +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) ClearVolFile(name string) error { + return api.ClearVolFile(c, name) +} + +// ListVolCore - list the cores associated with the volume +// +// PARAMS: +// - *ListVolCoreReq: the request parameters +// +// RETURNS: +// - *ListVolumeVerResult: the result list +// - error: nil if ok otherwise the specific error +func (c *Client) ListVolCore(lvcr *api.ListVolCoreReq) (*api.ListVolCoreResult, error) { + return api.ListVolCore(c, lvcr) +} + +// EditCoreVolVer - edit the core version +// +// PARAMS: +// - name: the volume name +// - *EditCoreVolVerReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) EditCoreVolVer(name string, + ecvr *api.EditCoreVolVerReq) error { + return api.EditCoreVolVer(c, name, ecvr) +} + +// ImportCfc - import a CFC function into volume +// +// PARAMS: +// - name: the volume name +// - *ImportCfcReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) ImportCfc(name string, + icr *api.ImportCfcReq) error { + return api.ImportCfc(c, name, icr) +} + +// ImportBos - import a BOS file into volume +// +// PARAMS: +// - name: the volume name +// - *ImportBosReq: the request parameters +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) ImportBos(name string, + ibr *api.ImportBosReq) error { + return api.ImportBos(c, name, ibr) +} + +// //////////////////////////////////////////// +// Volume API +// //////////////////////////////////////////// +// ListImageSys - list all system docker images +// +// PARAMS: +// - ListImageReq: list request parameters +// +// RETURNS: +// - *ListImageResult: the result iamge list +// - error: nil if ok otherwise the specific error +func (c *Client) ListImageSys(lir *api.ListImageReq) (*api.ListImageResult, error) { + return api.ListImageSys(c, lir) +} + +// GetImageSys - get a system docker images +// +// PARAMS: +// - uuid: the image uuid +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func (c *Client) GetImageSys(uuid string) (*api.Image, error) { + return api.GetImageSys(c, uuid) +} + +// ListImageUser - list all user docker images +// +// PARAMS: +// - ListImageReq: list request parameters +// +// RETURNS: +// - *ListImageResult: the result iamge list +// - error: nil if ok otherwise the specific error +func (c *Client) ListImageUser(lir *api.ListImageReq) (*api.ListImageResult, error) { + return api.ListImageUser(c, lir) +} + +// GetImageUser - get a user docker image +// +// PARAMS: +// - uuid: the image uuid +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func (c *Client) GetImageUser(uuid string) (*api.Image, error) { + return api.GetImageUser(c, uuid) +} + +// CreateImageUser - create a user docker image +// +// PARAMS: +// - CreateImageReq: request parameters, name, image url, description +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func (c *Client) CreateImageUser(cir *api.CreateImageReq) (*api.Image, error) { + return api.CreateImageUser(c, cir) +} + +// EditImageUser - edit a user docker image information +// +// PARAMS: +// - uuid: the image uuid +// - EditImageReq: request parameter: description +// +// RETURNS: +// - *Image: the result iamge +// - error: nil if ok otherwise the specific error +func (c *Client) EditImageUser(uuid string, eir *api.EditImageReq) (*api.Image, error) { + return api.EditImageUser(c, uuid, eir) +} + +// DeleteImageUser - delete a user docker image +// +// PARAMS: +// - uuid: the image uuid +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteImageUser(uuid string) error { + return api.DeleteImageUser(c, uuid) +} diff --git a/bce-sdk-go/services/bie/client_test.go b/bce-sdk-go/services/bie/client_test.go new file mode 100644 index 0000000..ddd5703 --- /dev/null +++ b/bce-sdk-go/services/bie/client_test.go @@ -0,0 +1,608 @@ +package bie + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/services/bie/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var CLIENT *Client + +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + fmt.Printf("init \n") + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 6; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +////////////////////////////////////////////// +// group API +////////////////////////////////////////////// + +func TestListGroup(t *testing.T) { + listReq := &api.ListGroupReq{PageNo: 1, PageSize: 1000} + res, err := CLIENT.ListGroup(listReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) +} + +func TestGroupCRUD(t *testing.T) { + // 1, list group first + listReq := &api.ListGroupReq{PageNo: 1, PageSize: 1000} + groups, err := CLIENT.ListGroup(listReq) + ExpectEqual(t.Errorf, nil, err) + + name := "TestGroupCRUD" + deleted := 0 + + // delete core with the same name + for _, g := range groups.Result { + if g.Name == name { + CLIENT.DeleteGroup(g.GroupUuid) + deleted = 1 + break + } + } + + // 2, create a new group + createReq := &api.CreateGroupReq{ + Name: name, + Description: "descr", + AuthType: "CERT", + Platform: "Linux-amd64", + } + + createdGroup, err := CLIENT.CreateGroup(createReq) + ExpectEqual(t.Errorf, nil, err) + + groups2, err := CLIENT.ListGroup(listReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, groups2.TotalCount, groups.TotalCount+1) + + // 3, edit group + newName := name + "new" + newDesc := "descrnew" + editReq := &api.EditGroupReq{ + Name: newName, + Description: newDesc, + } + + newGroup, err := CLIENT.EditGroup(createdGroup.GroupInfo.GroupUuid, editReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, newGroup.Name, newName) + ExpectEqual(t.Errorf, newGroup.Description, newDesc) + + // 4, delete group + err = CLIENT.DeleteGroup(newGroup.GroupUuid) + ExpectEqual(t.Errorf, nil, err) + + // 5, list again + groups2, _ = CLIENT.ListGroup(listReq) + ExpectEqual(t.Errorf, groups2.TotalCount, groups.TotalCount-deleted) + +} + +func createNewGroup(cli *Client, name string) (*api.CreateGroupResult, error) { + listReq := &api.ListGroupReq{PageNo: 1, PageSize: 1000} + // 1, list group first + groups, err := cli.ListGroup(listReq) + if err != nil { + return nil, err + } + + // delete core with the same name + for _, g := range groups.Result { + if g.Name == name { + CLIENT.DeleteGroup(g.GroupUuid) + break + } + } + + // 2, create a new group + createReq := &api.CreateGroupReq{ + Name: name, + Description: "descr", + AuthType: "CERT", + Platform: "Linux-amd64", + } + + return cli.CreateGroup(createReq) +} + +// //////////////////////////////////////////// +// core API +// //////////////////////////////////////////// +func TestCoreQuery(t *testing.T) { + name := "TestCoreQuery" + createdGroup, err := createNewGroup(CLIENT, name) + ExpectEqual(t.Errorf, nil, err) + + // 3, list the core + cores, err := CLIENT.ListCore(createdGroup.GroupInfo.GroupUuid) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, len(cores.Result), 1) + + // 4, get the core + core, err := CLIENT.GetCore(createdGroup.GroupInfo.GroupUuid, cores.Result[0].DeviceUuid) + ExpectEqual(t.Errorf, nil, err) + + // 5, renew core auth + _, err = CLIENT.RenewCoreAuth(core.DeviceUuid) + ExpectEqual(t.Errorf, nil, err) + + // 6, get online status + status, err := CLIENT.GetCoreStatus(core.DeviceUuid) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, status.IsCoreOnline, false) + + // 7, delete group + CLIENT.DeleteGroup(createdGroup.GroupInfo.GroupUuid) +} + +// //////////////////////////////////////////// +// Config API +// //////////////////////////////////////////// +func TestConfigCrudAndOp(t *testing.T) { + // 1, create a group and core + name := "TestConfigCrudAndOp" + newGroup, err := createNewGroup(CLIENT, name) + ExpectEqual(t.Errorf, nil, err) + + // 2, create a config + text := []byte(`{ + "moduleUuid": "b1f91fbe6fe54d2eaf70ef0025f1c3c2", + "moduleCategory": "SYSTEM", + "name": "first016", + "description": "test", + "replica": 1, + "mounts1": [ + {"name": "open-hub-001", "version": "LATEST", "readonly": false, "target": "/etc/avr"} + ], + "ports": ["100:101", "8000:8080"], + "args": ["abc", "-v", "1.1.1"], + "env": { + "name": "wangxiaochen", + "pwd": "abcabc" + }, + "restart": { + "retry": { + "max": 1 + }, + "policy": "always", + "backoff": { + "min": "10m", + "max": "10m", + "factor": 12 + } + }, + "resources": { + "cpu": { + "cpus": 2, + "setcpus": "2" + }, + "pids": { + "limit": 20 + }, + "memory": { + "limit": "10m", + "swap": "20m" + } + }, + "devs": ["/dev/01:/dev/02", "/dev/cat1:/dev/tom1"] + }`) + + var req api.CreateServiceReq + err = json.Unmarshal(text, &req) + ExpectEqual(t.Errorf, nil, err) + + idver := &api.CoreidVersion{Coreid: newGroup.CoreInfo.DeviceUuid, Version: "LATEST"} + newConf, err := CLIENT.CreateService(idver, &req) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, newConf.Name, "first016") + + // 3, list config + listReq := &api.ListConfigReq{} + confList, err := CLIENT.ListConfig(idver.Coreid, listReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, confList.TotalCount, 2) + // 4, get config + confGot, err := CLIENT.GetConfig(idver.Coreid, idver.Version) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, confGot.ConfigServices[0].ModuleCategory, "SYSTEM") + + // 5, pub config + pubRet, err := CLIENT.PubConfig(idver, &api.CfgPubBody{Description: "test"}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, pubRet.Status, "DONE") + + // 6, download url + _, err = CLIENT.DownloadConfig(&api.CfgDownloadReq{ + Coreid: idver.Coreid, Version: pubRet.Version, WithBin: true}) + ExpectEqual(t.Errorf, nil, err) + + // 7, deploy + err = CLIENT.DeployConfig(idver.Coreid, pubRet.Version) + ExpectEqual(t.Errorf, false, err == nil) +} + +// //////////////////////////////////////////// +// Volume API +// //////////////////////////////////////////// +func TestVolumeCrudAndOp(t *testing.T) { + // 0, list volume template + templates, err := CLIENT.ListVolumeTpl() + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(templates.Result) > 0) + + // 1, create volume + name := "TestVolumeCrudAndOp" + hostPath := "/var/local" + createReq := &api.CreateVolReq{ + Name: name, + Tags: []string{"tag1", "tag2"}, + TemplateCode: templates.Result[0].Code, + HostPath: hostPath, + Description: "desc", + } + createRet, err := CLIENT.CreateVolume(createReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, name, createRet.Name) + ExpectEqual(t.Errorf, templates.Result[0].Code, createRet.Template.Code) + + // 2, get volume + volumeGot, err := CLIENT.GetVolume(name) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, name, volumeGot.Name) + ExpectEqual(t.Errorf, templates.Result[0].Code, volumeGot.Template.Code) + + // 3, list volume + list, err := CLIENT.ListVolume(&api.ListVolumeReq{}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, list.TotalCount > 0) + + // 4, update + newDesc := "new description" + newTags := []string{"tag3", "tag4", "tag5", "tag6"} + newHostpath := hostPath + "/new" + updateReq := &api.EditVolumeReq{ + Tags: newTags, Description: newDesc, HostPath: newHostpath} + updateRet, err := CLIENT.EditVolume(name, updateReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, newDesc, updateRet.Description) + ExpectEqual(t.Errorf, true, len(updateRet.Tags) >= 4) + + // 5, query version list + verListRet, err := CLIENT.ListVolumeVer(&api.NameVersion{Name: name, Version: updateRet.Version}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(verListRet.Result) > 0) + + // 6, publish new version + pubRet, err := CLIENT.PubVolumeVer(name) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, false, pubRet.Version == updateRet.Version) + verListRet2, err := CLIENT.ListVolumeVer(&api.NameVersion{Name: name}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(verListRet2.Result) > 1) + + // 7, download url + downRet, err := CLIENT.DownloadVolVer(name, pubRet.Version) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(downRet.Url) > 0) + + // 8, delete + err = CLIENT.DeleteVolume(name) + ExpectEqual(t.Errorf, nil, err) +} + +func TestVolumeFileCrud(t *testing.T) { + // 0, list volume template + templates, err := CLIENT.ListVolumeTpl() + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(templates.Result) > 0) + t.Logf("%+v", templates.Result[0]) + + var tmplate api.VolTemplate + for _, t := range templates.Result { + if t.Code == "CUSTOMIZE" { + tmplate = t + } + } + + // 1, create volume + + // 1.1 delete old volume + name := "TestVolumeFileCrud" + listVol, err := CLIENT.ListVolume(&api.ListVolumeReq{}) + for _, v := range listVol.Result { + if v.Name == name { + CLIENT.DeleteVolume(name) + } + } + + hostPath := "/var/local" + createReq := &api.CreateVolReq{ + Name: name, + Tags: []string{"tag1", "tag2"}, + TemplateCode: tmplate.Code, + HostPath: hostPath, + Description: "desc", + } + createVolRet, err := CLIENT.CreateVolume(createReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", createVolRet) + + // 2, add file + filename := "file1" + content := "{\"a\":1}" + createFileReq := &api.CreateVolFileReq{ + FileName: filename, + Content: content, + } + createFileRet, err := CLIENT.CreateVolFile(name, createFileReq) + t.Logf("%+v", createFileRet) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, filename, createFileRet.FileName) + ExpectEqual(t.Errorf, content, createFileRet.Content) + + // 3, get file + getFileReq := &api.GetVolFileReq{ + Name: name, + FileName: filename, + Version: createVolRet.Version, + } + getFileRet, err := CLIENT.GetVolumeFile(getFileReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, filename, getFileRet.FileName) + ExpectEqual(t.Errorf, content, getFileRet.Content) + + // 4, update file + newContent := content + "new" + editReq := &api.EditVolFileReq{Content: newContent} + editRet, err := CLIENT.EditVolumeFile(&api.Name2{Name: name, FileName: filename}, editReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, filename, editRet.FileName) + ExpectEqual(t.Errorf, newContent, editRet.Content) + + // 5, get file + getFileRet, err = CLIENT.GetVolumeFile(getFileReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, filename, getFileRet.FileName) + ExpectEqual(t.Errorf, newContent, getFileRet.Content) + + // 6, delete file + err = CLIENT.DeleteVolFile(name, filename) + ExpectEqual(t.Errorf, nil, err) + + // 7, delete volume + CLIENT.DeleteVolume(name) +} + +func TestVolumeMisc(t *testing.T) { + // 0, list volume template + templates, err := CLIENT.ListVolumeTpl() + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(templates.Result) > 0) + + var tmplate api.VolTemplate + for _, tmp := range templates.Result { + if tmp.Code == "CFC" { + tmplate = tmp + break + } + } + + // 1, create volume + + // 1.1 delete old volume + name := "TestVolumeMisc" + listVol, err := CLIENT.ListVolume(&api.ListVolumeReq{}) + for _, v := range listVol.Result { + if v.Name == name { + CLIENT.DeleteVolume(name) + } + } + + hostPath := "/var/local" + createReq := &api.CreateVolReq{ + Name: name, + Tags: []string{"tag1", "tag2"}, + TemplateCode: tmplate.Code, + HostPath: hostPath, + Description: "desc", + } + createVolRet, err := CLIENT.CreateVolume(createReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", createVolRet) + + // 2, import cfc + importCfcReq := &api.ImportCfcReq{ + Name: "non-exist-name", + Version: "1", + } + + err = CLIENT.ImportCfc(name, importCfcReq) + ExpectEqual(t.Errorf, true, err != nil) + fmt.Println(err) + + // 3, get core info + listCoreReq := &api.ListVolCoreReq{ + Name: name, + } + listCoreRet, err := CLIENT.ListVolCore(listCoreReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 0, listCoreRet.TotalCount) + + // 4, publish a new version and update core col version + // publish colume version + pubRet, err := CLIENT.PubVolumeVer(name) + ExpectEqual(t.Errorf, nil, err) + + entry := api.EditCoreVolVerEntry{ + DeviceUUID: "", + OldVersion: "V2", + NewVersion: pubRet.Version, + } + editCoreVerReq := &api.EditCoreVolVerReq{ + Jobs: []api.EditCoreVolVerEntry{entry}, + } + + err = CLIENT.EditCoreVolVer(name, editCoreVerReq) + ExpectEqual(t.Errorf, true, err != nil) + + // 5, delete volume + CLIENT.DeleteVolume(name) +} + +func TestVolumeImportBos(t *testing.T) { + // 0, list volume template + templates, err := CLIENT.ListVolumeTpl() + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(templates.Result) > 0) + + var tmplate api.VolTemplate + for _, tmp := range templates.Result { + if tmp.Code == "BOS" { + tmplate = tmp + break + } + } + + // 1, create volume + + // 1.1 delete old volume + name := "TestVolumeImportBos" + listVol, err := CLIENT.ListVolume(&api.ListVolumeReq{}) + for _, v := range listVol.Result { + if v.Name == name { + CLIENT.DeleteVolume(name) + } + } + + hostPath := "/var/local" + createReq := &api.CreateVolReq{ + Name: name, + Tags: []string{"tag1", "tag2"}, + TemplateCode: tmplate.Code, + HostPath: hostPath, + Description: "desc", + } + createVolRet, err := CLIENT.CreateVolume(createReq) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", createVolRet) + + // 2, import cfc + importBosReq := &api.ImportBosReq{ + BosBucket: "non-exist-bucket", + BosKey: "non-exist-key", + } + + err = CLIENT.ImportBos(name, importBosReq) + ExpectEqual(t.Errorf, true, err != nil) + fmt.Println(err) + + // 3, delete volume + CLIENT.DeleteVolume(name) +} + +func TestDockerImages(t *testing.T) { + listReq := &api.ListImageReq{PageNo: 1, PageSize: 1000} + list, err := CLIENT.ListImageSys(listReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, list.TotalCount >= 0) + _, err = CLIENT.GetImageSys("notexistimageid") + ExpectEqual(t.Errorf, true, err != nil) + resultSize := listReq.PageSize + if resultSize > list.TotalCount { + resultSize = list.TotalCount + } + ExpectEqual(t.Errorf, resultSize, len(list.Result)) + + list, err = CLIENT.ListImageUser(listReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, list.TotalCount >= 0) + cnt1 := list.TotalCount + _, err = CLIENT.GetImageUser("notexistimageid") + ExpectEqual(t.Errorf, true, err != nil) + + name := "TestDockerImages" + // 0, delete user iamge left by previous test + for _, m := range list.Result { + if name == m.Name { + CLIENT.DeleteImageUser(m.UUID) + } + } + + // 1, create a new user image + url := "hub.c.163.com/library/nginx:latest" + desc := "desc" + createReq := &api.CreateImageReq{Name: name, Description: desc, Image: url} + img, err := CLIENT.CreateImageUser(createReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, name, img.Name) + ExpectEqual(t.Errorf, desc, img.Description) + ExpectEqual(t.Errorf, url, img.Image) + + // 2, list user image again, and compare the totalCount + list, err = CLIENT.ListImageUser(listReq) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, cnt1+1, list.TotalCount) + + // 2, delete the create image + err = CLIENT.DeleteImageUser(img.UUID) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/blb/backendserver.go b/bce-sdk-go/services/blb/backendserver.go new file mode 100644 index 0000000..5b08279 --- /dev/null +++ b/bce-sdk-go/services/blb/backendserver.go @@ -0,0 +1,166 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// backendserver.go - the backendserver APIs definition supported by the BLB service + +package blb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// AddBackendServers - add backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to add backend servers +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) AddBackendServers(blbId string, args *AddBackendServersArgs) error { + + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.BackendServerList) == 0 { + return fmt.Errorf("unset backendServer list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getBackendServerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateBackendServers - update backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update backend servers +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateBackendServers(blbId string, args *UpdateBackendServersArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.BackendServerList) == 0 { + return fmt.Errorf("unset backendServer list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBackendServerUri(blbId)). + WithQueryParam("update", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeBackendServers - describe all backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all backend servers +// +// RETURNS: +// - *DescribeBackendServersResult: the result of describe all backend servers +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeBackendServers(blbId string, args *DescribeBackendServersArgs) (*DescribeBackendServersResult, error) { + if args == nil { + args = &DescribeBackendServersArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeBackendServersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBackendServerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + err := request.Do() + return result, err +} + +// DescribeHealthStatus - describe all backend servers health status +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all backend servers health status +// +// RETURNS: +// - *DescribeHealthStatusResult: the result of describe all backend servers health status +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeHealthStatus(blbId string, args *DescribeHealthStatusArgs) (*DescribeHealthStatusResult, error) { + if args == nil { + args = &DescribeHealthStatusArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeHealthStatusResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBackendServerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// RemoveBackendServers - remove backend servers +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to remove backend servers, a backend server list +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) RemoveBackendServers(blbId string, args *RemoveBackendServersArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.BackendServerList) == 0 { + return fmt.Errorf("unset backend server list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBackendServerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/blb/blb.go b/bce-sdk-go/services/blb/blb.go new file mode 100644 index 0000000..e26ea56 --- /dev/null +++ b/bce-sdk-go/services/blb/blb.go @@ -0,0 +1,222 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// blb.go - the Normal BLB APIs definition supported by the BLB service + +package blb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateLoadBalancer - create a LoadBalancer +// +// PARAMS: +// - args: parameters to create LoadBalancer +// +// RETURNS: +// - *CreateLoadBalancerResult: the result of create LoadBalancer, contains new LoadBalancer's ID +// - error: nil if ok otherwise the specific error +func (c *Client) CreateLoadBalancer(args *CreateLoadBalancerArgs) (*CreateLoadBalancerResult, error) { + if args == nil || len(args.SubnetId) == 0 { + return nil, fmt.Errorf("unset subnet id") + } + + if len(args.VpcId) == 0 { + return nil, fmt.Errorf("unset vpc id") + } + + result := &CreateLoadBalancerResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getBlbUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateLoadBalancer - update a LoadBalancer +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update LoadBalancer +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateLoadBalancer(blbId string, args *UpdateLoadBalancerArgs) error { + if args == nil { + args = &UpdateLoadBalancerArgs{} + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBlbUriWithId(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeLoadBalancers - describe all LoadBalancers +// +// PARAMS: +// - args: parameters to describe all LoadBalancers +// +// RETURNS: +// - *DescribeLoadBalancersResult: the result all LoadBalancers's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLoadBalancers(args *DescribeLoadBalancersArgs) (*DescribeLoadBalancersResult, error) { + if args == nil { + args = &DescribeLoadBalancersArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeLoadBalancersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbUri()). + WithQueryParamFilter("address", args.Address). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("blbId", args.BlbId). + WithQueryParamFilter("bccId", args.BccId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParamFilter("type", args.Type). + WithResult(result) + + if args.ExactlyMatch { + request.WithQueryParam("exactlyMatch", "true") + } + + err := request.Do() + return result, err +} + +// DescribeLoadBalancerDetail - describe a LoadBalancer +// +// PARAMS: +// - blbId: describe LoadBalancer's ID +// +// RETURNS: +// - *DescribeLoadBalancerDetailResult: the result LoadBalancer detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLoadBalancerDetail(blbId string) (*DescribeLoadBalancerDetailResult, error) { + result := &DescribeLoadBalancerDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbUriWithId(blbId)). + WithResult(result). + Do() + + return result, err +} + +// DeleteLoadBalancer - delete a LoadBalancer +// +// PARAMS: +// - blbId: parameters to delete LoadBalancer +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteLoadBalancer(blbId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getBlbUriWithId(blbId)). + Do() +} + +// DescribeLbClusterDetail - describe a LoadBalancer cluster +// +// PARAMS: +// - clusterId: describe LoadBalancer cluster's ID +// +// RETURNS: +// - *DescribeLbClusterDetailResult: the result LoadBalancer cluster detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLbClusterDetail(clusterId string) (*DescribeLbClusterDetailResult, error) { + result := &DescribeLbClusterDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbClusterUriWithId(clusterId)). + WithResult(result). + Do() + + return result, err +} + +// DescribeLbClusters - describe all LoadBalancerClusters +// +// PARAMS: +// - args: parameters to describe all LoadBalancerClusters +// +// RETURNS: +// - *DescribeLbClustersResult: the result all LoadBalancerClusters's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeLbClusters(args *DescribeLbClustersArgs) (*DescribeLbClustersResult, error) { + if args == nil { + args = &DescribeLbClustersArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeLbClustersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getBlbClusterUri()). + WithQueryParamFilter("clusterName", args.ClusterName). + WithQueryParamFilter("clusterId", args.ClusterId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ExactlyMatch { + request.WithQueryParam("exactlyMatch", "true") + } + + err := request.Do() + + return result, err +} + +// UpdateLoadBalancerAcl - update the specified LoadBalancer to support the acl feature +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update LoadBalancer acl +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateLoadBalancerAcl(blbId string, args *UpdateLoadBalancerAclArgs) error { + if args == nil || args.SupportAcl == nil { + args = &UpdateLoadBalancerAclArgs{} + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBlbAclUriWithId(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/blb/client.go b/bce-sdk-go/services/blb/client.go new file mode 100644 index 0000000..8d84413 --- /dev/null +++ b/bce-sdk-go/services/blb/client.go @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Application LoadBalance service + +// Package blb defines the Normal BLB services of BCE. The supported APIs are all defined in sub-package +package blb + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + DEFAULT_SERVICE_DOMAIN = "blb." + bce.DEFAULT_REGION + ".baidubce.com" + URI_PREFIX = bce.URI_PREFIX + "v1" + REQUEST_BLB_URL = "/blb" + + LISTENER_URL = "/listener" + TCPLISTENER_URL = "/TCPlistener" + UDPLISTENER_URL = "/UDPlistener" + HTTPLISTENER_URL = "/HTTPlistener" + HTTPSLISTENER_URL = "/HTTPSlistener" + SSLLISTENER_URL = "/SSLlistener" + + BACKENDSERVER_URL = "/backendserver" + + REQUEST_BLB_CLUSTER_URL = "/blbcluster" + SECURITY_GROUP_URL = "/securitygroup" + ENTERPRISE_SECURITY_GROUP_URL = "/enterprise/securitygroup" +) + +// Client of APPBLB service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getBlbUri() string { + return URI_PREFIX + REQUEST_BLB_URL +} + +func getBlbUriWithId(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id +} + +func getBlbAclUriWithId(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/acl/" + id +} + +func getListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + LISTENER_URL +} + +func getTCPListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + TCPLISTENER_URL +} + +func getUDPListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + UDPLISTENER_URL +} + +func getHTTPListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + HTTPLISTENER_URL +} + +func getHTTPSListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + HTTPSLISTENER_URL +} + +func getSSLListenerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + SSLLISTENER_URL +} + +func getBackendServerUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + BACKENDSERVER_URL +} + +func getBlbClusterUri() string { + return URI_PREFIX + REQUEST_BLB_CLUSTER_URL +} + +func getBlbClusterUriWithId(id string) string { + return URI_PREFIX + REQUEST_BLB_CLUSTER_URL + "/" + id +} + +func getSecurityGroupUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + SECURITY_GROUP_URL +} + +func getEnterpriseSecurityGroupUri(id string) string { + return URI_PREFIX + REQUEST_BLB_URL + "/" + id + ENTERPRISE_SECURITY_GROUP_URL +} diff --git a/bce-sdk-go/services/blb/client_test.go b/bce-sdk-go/services/blb/client_test.go new file mode 100644 index 0000000..826f56f --- /dev/null +++ b/bce-sdk-go/services/blb/client_test.go @@ -0,0 +1,431 @@ +package blb + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BLB_CLIENT *Client + BLB_ID string + + // set these values before start test + VPC_TEST_ID = "" + SUBNET_TEST_ID = "" + INSTANCE_ID = "" + CERT_ID = "" + CLUSTER_ID = "" + CLUSTER_PROPERTY_TEST = "" + TEST_BLB_ID = "lb-36c64ec0" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + BLB_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateLoadBalancer(t *testing.T) { + createArgs := &CreateLoadBalancerArgs{ + ClientToken: getClientToken(), + Name: "sdkBlb", + VpcId: VPC_TEST_ID, + SubnetId: SUBNET_TEST_ID, + ClusterProperty: CLUSTER_PROPERTY_TEST, + } + + createResult, err := BLB_CLIENT.CreateLoadBalancer(createArgs) + ExpectEqual(t.Errorf, nil, err) + + BLB_ID = createResult.BlbId +} + +func TestClient_UpdateLoadBalancer(t *testing.T) { + updateArgs := &UpdateLoadBalancerArgs{ + Name: "testSdk", + Description: "test desc", + } + err := BLB_CLIENT.UpdateLoadBalancer(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeLoadBalancers(t *testing.T) { + describeArgs := &DescribeLoadBalancersArgs{} + res, err := BLB_CLIENT.DescribeLoadBalancers(describeArgs) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(1) * time.Second) +} + +func TestClient_DescribeLoadBalancerDetail(t *testing.T) { + res, err := BLB_CLIENT.DescribeLoadBalancerDetail(BLB_ID) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateTCPListener(t *testing.T) { + createArgs := &CreateTCPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 90, + BackendPort: 90, + Scheduler: "RoundRobin", + } + err := BLB_CLIENT.CreateTCPListener(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateTCPListener(t *testing.T) { + updateArgs := &UpdateTCPListenerArgs{ + ListenerPort: 90, + Scheduler: "Hash", + } + err := BLB_CLIENT.UpdateTCPListener(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeTCPListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{ + ListenerPort: 90, + } + _, err := BLB_CLIENT.DescribeTCPListeners(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateUDPListener(t *testing.T) { + createArgs := &CreateUDPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 91, + BackendPort: 91, + Scheduler: "RoundRobin", + HealthCheckString: "a", + } + err := BLB_CLIENT.CreateUDPListener(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateUDPListener(t *testing.T) { + updateArgs := &UpdateUDPListenerArgs{ + ListenerPort: 91, + Scheduler: "Hash", + } + err := BLB_CLIENT.UpdateUDPListener(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeUDPListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{ + ListenerPort: 91, + } + _, err := BLB_CLIENT.DescribeUDPListeners(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateHTTPListener(t *testing.T) { + createArgs := &CreateHTTPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 92, + BackendPort: 92, + Scheduler: "RoundRobin", + } + err := BLB_CLIENT.CreateHTTPListener(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateHTTPListener(t *testing.T) { + updateArgs := &UpdateHTTPListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 92, + Scheduler: "LeastConnection", + KeepSession: true, + } + err := BLB_CLIENT.UpdateHTTPListener(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeHTTPListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{ + ListenerPort: 92, + } + _, err := BLB_CLIENT.DescribeHTTPListeners(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateHTTPSListener(t *testing.T) { + createArgs := &CreateHTTPSListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 93, + BackendPort: 93, + Scheduler: "RoundRobin", + CertIds: []string{CERT_ID}, + } + err := BLB_CLIENT.CreateHTTPSListener(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateHTTPSListener(t *testing.T) { + updateArgs := &UpdateHTTPSListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 93, + Scheduler: "LeastConnection", + KeepSession: true, + CertIds: []string{CERT_ID}, + } + err := BLB_CLIENT.UpdateHTTPSListener(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeHTTPSListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{ + ListenerPort: 93, + } + _, err := BLB_CLIENT.DescribeHTTPSListeners(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateSSLListener(t *testing.T) { + createArgs := &CreateSSLListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 94, + BackendPort: 94, + Scheduler: "RoundRobin", + CertIds: []string{CERT_ID}, + } + err := BLB_CLIENT.CreateSSLListener(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateSSLListener(t *testing.T) { + updateArgs := &UpdateSSLListenerArgs{ + ClientToken: getClientToken(), + ListenerPort: 94, + Scheduler: "LeastConnection", + CertIds: []string{CERT_ID}, + } + err := BLB_CLIENT.UpdateSSLListener(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeSSLListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{ + ListenerPort: 94, + } + _, err := BLB_CLIENT.DescribeSSLListeners(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeAllListeners(t *testing.T) { + describeArgs := &DescribeListenerArgs{} + result, err := BLB_CLIENT.DescribeAllListeners(BLB_ID, describeArgs) + if err != nil { + fmt.Println("get all listener failed:", err) + } else { + fmt.Println("get all listener success: ", result) + } +} + +func TestClient_AddBackendServers(t *testing.T) { + createArgs := &AddBackendServersArgs{ + ClientToken: getClientToken(), + BackendServerList: []BackendServerModel{ + {InstanceId: INSTANCE_ID, Weight: 30}, + }, + } + err := BLB_CLIENT.AddBackendServers(BLB_ID, createArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateBackendServers(t *testing.T) { + updateArgs := &UpdateBackendServersArgs{ + ClientToken: getClientToken(), + BackendServerList: []BackendServerModel{ + {InstanceId: INSTANCE_ID, Weight: 50}, + }, + } + err := BLB_CLIENT.UpdateBackendServers(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeBackendServers(t *testing.T) { + describeArgs := &DescribeBackendServersArgs{} + _, err := BLB_CLIENT.DescribeBackendServers(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeHealthStatus(t *testing.T) { + describeArgs := &DescribeHealthStatusArgs{ + ListenerPort: 90, + } + _, err := BLB_CLIENT.DescribeHealthStatus(BLB_ID, describeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RemoveBackendServers(t *testing.T) { + deleteArgs := &RemoveBackendServersArgs{ + BackendServerList: []string{INSTANCE_ID}, + ClientToken: getClientToken(), + } + err := BLB_CLIENT.RemoveBackendServers(BLB_ID, deleteArgs) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteListeners(t *testing.T) { + deleteArgs := &DeleteListenersArgs{ + PortList: []uint16{90, 91, 92, 93, 94}, + ClientToken: getClientToken(), + } + err := BLB_CLIENT.DeleteListeners(BLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeletePortTypeListeners(t *testing.T) { + deleteArgs := &DeleteListenersArgs{ + PortTypeList: []PortTypeModel{ + { + Port: 80, + Type: "UDP", + }, + { + Port: 80, + Type: "HTTP", + }, + }, + ClientToken: getClientToken(), + } + err := BLB_CLIENT.DeleteListeners(BLB_ID, deleteArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteLoadBalancer(t *testing.T) { + err := BLB_CLIENT.DeleteLoadBalancer(BLB_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeLbClusters(t *testing.T) { + describeArgs := &DescribeLbClustersArgs{} + res, err := BLB_CLIENT.DescribeLbClusters(describeArgs) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeLbClusterDetail(t *testing.T) { + res, err := BLB_CLIENT.DescribeLbClusterDetail(CLUSTER_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateLoadBalancerAcl(t *testing.T) { + supportAcl := new(bool) + *supportAcl = true + updateArgs := &UpdateLoadBalancerAclArgs{ + ClientToken: getClientToken(), + SupportAcl: supportAcl, + } + err := BLB_CLIENT.UpdateLoadBalancerAcl(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindSecurityGroups(t *testing.T) { + updateArgs := &UpdateSecurityGroupsArgs{ + ClientToken: getClientToken(), + SecurityGroupIds: []string{"sg-id"}, + } + err := BLB_CLIENT.BindSecurityGroups(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindSecurityGroups(t *testing.T) { + updateArgs := &UpdateSecurityGroupsArgs{ + ClientToken: getClientToken(), + SecurityGroupIds: []string{"sg-id"}, + } + err := BLB_CLIENT.UnbindSecurityGroups(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeSecurityGroups(t *testing.T) { + res, err := BLB_CLIENT.DescribeSecurityGroups(BLB_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindEnterpriseSecurityGroups(t *testing.T) { + updateArgs := &UpdateEnterpriseSecurityGroupsArgs{ + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{"esg-id"}, + } + err := BLB_CLIENT.BindEnterpriseSecurityGroups(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindEnterpriseSecurityGroups(t *testing.T) { + updateArgs := &UpdateEnterpriseSecurityGroupsArgs{ + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{"esg-id"}, + } + err := BLB_CLIENT.UnbindEnterpriseSecurityGroups(BLB_ID, updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeEnterpriseSecurityGroups(t *testing.T) { + res, err := BLB_CLIENT.DescribeEnterpriseSecurityGroups(BLB_ID) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/blb/config.json b/bce-sdk-go/services/blb/config.json new file mode 100644 index 0000000..5a6634f --- /dev/null +++ b/bce-sdk-go/services/blb/config.json @@ -0,0 +1,5 @@ +{ + "AK":"", + "SK":"", + "Endpoint":"" +} diff --git a/bce-sdk-go/services/blb/enterprisesecuritygroups.go b/bce-sdk-go/services/blb/enterprisesecuritygroups.go new file mode 100644 index 0000000..6c58641 --- /dev/null +++ b/bce-sdk-go/services/blb/enterprisesecuritygroups.go @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// enterprisesecuritygroups.go - the enterprisesecuritygroup APIs definition supported by the BLB service + +package blb + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// BindEnterpriseSecurityGroups - bind the blb enterprise security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update enterprise security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) BindEnterpriseSecurityGroups(blbId string, args *UpdateEnterpriseSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.EnterpriseSecurityGroupIds) == 0 { + return fmt.Errorf("unset enterprise security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithQueryParam("bind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UnbindEnterpriseSecurityGroups - unbind the blb enterprise security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update enterprise security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UnbindEnterpriseSecurityGroups(blbId string, args *UpdateEnterpriseSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.EnterpriseSecurityGroupIds) == 0 { + return fmt.Errorf("unset enterprise security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithQueryParam("unbind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeEnterpriseSecurityGroups - describe all enterprise security groups of the specified LoadBalancer (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// +// RETURNS: +// - *DescribeEnterpriseSecurityGroupsResult: the result of describe all enterprise security groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeEnterpriseSecurityGroups(blbId string) (*DescribeEnterpriseSecurityGroupsResult, error) { + + result := &DescribeEnterpriseSecurityGroupsResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEnterpriseSecurityGroupUri(blbId)). + WithResult(result) + + err := request.Do() + return result, err +} diff --git a/bce-sdk-go/services/blb/listener.go b/bce-sdk-go/services/blb/listener.go new file mode 100644 index 0000000..5c1937d --- /dev/null +++ b/bce-sdk-go/services/blb/listener.go @@ -0,0 +1,542 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// listener.go - the Normal BLB Listener APIs definition supported by the BLB service + +package blb + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateTCPListener - create a TCP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create TCP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateTCPListener(blbId string, args *CreateTCPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if args.BackendPort == 0 { + return fmt.Errorf("unsupport backend port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTCPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateUDPListener - create a UDP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create UDP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateUDPListener(blbId string, args *CreateUDPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if args.BackendPort == 0 { + return fmt.Errorf("unsupport backend port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.HealthCheckString) == 0 { + return fmt.Errorf("unset healthCheckString") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getUDPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateHTTPListener - create a HTTP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create HTTP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateHTTPListener(blbId string, args *CreateHTTPListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if args.BackendPort == 0 { + return fmt.Errorf("unsupport backend port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getHTTPListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateHTTPSListener - create a HTTPS Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create HTTPS Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateHTTPSListener(blbId string, args *CreateHTTPSListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if args.BackendPort == 0 { + return fmt.Errorf("unsupport backend port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getHTTPSListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// CreateAppSSLListener - create a SSL Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to create SSL Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) CreateSSLListener(blbId string, args *CreateSSLListenerArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.ListenerPort == 0 { + return fmt.Errorf("unsupport listener port") + } + + if args.BackendPort == 0 { + return fmt.Errorf("unsupport backend port") + } + + if len(args.Scheduler) == 0 { + return fmt.Errorf("unset scheduler") + } + + if len(args.CertIds) == 0 { + return fmt.Errorf("unset certIds") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getSSLListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateTCPListener - update a TCP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update TCP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateTCPListener(blbId string, args *UpdateTCPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getTCPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateUDPListener - update a UDP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update UDP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateUDPListener(blbId string, args *UpdateUDPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUDPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateHTTPListener - update a HTTP Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update HTTP Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateHTTPListener(blbId string, args *UpdateHTTPListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getHTTPListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateHTTPSListener - update a HTTPS Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update HTTPS Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateHTTPSListener(blbId string, args *UpdateHTTPSListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getHTTPSListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateSSLListener - update a SSL Listener +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to update SSL Listener +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateSSLListener(blbId string, args *UpdateSSLListenerArgs) error { + if args == nil || args.ListenerPort == 0 { + return fmt.Errorf("unset listener port") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getSSLListenerUri(blbId)). + WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeTCPListeners - describe all TCP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all TCP Listeners +// +// RETURNS: +// - *DescribeTCPListenersResult: the result of describe all TCP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeTCPListeners(blbId string, args *DescribeListenerArgs) (*DescribeTCPListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeTCPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTCPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeUDPListeners - describe all UDP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all UDP Listeners +// +// RETURNS: +// - *DescribeUDPListenersResult: the result of describe all UDP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeUDPListeners(blbId string, args *DescribeListenerArgs) (*DescribeUDPListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeUDPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getUDPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeHTTPListeners - describe all HTTP Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all HTTP Listeners +// +// RETURNS: +// - *DescribeHTTPListenersResult: the result of describe all HTTP Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeHTTPListeners(blbId string, args *DescribeListenerArgs) (*DescribeHTTPListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeHTTPListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getHTTPListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeHTTPSListeners - describe all HTTPS Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all HTTPS Listeners +// +// RETURNS: +// - *DescribeHTTPSListenersResult: the result of describe all HTTPS Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeHTTPSListeners(blbId string, args *DescribeListenerArgs) (*DescribeHTTPSListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeHTTPSListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getHTTPSListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeSSLListeners - describe all SSL Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all SSL Listeners +// +// RETURNS: +// - *DescribeSSLListenersResult: the result of describe all SSL Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeSSLListeners(blbId string, args *DescribeListenerArgs) (*DescribeSSLListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeSSLListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSSLListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DescribeAllListeners - describe all Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to describe all Listeners +// +// RETURNS: +// - *DescribeAllListenersResult: the result of describe all Listeners +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeAllListeners(blbId string, args *DescribeListenerArgs) (*DescribeAllListenersResult, error) { + if args == nil { + args = &DescribeListenerArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &DescribeAllListenersResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getListenerUri(blbId)). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + if args.ListenerPort != 0 { + request.WithQueryParam("listenerPort", strconv.Itoa(int(args.ListenerPort))) + } + + err := request.Do() + return result, err +} + +// DeleteListeners - delete Listeners +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: parameters to delete Listeners, a listener port list +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteListeners(blbId string, args *DeleteListenersArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.PortList) == 0 && len(args.PortTypeList) == 0 { + return fmt.Errorf("unset port list") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getListenerUri(blbId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("batchdelete", ""). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/blb/model.go b/bce-sdk-go/services/blb/model.go new file mode 100644 index 0000000..b37a5cb --- /dev/null +++ b/bce-sdk-go/services/blb/model.go @@ -0,0 +1,632 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package blb + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type BLBStatus string + +const ( + BLBStatusCreating BLBStatus = "creating" + BLBStatusAvailable BLBStatus = "available" + BLBStatusUpdating BLBStatus = "updating" + BLBStatusPaused BLBStatus = "paused" + BLBStatusUnavailable BLBStatus = "unavailable" +) + +type DescribeResultMeta struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type CreateLoadBalancerArgs struct { + ClientToken string `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + SubnetId string `json:"subnetId"` + VpcId string `json:"vpcId"` + ClusterProperty string `json:"clusterProperty"` + Type string `json:"type,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +type CreateLoadBalancerResult struct { + Address string `json:"address"` + Name string `json:"name"` + Description string `json:"desc"` + BlbId string `json:"blbId"` +} + +type UpdateLoadBalancerArgs struct { + ClientToken string `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"desc,omitempty"` + AllowDelete *bool `json:"allowDelete,omitempty"` +} + +type UpdateLoadBalancerAclArgs struct { + ClientToken string `json:"-"` + SupportAcl *bool `json:"supportAcl,omitempty"` +} + +type DescribeLoadBalancersArgs struct { + Address string + Name string + BlbId string + BccId string + ExactlyMatch bool + Marker string + MaxKeys int + Type string +} + +type BLBModel struct { + BlbId string `json:"blbId"` + Name string `json:"name"` + Description string `json:"desc"` + Address string `json:"address"` + Status BLBStatus `json:"status"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + PublicIp string `json:"publicIp"` + Layer4ClusterId string `json:"layer4ClusterId"` + Layer7ClusterId string `json:"layer7ClusterId"` + Tags []model.TagModel `json:"tags"` + EipRouteType string `json:"eipRouteType"` + AllowDelete bool `json:"allowDelete"` +} + +type DescribeLoadBalancersResult struct { + BlbList []BLBModel `json:"blbList"` + DescribeResultMeta +} + +type ListenerModel struct { + Port string `json:"port"` + Type string `json:"type"` +} + +type PortTypeModel struct { + Port int `json:"port"` + Type string `json:"type"` +} + +type DescribeLoadBalancerDetailResult struct { + BlbId string `json:"blbId"` + Status BLBStatus `json:"status"` + Name string `json:"name"` + Description string `json:"desc"` + Address string `json:"address"` + PublicIp string `json:"publicIp"` + Cidr string `json:"cidr"` + VpcName string `json:"vpcName"` + CreateTime string `json:"createTime"` + Layer4ClusterId string `json:"layer4ClusterId"` + Layer7ClusterId string `json:"layer7ClusterId"` + Listener []ListenerModel `json:"listener"` + Tags []model.TagModel `json:"tags"` + EipRouteType string `json:"eipRouteType"` + Ipv6 string `json:"ipv6,omitempty"` +} + +type CreateTCPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` +} + +type CreateUDPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` + HealthCheckString string `json:"healthCheckString"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` +} + +type CreateHTTPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionDuration int `json:"keepSessionDuration,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + HealthCheckType string `json:"healthCheckType,omitempty"` + HealthCheckPort uint16 `json:"healthCheckPort,omitempty"` + HealthCheckURI string `json:"healthCheckURI,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + RedirectPort uint16 `json:"redirectPort,omitempty"` +} + +type CreateHTTPSListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + CertIds []string `json:"certIds"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionDuration int `json:"keepSessionDuration,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + HealthCheckType string `json:"healthCheckType,omitempty"` + HealthCheckPort uint16 `json:"healthCheckPort,omitempty"` + HealthCheckURI string `json:"healthCheckURI,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + RedirectPort uint16 `json:"redirectPort,omitempty"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type CreateSSLListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + CertIds []string `json:"certIds"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type UpdateListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + Scheduler string `json:"scheduler,omitempty"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` +} + +type UpdateTCPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + BackendPort uint16 `json:"backendPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + TcpSessionTimeout int `json:"tcpSessionTimeout,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` +} + +type UpdateUDPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + BackendPort uint16 `json:"backendPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + UdpSessionTimeout int `json:"udpSessionTimeout,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckString string `json:"healthCheckString,omitempty"` +} + +type UpdateHTTPListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + BackendPort uint16 `json:"backendPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionDuration int `json:"keepSessionDuration,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + HealthCheckType string `json:"healthCheckType,omitempty"` + HealthCheckPort uint16 `json:"healthCheckPort,omitempty"` + HealthCheckURI string `json:"healthCheckURI,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + RedirectPort uint16 `json:"redirectPort,omitempty"` +} + +type UpdateHTTPSListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + KeepSession bool `json:"keepSession,omitempty"` + KeepSessionType string `json:"keepSessionType,omitempty"` + KeepSessionDuration int `json:"keepSessionDuration,omitempty"` + KeepSessionCookieName string `json:"keepSessionCookieName,omitempty"` + XForwardedFor bool `json:"xForwardedFor,omitempty"` + HealthCheckType string `json:"healthCheckType,omitempty"` + HealthCheckPort uint16 `json:"healthCheckPort,omitempty"` + HealthCheckURI string `json:"healthCheckURI,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus,omitempty"` + ServerTimeout int `json:"serverTimeout,omitempty"` + CertIds []string `json:"certIds,omitempty"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` +} + +type UpdateSSLListenerArgs struct { + ClientToken string `json:"-"` + ListenerPort uint16 `json:"-"` + BackendPort uint16 `json:"backendPort,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval,omitempty"` + UnhealthyThreshold int `json:"unhealthyThreshold,omitempty"` + HealthyThreshold int `json:"healthyThreshold,omitempty"` + CertIds []string `json:"certIds,omitempty"` + EncryptionType string `json:"encryptionType,omitempty"` + EncryptionProtocols []string `json:"encryptionProtocols,omitempty"` + AppliedCiphers string `json:"appliedCiphers,omitempty"` + DualAuth bool `json:"dualAuth,omitempty"` + ClientCertIds []string `json:"clientCertIds,omitempty"` +} + +type TCPListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + GetBlbIp bool `json:"getBlbIp"` + TcpSessionTimeout int `json:"tcpSessionTimeout"` +} + +type UDPListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + UdpSessionTimeout int `json:"udpSessionTimeout"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + GetBlbIp bool `json:"getBlbIp"` + HealthCheckString string `json:"healthCheckString"` +} + +type HTTPListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionDuration int `json:"keepSessionDuration"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + HealthCheckType string `json:"healthCheckType"` + HealthCheckPort uint16 `json:"healthCheckPort"` + HealthCheckURI string `json:"healthCheckURI"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + GetBlbIp bool `json:"getBlbIp"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + ServerTimeout int `json:"serverTimeout"` + RedirectPort int `json:"redirectPort"` +} + +type HTTPSListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionDuration int `json:"keepSessionDuration"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + HealthCheckType string `json:"healthCheckType"` + HealthCheckPort uint16 `json:"healthCheckPort"` + HealthCheckURI string `json:"healthCheckURI"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + GetBlbIp bool `json:"getBlbIp"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + ServerTimeout int `json:"serverTimeout"` + CertIds []string `json:"certIds"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` +} + +type SSLListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + GetBlbIp bool `json:"getBlbIp"` + CertIds []string `json:"certIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` + ServerTimeout int `json:"serverTimeout"` +} + +type AllListenerModel struct { + ListenerPort uint16 `json:"listenerPort"` + ListenerType string `json:"listenerType"` + BackendPort uint16 `json:"backendPort"` + Scheduler string `json:"scheduler"` + GetBlbIp bool `json:"getBlbIp"` + TcpSessionTimeout int `json:"tcpSessionTimeout"` + UdpSessionTimeout int `json:"udpSessionTimeout"` + HealthCheckString string `json:"healthCheckString"` + KeepSession bool `json:"keepSession"` + KeepSessionType string `json:"keepSessionType"` + KeepSessionDuration int `json:"keepSessionDuration"` + KeepSessionCookieName string `json:"keepSessionCookieName"` + XForwardedFor bool `json:"xForwardedFor"` + HealthCheckType string `json:"healthCheckType"` + HealthCheckPort uint16 `json:"healthCheckPort"` + HealthCheckURI string `json:"healthCheckURI"` + HealthCheckTimeoutInSecond int `json:"healthCheckTimeoutInSecond"` + HealthCheckInterval int `json:"healthCheckInterval"` + UnhealthyThreshold int `json:"unhealthyThreshold"` + HealthyThreshold int `json:"healthyThreshold"` + HealthCheckNormalStatus string `json:"healthCheckNormalStatus"` + HealthCheckHost string `json:"healthCheckHost"` + ServerTimeout int `json:"serverTimeout"` + RedirectPort int `json:"redirectPort"` + CertIds []string `json:"certIds"` + DualAuth bool `json:"dualAuth"` + ClientCertIds []string `json:"clientCertIds"` + EncryptionType string `json:"encryptionType"` + EncryptionProtocols []string `json:"encryptionProtocols"` + AppliedCiphers string `json:"appliedCiphers"` +} + +type DescribeListenerArgs struct { + ListenerPort uint16 + Marker string + MaxKeys int +} + +type DescribeTCPListenersResult struct { + ListenerList []TCPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeUDPListenersResult struct { + ListenerList []UDPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeHTTPListenersResult struct { + ListenerList []HTTPListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeHTTPSListenersResult struct { + ListenerList []HTTPSListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeSSLListenersResult struct { + ListenerList []SSLListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DescribeAllListenersResult struct { + AllListenerList []AllListenerModel `json:"listenerList"` + DescribeResultMeta +} + +type DeleteListenersArgs struct { + ClientToken string `json:"-"` + PortList []uint16 `json:"portList"` + PortTypeList []PortTypeModel `json:"portTypeList"` +} + +type AddBackendServersArgs struct { + ClientToken string `json:"-"` + BackendServerList []BackendServerModel `json:"backendServerList"` +} + +type BackendServerModel struct { + InstanceId string `json:"instanceId"` + Weight int `json:"weight"` + PrivateIp string `json:"privateIp,omitempty"` +} + +type BackendServerStatus struct { + InstanceId string `json:"instanceId"` + Weight int `json:"weight"` + Status string `json:"status"` + PrivateIp string `json:"privateIp"` +} + +type UpdateBackendServersArgs struct { + ClientToken string `json:"-"` + BackendServerList []BackendServerModel `json:"backendServerList"` +} + +type DescribeBackendServersArgs struct { + Marker string + MaxKeys int +} + +type DescribeBackendServersResult struct { + BackendServerList []BackendServerModel `json:"backendServerList"` + DescribeResultMeta +} + +type DescribeHealthStatusArgs struct { + ListenerPort uint16 + Marker string + MaxKeys int +} + +type DescribeHealthStatusResult struct { + BackendServerList []BackendServerStatus `json:"backendServerList"` + Type string `json:"type"` + ListenerPort uint16 `json:"listenerPort"` + BackendPort uint16 `json:"backendPort"` + DescribeResultMeta +} + +type RemoveBackendServersArgs struct { + ClientToken string `json:"-"` + BackendServerList []string `json:"backendServerList"` +} + +type DescribeLbClusterDetailResult struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + ClusterType string `json:"clusterType"` + ClusterRegion string `json:"clusterRegion"` + ClusterAz string `json:"clusterAz"` + TotalConnectCount uint64 `json:"totalConnectCount"` + NewConnectCps uint64 `json:"newConnectCps"` + NetworkInBps uint64 `json:"networkInBps"` + NetworkOutBps uint64 `json:"networkOutBps"` + NetworkInPps uint64 `json:"networkInPps"` + NetworkOutPps uint64 `json:"networkOutPps"` + HttpsQps uint64 `json:"httpsQps"` + HttpQps uint64 `json:"httpQps"` + HttpNewConnectCps uint64 `json:"httpNewConnectCps"` + HttpsNewConnectCps uint64 `json:"httpsNewConnectCps"` +} + +type DescribeLbClustersArgs struct { + ClusterName string + ClusterId string + ExactlyMatch bool + Marker string + MaxKeys int +} + +type DescribeLbClustersResult struct { + ClusterList []ClusterModel `json:"clusterList"` + DescribeResultMeta +} + +type ClusterModel struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + ClusterType string `json:"clusterType"` + ClusterRegion string `json:"clusterRegion"` + ClusterAz string `json:"clusterAz"` +} + +type UpdateSecurityGroupsArgs struct { + ClientToken string `json:"-"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type UpdateEnterpriseSecurityGroupsArgs struct { + ClientToken string `json:"-"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` +} + +type DescribeSecurityGroupsResult struct { + BlbSecurityGroups []BlbSecurityGroupModel `json:"blbSecurityGroups"` +} + +type DescribeEnterpriseSecurityGroupsResult struct { + BlbEnterpriseSecurityGroups []BlbEnterpriseSecurityGroupModel `json:"enterpriseSecurityGroups"` +} + +type BlbSecurityGroupModel struct { + SecurityGroupId string `json:"securityGroupId"` + SecurityGroupName string `json:"securityGroupName"` + SecurityGroupDesc string `json:"securityGroupDesc"` + VpcName string `json:"vpcName"` + SecurityGroupRules []BlbSecurityGroupRuleModel `json:"securityGroupRules"` +} + +type BlbEnterpriseSecurityGroupModel struct { + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId"` + EnterpriseSecurityGroupName string `json:"enterpriseSecurityGroupName"` + EnterpriseSecurityGroupDesc string `json:"enterpriseSecurityGroupDesc"` + EnterpriseSecurityGroupRules []BlbEnterpriseSecurityGroupRuleModel `json:"enterpriseSecurityGroupRules"` +} + +type BlbSecurityGroupRuleModel struct { + SecurityGroupRuleId string `json:"securityGroupRuleId"` + Direction string `json:"direction"` + Ethertype string `json:"ethertype,omitempty"` + PortRange string `json:"portRange,omitempty"` + Protocol string `json:"protocol,omitempty"` + SourceGroupId string `json:"sourceGroupId,omitempty"` + SourceIp string `json:"sourceIp,omitempty"` + DestGroupId string `json:"destGroupId,omitempty"` + DestIp string `json:"destIp,omitempty"` +} + +type BlbEnterpriseSecurityGroupRuleModel struct { + EnterpriseSecurityGroupRuleId string `json:"enterpriseSecurityGroupRuleId"` + Direction string `json:"direction"` + Action string `json:"action"` + Priority int `json:"priority"` + Remark string `json:"remark"` + Ethertype string `json:"ethertype,omitempty"` + PortRange string `json:"portRange,omitempty"` + Protocol string `json:"protocol,omitempty"` + SourceIp string `json:"sourceIp,omitempty"` + DestIp string `json:"destIp,omitempty"` +} diff --git a/bce-sdk-go/services/blb/securitygroup.go b/bce-sdk-go/services/blb/securitygroup.go new file mode 100644 index 0000000..d41e799 --- /dev/null +++ b/bce-sdk-go/services/blb/securitygroup.go @@ -0,0 +1,95 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// securitygroup.go - the securitygroup APIs definition supported by the BLB service + +package blb + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// BindSecurityGroups - bind the blb security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) BindSecurityGroups(blbId string, args *UpdateSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.SecurityGroupIds) == 0 { + return fmt.Errorf("unset security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getSecurityGroupUri(blbId)). + WithQueryParam("bind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UnbindSecurityGroups - unbind the blb security groups (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// - args: the parameter to update security groups +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UnbindSecurityGroups(blbId string, args *UpdateSecurityGroupsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if len(args.SecurityGroupIds) == 0 { + return fmt.Errorf("unset security group ids") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getSecurityGroupUri(blbId)). + WithQueryParam("unbind", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DescribeSecurityGroups - describe all security groups of the specified LoadBalancer (normal/application/ipv6 LoadBalancer) +// +// PARAMS: +// - blbId: LoadBalancer's ID +// +// RETURNS: +// - *DescribeSecurityGroupsResult: the result of describe all security groups +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeSecurityGroups(blbId string) (*DescribeSecurityGroupsResult, error) { + + result := &DescribeSecurityGroupsResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupUri(blbId)). + WithResult(result) + + err := request.Do() + return result, err +} diff --git a/bce-sdk-go/services/bls/api/fastquery.go b/bce-sdk-go/services/bls/api/fastquery.go new file mode 100644 index 0000000..532a3c9 --- /dev/null +++ b/bce-sdk-go/services/bls/api/fastquery.go @@ -0,0 +1,168 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// fastquery.go - the fastQuery APIs definition supported by the BLS service + +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateFastQuery - create a fastQuery +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: the fastQuery body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CreateFastQuery(cli bce.Client, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(FASTQUERY_PREFIX) + req.SetMethod(http.POST) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DescribeFastQuery - get specific fastQuery info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - fastQueryName: fastQuery name to get +// +// RETURNS: +// - *FastQuery: target fastQuery info +// - error: nil if success otherwise the specific error +func DescribeFastQuery(cli bce.Client, fastQueryName string) (*FastQuery, error) { + req := &bce.BceRequest{} + req.SetUri(getFastQueryUri(fastQueryName)) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &FastQuery{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// UpdateFastQuery - update specific fastQuery info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: update fastQuery body +// - fastQueryName: fastQuery to update +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateFastQuery(cli bce.Client, body *bce.Body, fastQueryName string) error { + req := &bce.BceRequest{} + req.SetUri(getFastQueryUri(fastQueryName)) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteFastQuery - delete specific fastQuery +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - fastQueryName: fastQuery name to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteFastQuery(cli bce.Client, fastQueryName string) error { + req := &bce.BceRequest{} + req.SetUri(getFastQueryUri(fastQueryName)) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// ListFastQuery - get all fastQuery info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: query args to get pattern-match fastQuery +// +// RETURNS: +// - *ListFastQueryResult: pattern-match fastQuery result +// - error: nil if success otherwise the specific error +func ListFastQuery(cli bce.Client, args *QueryConditions) (*ListFastQueryResult, error) { + req := &bce.BceRequest{} + req.SetUri(FASTQUERY_PREFIX) + req.SetMethod(http.GET) + // Set optional args + if args != nil { + if args.NamePattern != "" { + req.SetParam("namePattern", args.NamePattern) + } + if args.Order != "" { + req.SetParam("order", args.Order) + } + if args.OrderBy != "" { + req.SetParam("orderBy", args.OrderBy) + } + if args.PageNo > 0 { + req.SetParam("pageNo", strconv.Itoa(args.PageNo)) + } + if args.PageSize > 0 { + req.SetParam("pageSize", strconv.Itoa(args.PageSize)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListFastQueryResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bls/api/index.go b/bce-sdk-go/services/bls/api/index.go new file mode 100644 index 0000000..5a3b92c --- /dev/null +++ b/bce-sdk-go/services/bls/api/index.go @@ -0,0 +1,122 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// index.go - the Index APIs definition supported by the BLS service + +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateIndex - create index for logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStoreName: logStore needs to be indexed +// - body: index mappings body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CreateIndex(cli bce.Client, logStoreName string, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getIndexUri(logStoreName)) + req.SetMethod(http.POST) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// UpdateIndex - update index info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStoreName: logStore needs to be updated +// - body: index mappings body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateIndex(cli bce.Client, logStoreName string, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getIndexUri(logStoreName)) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteIndex - delete index for logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStoreName: logStore to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteIndex(cli bce.Client, logStoreName string) error { + req := &bce.BceRequest{} + req.SetUri(getIndexUri(logStoreName)) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DescribeIndex - get specific logStore index info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStoreName: logStore needs to be get +// +// RETURNS: +// - *IndexFields: index mappings info +// - error: nil if success otherwise the specific error +func DescribeIndex(cli bce.Client, logStoreName string) (*IndexFields, error) { + req := &bce.BceRequest{} + req.SetUri(getIndexUri(logStoreName)) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &IndexFields{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bls/api/logrecord.go b/bce-sdk-go/services/bls/api/logrecord.go new file mode 100644 index 0000000..24aded2 --- /dev/null +++ b/bce-sdk-go/services/bls/api/logrecord.go @@ -0,0 +1,131 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// logrecord.go - the logRecord APIs definition supported by the BLS service + +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// PushLogRecord - push logRecords into logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: target logStore to store logRecords +// - body: logRecord body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PushLogRecord(cli bce.Client, logStore string, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getLogRecordUri(logStore)) + req.SetMethod(http.POST) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PullLogRecord - get logRecords from logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: target logStore to get logRecords +// - args: pull logRecords limitation args +// +// RETURNS: +// - *PullLogRecordResult: pull logRecord result set +// - error: nil if success otherwise the specific error +func PullLogRecord(cli bce.Client, logStore string, args *PullLogRecordArgs) (*PullLogRecordResult, error) { + req := &bce.BceRequest{} + req.SetUri(getLogRecordUri(logStore)) + req.SetMethod(http.GET) + if args != nil { + if args.LogStreamName != "" { + req.SetParam("logStreamName", args.LogStreamName) + } + if len(args.StartDateTime) != 0 { + req.SetParam("startDateTime", string(args.StartDateTime)) + } + if len(args.EndDateTime) != 0 { + req.SetParam("endDateTime", string(args.EndDateTime)) + } + if args.Limit > 0 && args.Limit <= 1000 { + req.SetParam("limit", strconv.Itoa(args.Limit)) + } + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &PullLogRecordResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// QueryLogRecord - retrieve logRecords from logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: target logStore to retrieve logRecords +// - args: query logRecords conditions args +// +// RETURNS: +// - *QueryLogResult: query logRecord result set +// - error: nil if success otherwise the specific error +func QueryLogRecord(cli bce.Client, logStore string, args *QueryLogRecordArgs) (*QueryLogResult, error) { + req := &bce.BceRequest{} + req.SetUri(getLogRecordUri(logStore)) + req.SetMethod(http.GET) + if args != nil { + req.SetParam("logStreamName", args.LogStreamName) + req.SetParam("query", args.Query) + req.SetParam("startDateTime", string(args.StartDateTime)) + req.SetParam("endDateTime", string(args.EndDateTime)) + if args.Limit > 0 { + req.SetParam("limit", strconv.Itoa(args.Limit)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &QueryLogResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bls/api/logshipper.go b/bce-sdk-go/services/bls/api/logshipper.go new file mode 100644 index 0000000..34dc542 --- /dev/null +++ b/bce-sdk-go/services/bls/api/logshipper.go @@ -0,0 +1,297 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// logshipper.go - the logShipper APIs definition supported by the BLS service + +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateLogShipper - create logShipper +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: logShipper parameters body +// +// RETURNS: +// - string: snowflake base64 id for logShipper, empty string if creation fail +// - error: nil if success otherwise the specific error +func CreateLogShipper(cli bce.Client, body *bce.Body) (string, error) { + req := &bce.BceRequest{} + req.SetUri(LOGSHIPPER_PREFIX) + req.SetMethod(http.POST) + if body != nil { + req.SetBody(body) + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + result := &CreateLogShipperResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return "", err + } + return result.LogShipperID, nil +} + +// UpdateLogShipper - update logShipper name and destConfig +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logShipperID: logShipperID to update +// - body: logShipper parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateLogShipper(cli bce.Client, body *bce.Body, logShipperID string) error { + req := &bce.BceRequest{} + req.SetUri(getLogShipperUri(logShipperID)) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetLogShipper - get logShipper info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logShipperID: logShipper to get +// +// RETURNS: +// - *LogShipper: logShipper info +// - error: nil if success otherwise the specific error +func GetLogShipper(cli bce.Client, logShipperID string) (*LogShipper, error) { + req := &bce.BceRequest{} + req.SetUri(getLogShipperUri(logShipperID)) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &LogShipper{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ListLogShipper - get all pattern-match logShipper info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: conditions logShipper should match +// +// RETURNS: +// - *ListShipperResult: logShipper result set +// - error: nil if success otherwise the specific error +func ListLogShipper(cli bce.Client, args *ListLogShipperCondition) (*ListShipperResult, error) { + req := &bce.BceRequest{} + req.SetUri(LOGSHIPPER_PREFIX) + req.SetMethod(http.GET) + if args != nil { + if args.LogShipperID != "" { + req.SetParam("logShipperID", args.LogShipperID) + } + if args.LogShipperName != "" { + req.SetParam("logShipperName", args.LogShipperName) + } + if args.LogShipperName != "" { + req.SetParam("logShipperName", args.LogShipperName) + } + if args.DestType != "" { + req.SetParam("destType", args.DestType) + } + if args.Status != "" { + req.SetParam("status", args.Status) + } + if args.Order != "" { + req.SetParam("order", args.Order) + } + if args.OrderBy != "" { + req.SetParam("orderBy", args.OrderBy) + } + if args.PageNo > 0 { + req.SetParam("pageNo", strconv.Itoa(args.PageNo)) + } + if args.PageSize > 0 { + req.SetParam("pageSize", strconv.Itoa(args.PageSize)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListShipperResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ListLogShipperRecord - get logShipper's execution records +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logShipperID: logShipper to get +// - args: conditions records should match +// +// RETURNS: +// - *ListShipperRecordResult: logShipper records result set +// - error: nil if success otherwise the specific error +func ListLogShipperRecord(cli bce.Client, logShipperID string, args *ListShipperRecordCondition) (*ListShipperRecordResult, error) { + req := &bce.BceRequest{} + req.SetUri(getLogShipperRecordUri(logShipperID)) + req.SetMethod(http.GET) + if args != nil { + if args.SinceHours > 0 { + req.SetParam("sinceHours", strconv.Itoa(args.SinceHours)) + } + if args.PageNo > 0 { + req.SetParam("pageNo", strconv.Itoa(args.PageNo)) + } + if args.PageSize > 0 { + req.SetParam("pageSize", strconv.Itoa(args.PageSize)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListShipperRecordResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteSingleLogShipper - delete logShipper by id +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logShipperID: logShipper to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteSingleLogShipper(cli bce.Client, logShipperID string) error { + req := &bce.BceRequest{} + req.SetUri(getLogShipperUri(logShipperID)) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// BulkDeleteLogShipper - bulk delete logShipper by id +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: bulkDeleteLogShipper parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BulkDeleteLogShipper(cli bce.Client, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(LOGSHIPPER_PREFIX) + req.SetMethod(http.DELETE) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// SetSingleLogShipperStatus - set logShipper status by id +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logShipperID: logShipper to set +// - body: setSingleLogShipperStatus parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetSingleLogShipperStatus(cli bce.Client, logShipperID string, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getLogShipperStatusUri(logShipperID)) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// BulkSetLogShipperStatus - bulk set logShipper status by id +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: bulkSetLogShipperStatus parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func BulkSetLogShipperStatus(cli bce.Client, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBulkSetLogShipperStatusUri()) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} diff --git a/bce-sdk-go/services/bls/api/logstore.go b/bce-sdk-go/services/bls/api/logstore.go new file mode 100644 index 0000000..eba5078 --- /dev/null +++ b/bce-sdk-go/services/bls/api/logstore.go @@ -0,0 +1,170 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// logstore.go - the logStore APIs definition supported by the BLS service + +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateLogStore - create logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: logStore parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func CreateLogStore(cli bce.Client, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(LOGSTORE_PREFIX) + req.SetMethod(http.POST) + if body != nil { + req.SetBody(body) + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// UpdateLogStore - update logStore retention +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: logStore to update +// - body: logStore parameters body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func UpdateLogStore(cli bce.Client, logStore string, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getLogStoreUri(logStore)) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DescribeLogStore - get logStore info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: logStore to get +// +// RETURNS: +// - *LogStore: logStore info +// - error: nil if success otherwise the specific error +func DescribeLogStore(cli bce.Client, logStore string) (*LogStore, error) { + req := &bce.BceRequest{} + req.SetUri(getLogStoreUri(logStore)) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &LogStore{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteLogStore - delete logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: logStore to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteLogStore(cli bce.Client, logStore string) error { + req := &bce.BceRequest{} + req.SetUri(getLogStoreUri(logStore)) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// ListLogStore - get all pattern-match logStore info +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: conditions logStore should match +// +// RETURNS: +// - *ListLogStoreResult: logStore result set +// - error: nil if success otherwise the specific error +func ListLogStore(cli bce.Client, args *QueryConditions) (*ListLogStoreResult, error) { + req := &bce.BceRequest{} + req.SetUri(LOGSTORE_PREFIX) + req.SetMethod(http.GET) + // Set optional args + if args != nil { + if args.NamePattern != "" { + req.SetParam("namePattern", args.NamePattern) + } + if args.Order != "" { + req.SetParam("order", args.Order) + } + if args.OrderBy != "" { + req.SetParam("orderBy", args.OrderBy) + } + if args.PageNo > 0 { + req.SetParam("pageNo", strconv.Itoa(args.PageNo)) + } + if args.PageSize > 0 { + req.SetParam("pageSize", strconv.Itoa(args.PageSize)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListLogStoreResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bls/api/logstream.go b/bce-sdk-go/services/bls/api/logstream.go new file mode 100644 index 0000000..f66ee45 --- /dev/null +++ b/bce-sdk-go/services/bls/api/logstream.go @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// logstream.go - the logStream APIs definition supported by the BLS service + +package api + +import ( + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListLogStream - get all pattern-match logStream in logStore +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - logStore: logStore to parse +// - args: conditions logStream should match +// +// RETURNS: +// - *ListLogStreamResult: pattern-match logStream result set +// - error: nil if success otherwise the specific error +func ListLogStream(cli bce.Client, logStore string, args *QueryConditions) (*ListLogStreamResult, error) { + req := &bce.BceRequest{} + req.SetUri(getLogStreamName(logStore)) + req.SetMethod(http.GET) + // Set optional args + if args != nil { + if args.NamePattern != "" { + req.SetParam("namePattern", args.NamePattern) + } + if args.Order != "" { + req.SetParam("order", args.Order) + } + if args.OrderBy != "" { + req.SetParam("orderBy", args.OrderBy) + } + if args.PageNo > 0 { + req.SetParam("pageNo", strconv.Itoa(args.PageNo)) + } + if args.PageSize > 0 { + req.SetParam("pageSize", strconv.Itoa(args.PageSize)) + } + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListLogStreamResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bls/api/model.go b/bce-sdk-go/services/bls/api/model.go new file mode 100644 index 0000000..cc3356a --- /dev/null +++ b/bce-sdk-go/services/bls/api/model.go @@ -0,0 +1,266 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +type DateTime string + +type LogRecord struct { + Message string `json:"message"` + Timestamp int64 `json:"timestamp"` + Sequence int `json:"sequence"` +} + +type LogStream struct { + CreationDateTime DateTime `json:"creationDateTime"` + LogStreamName string `json:"logStreamName"` +} + +type LogStore struct { + CreationDateTime DateTime `json:"creationDateTime"` + LastModifiedTime DateTime `json:"lastModifiedTime"` + LogStoreName string `json:"logStoreName"` + Retention int `json:"retention"` +} + +type LogShipper struct { + Status string `json:"status"` + LogShipperName string `json:"logShipperName"` + LogStoreName string `json:"logStoreName"` + StartTime string `json:"startTime"` + DestType string `json:"destType"` + DestConfig *ShipperDestConfig `json:"destConfig"` +} + +type QueryConditions struct { + NamePattern string `json:"namePattern"` + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type PushLogRecordBody struct { + LogStreamName string `json:"logStreamName"` + Type string `json:"type"` + LogRecords []LogRecord `json:"logRecords"` +} + +type QueryLogRecordArgs struct { + LogStreamName string `json:"logStreamName"` + Query string `json:"query"` + StartDateTime DateTime `json:"startDatetime"` + EndDateTime DateTime `json:"endDateTime"` + Limit int `json:"limit"` +} + +type PullLogRecordArgs struct { + LogStreamName string `json:"logStreamName"` + StartDateTime DateTime `json:"startDatetime"` + EndDateTime DateTime `json:"endDateTime"` + Limit int `json:"limit"` + Marker string `json:"marker"` +} + +type PullLogRecordResult struct { + Result []LogRecord `json:"result"` + IsTruncated bool `json:"isTruncated"` + Marker string `json:"marker"` + NextMarker string `json:"nextMarker"` +} + +type Histogram struct { + Interval int `json:"interval"` + StartDateTime DateTime `json:"startDatetime"` + EndDateTime DateTime `json:"endDateTime"` + Counts []int `json:"counts"` +} + +type Statistics struct { + ExecutionTimeInMs int `json:"executionTimeInMs"` + ScanCount int `json:"scanCount"` + Histogram *Histogram `json:"histogram"` +} + +type DataSetScanInfo struct { + IsTruncated bool `json:"isTruncated"` + TruncatedReason string `json:"truncatedReason"` + Statistics *Statistics `json:"statistics"` +} + +type ResultSet struct { + Columns []string `json:"columns"` + Rows [][]interface{} `json:"rows"` + IsTruncated bool `json:"isTruncated"` + TruncatedReason string `json:"truncatedReason"` +} + +type QueryLogResult struct { + ResultSet *ResultSet `json:"resultSet"` + DataSetScanInfo *DataSetScanInfo `json:"dataScanInfo"` +} + +type ListLogStreamResult struct { + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNumber int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []LogStream `json:"result"` +} + +type ListLogStoreResult struct { + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []LogStore `json:"result"` +} + +type FastQuery struct { + CreationDateTime DateTime `json:"creationDateTime"` + LastModifiedTime DateTime `json:"lastModifiedTime"` + FastQueryName string `json:"fastQueryName"` + Description string `json:"description"` + Query string `json:"query"` + LogStoreName string `json:"logStoreName"` + LogStreamName string `json:"logStreamName"` +} + +type CreateFastQueryBody struct { + FastQueryName string `json:"fastQueryName"` + Query string `json:"query"` + Description string `json:"description"` + LogStoreName string `json:"logStoreName"` + LogStreamName string `json:"logStreamName"` +} + +type UpdateFastQueryBody struct { + Query string `json:"query"` + Description string `json:"description"` + LogStoreName string `json:"logStoreName"` + LogStreamName string `json:"logStreamName"` +} + +type ListFastQueryResult struct { + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []FastQuery `json:"result"` +} + +type LogField struct { + Type string `json:"type"` + Fields map[string]LogField `json:"fields,omitempty"` +} + +type IndexFields struct { + FullText bool `json:"fulltext"` + Fields map[string]LogField `json:"fields"` +} + +type CreateLogShipperBody struct { + LogShipperName string `json:"logShipperName"` + LogStoreName string `json:"logStoreName"` + StartTime string `json:"startTime"` + DestType string `json:"destType"` + DestConfig *ShipperDestConfig `json:"destConfig"` +} + +type CreateLogShipperResponse struct { + LogShipperID string `json:"logShipperID"` +} + +type ShipperDestConfig struct { + BOSPath string `json:"BOSPath"` + PartitionFormatTS string `json:"partitionFormatTS"` + PartitionFormatLogStream bool `json:"partitionFormatLogStream"` + MaxObjectSize int64 `json:"maxObjectSize"` + CompressType string `json:"compressType"` + DeliverInterval int64 `json:"deliverInterval"` + StorageFormat string `json:"storageFormat"` + CsvHeadline bool `json:"csvHeadline"` + CsvDelimiter string `json:"csvDelimiter"` + CsvQuote string `json:"csvQuote"` + NullIdentifier string `json:"nullIdentifier"` + SelectedColumnName string `json:"selectedColumnName"` + SelectedColumnType string `json:"selectedColumnType"` +} + +type ListShipperRecordCondition struct { + SinceHours int `json:"sinceHours"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type ListShipperRecordResult struct { + TotalCount int `json:"totalCount"` + Result []LogShipperRecord `json:"result"` +} + +type LogShipperRecord struct { + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + FinishedCount int `json:"finishedCount"` +} + +type ListShipperResult struct { + TotalCount int `json:"totalCount"` + Result []ShipperSummary `json:"result"` +} + +type ShipperSummary struct { + LogShipperID string `json:"logShipperID"` + LogShipperName string `json:"logShipperName"` + LogStoreName string `json:"logStoreName"` + DestType string `json:"destType"` + Status string `json:"status"` + ErrMessage string `json:"errMessage"` + CreateDateTime string `json:"createDateTime"` +} + +type ListLogShipperCondition struct { + LogShipperID string `json:"logShipperID"` + LogShipperName string `json:"logShipperName"` + LogStoreName string `json:"logStoreName"` + DestType string `json:"destType"` + Status string `json:"status"` + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type UpdateLogShipperBody struct { + LogShipperName string `json:"logShipperName"` + DestConfig *ShipperDestConfig `json:"destConfig"` +} + +type BulkDeleteShipperCondition struct { + LogShipperIDs []string `json:"logShipperIDs"` +} + +type SetSingleShipperStatusCondition struct { + DesiredStatus string `json:"desiredStatus"` +} + +type BulkSetShipperStatusCondition struct { + LogShipperIDs []string `json:"logShipperIDs"` + DesiredStatus string `json:"desiredStatus"` +} diff --git a/bce-sdk-go/services/bls/api/utils.go b/bce-sdk-go/services/bls/api/utils.go new file mode 100644 index 0000000..323b2ea --- /dev/null +++ b/bce-sdk-go/services/bls/api/utils.go @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BLS service + +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + LOGSTORE_PREFIX = bce.URI_PREFIX + "v1" + bce.URI_PREFIX + "logstore" + FASTQUERY_PREFIX = bce.URI_PREFIX + "v1" + bce.URI_PREFIX + "fastquery" + LOGSHIPPER_PREFIX = bce.URI_PREFIX + "v1" + bce.URI_PREFIX + "logshipper" +) + +func getLogStoreUri(logStoreName string) string { + return LOGSTORE_PREFIX + bce.URI_PREFIX + logStoreName +} + +func getLogStreamName(logStoreName string) string { + return getLogStoreUri(logStoreName) + bce.URI_PREFIX + "logstream" +} + +func getLogRecordUri(logStoreName string) string { + return getLogStoreUri(logStoreName) + bce.URI_PREFIX + "logrecord" +} + +func getFastQueryUri(fastQuery string) string { + return FASTQUERY_PREFIX + bce.URI_PREFIX + fastQuery +} + +func getIndexUri(logStoreName string) string { + return getLogStoreUri(logStoreName) + bce.URI_PREFIX + "index" +} + +func getLogShipperUri(logShipperID string) string { + return LOGSHIPPER_PREFIX + bce.URI_PREFIX + logShipperID +} + +func getLogShipperRecordUri(logShipperID string) string { + return getLogShipperUri(logShipperID) + bce.URI_PREFIX + "record" +} + +func getLogShipperStatusUri(logShipperID string) string { + return getLogShipperUri(logShipperID) + bce.URI_PREFIX + "status" +} + +func getBulkSetLogShipperStatusUri() string { + return LOGSHIPPER_PREFIX + bce.URI_PREFIX + "status" + bce.URI_PREFIX + "batch" +} diff --git a/bce-sdk-go/services/bls/client.go b/bce-sdk-go/services/bls/client.go new file mode 100644 index 0000000..28715a9 --- /dev/null +++ b/bce-sdk-go/services/bls/client.go @@ -0,0 +1,286 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BLS service + +// Package bls defines the BLS services of BCE. The supported APIs are all defined in sub-package +// model with five types: 5 LogStore APIs, 1 LogStream API, 3 logRecord APIs, 5 FastQuery APIs +// and 3 index APIs. + +package bls + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bls/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "bls-log.bj.baidubce.com" +) + +type Client struct { + *bce.BceClient +} + +type BlsClientConfiguration struct { + Ak string + Sk string + Endpoint string +} + +func NewClient(ak, sk, endpoint string) (*Client, error) { + return NewClientWithConfig(&BlsClientConfiguration{ + Ak: ak, + Sk: sk, + Endpoint: endpoint, + }) +} + +func NewClientWithConfig(config *BlsClientConfiguration) (*Client, error) { + ak, sk, endpoint := config.Ak, config.Sk, config.Endpoint + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + client, _ := bce.NewBceClientWithAkSk(ak, sk, endpoint) + return &Client{client}, nil +} + +// LogStore opts +func (c *Client) CreateLogStore(logStore string, retention int) error { + params, jsonErr := json.Marshal(&api.LogStore{ + LogStoreName: logStore, + Retention: retention, + }) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return err + } + return api.CreateLogStore(c, body) +} + +func (c *Client) UpdateLogStore(logStore string, retention int) error { + param, jsonErr := json.Marshal(&api.LogStore{ + Retention: retention, + }) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(param)) + if err != nil { + return err + } + return api.UpdateLogStore(c, logStore, body) +} + +func (c *Client) DescribeLogStore(logStore string) (*api.LogStore, error) { + return api.DescribeLogStore(c, logStore) +} + +func (c *Client) DeleteLogStore(logStore string) error { + return api.DeleteLogStore(c, logStore) +} + +func (c *Client) ListLogStore(args *api.QueryConditions) (*api.ListLogStoreResult, error) { + return api.ListLogStore(c, args) +} + +// LogStream opt +func (c *Client) ListLogStream(logStore string, args *api.QueryConditions) (*api.ListLogStreamResult, error) { + return api.ListLogStream(c, logStore, args) +} + +// LogRecord opts +func (c *Client) PushLogRecord(logStore string, logStream string, logType string, logRecords []api.LogRecord) error { + params, jsonErr := json.Marshal(&api.PushLogRecordBody{ + LogStreamName: logStream, + Type: logType, + LogRecords: logRecords, + }) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return err + } + return api.PushLogRecord(c, logStore, body) +} + +func (c *Client) PullLogRecord(logStore string, args *api.PullLogRecordArgs) (*api.PullLogRecordResult, error) { + return api.PullLogRecord(c, logStore, args) +} + +func (c *Client) QueryLogRecord(logStore string, args *api.QueryLogRecordArgs) (*api.QueryLogResult, error) { + return api.QueryLogRecord(c, logStore, args) +} + +// FastQuery opts +func (c *Client) CreateFastQuery(args *api.CreateFastQueryBody) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.CreateFastQuery(c, body) +} + +func (c *Client) DescribeFastQuery(fastQueryName string) (*api.FastQuery, error) { + return api.DescribeFastQuery(c, fastQueryName) +} + +func (c *Client) UpdateFastQuery(fastQueryName string, args *api.UpdateFastQueryBody) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.UpdateFastQuery(c, body, fastQueryName) +} + +func (c *Client) DeleteFastQuery(fastQueryName string) error { + return api.DeleteFastQuery(c, fastQueryName) +} + +func (c *Client) ListFastQuery(args *api.QueryConditions) (*api.ListFastQueryResult, error) { + return api.ListFastQuery(c, args) +} + +// Index opts +func (c *Client) CreateIndex(logStore string, fulltext bool, fields map[string]api.LogField) error { + params, jsonErr := json.Marshal(&api.IndexFields{ + FullText: fulltext, + Fields: fields, + }) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return err + } + return api.CreateIndex(c, logStore, body) +} + +func (c *Client) UpdateIndex(logStore string, fulltext bool, fields map[string]api.LogField) error { + params, jsonErr := json.Marshal(&api.IndexFields{ + FullText: fulltext, + Fields: fields, + }) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return err + } + return api.UpdateIndex(c, logStore, body) +} + +func (c *Client) DeleteIndex(logStore string) error { + return api.DeleteIndex(c, logStore) +} + +func (c *Client) DescribeIndex(logStore string) (*api.IndexFields, error) { + return api.DescribeIndex(c, logStore) +} + +// LogShipper opts +func (c *Client) ListLogShipper(args *api.ListLogShipperCondition) (*api.ListShipperResult, error) { + return api.ListLogShipper(c, args) +} + +func (c *Client) CreateLogShipper(args *api.CreateLogShipperBody) (string, error) { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + + return "", jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + + return "", nil + } + return api.CreateLogShipper(c, body) +} + +func (c *Client) UpdateLogShipper(logShipperID string, args *api.UpdateLogShipperBody) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.UpdateLogShipper(c, body, logShipperID) +} + +func (c *Client) GetLogShipper(logShipperID string) (*api.LogShipper, error) { + return api.GetLogShipper(c, logShipperID) +} + +func (c *Client) ListLogShipperRecord(logShipperID string, args *api.ListShipperRecordCondition) (*api.ListShipperRecordResult, error) { + return api.ListLogShipperRecord(c, logShipperID, args) +} + +func (c *Client) DeleteSingleLogShipper(logShipperID string) error { + return api.DeleteSingleLogShipper(c, logShipperID) +} + +func (c *Client) BulkDeleteLogShipper(args *api.BulkDeleteShipperCondition) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.BulkDeleteLogShipper(c, body) +} + +func (c *Client) SetSingleLogShipperStatus(logShipperID string, args *api.SetSingleShipperStatusCondition) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.SetSingleLogShipperStatus(c, logShipperID, body) +} + +func (c *Client) BulkSetLogShipperStatus(args *api.BulkSetShipperStatusCondition) error { + params, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromString(string(params)) + if err != nil { + return nil + } + return api.BulkSetLogShipperStatus(c, body) +} diff --git a/bce-sdk-go/services/bls/client_test.go b/bce-sdk-go/services/bls/client_test.go new file mode 100644 index 0000000..a98465c --- /dev/null +++ b/bce-sdk-go/services/bls/client_test.go @@ -0,0 +1,549 @@ +package bls + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bls/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BLS_CLIENT *Client + EXIST_LOGSTORE = "bls-rd-wzy" + LOGSTREAM_PATTERN = "wzy" + JSON_LOGSTREAM = LOGSTREAM_PATTERN + "-JSON" + TEXT_LOGSTREAM = LOGSTREAM_PATTERN + "-TEXT" + FASTQUERY_NAME = "speedo" + LOGSHIPPER_NAME = "test-bls-sdk" + TEST_STARTTIME = "2021-07-06T19:01:00Z" + LOGSHIPPER_ID = "MjI3NDY5OTE5MTk3MzE1MDcy" + DEAFAULT_LOGSTORE = "ng-log" + DEAFAULT_BOSPATH = "/bls-test/bls-sdk/" + DEFAULT_TEST_DOMAIN = "10.132.106.242" +) + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &BlsClientConfiguration{} + err = decoder.Decode(confObj) + if err != nil { + fmt.Println(err) + return + } + // default use http protocol + BLS_CLIENT, _ = NewClient(confObj.Ak, confObj.Sk, DEFAULT_TEST_DOMAIN) + // log.SetLogHandler(log.STDERR | log.FILE) + // log.SetRotateType(log.ROTATE_SIZE) + log.SetLogLevel(log.WARN) + // log.SetLogHandler(log.STDERR) + // log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + actual interface{}, expected interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +// LogStore test +func TestCreateLogStore(t *testing.T) { + err := BLS_CLIENT.CreateLogStore(EXIST_LOGSTORE, 3) + ExpectEqual(t.Errorf, err, nil) + err = BLS_CLIENT.CreateLogStore(EXIST_LOGSTORE, 36) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusConflict) + } + res, err := BLS_CLIENT.DescribeLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.LogStoreName, EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res.Retention, 3) +} + +func TestUpdateLogStore(t *testing.T) { + err := BLS_CLIENT.UpdateLogStore(EXIST_LOGSTORE, 8) + ExpectEqual(t.Errorf, err, nil) + res, err := BLS_CLIENT.DescribeLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.LogStoreName, EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res.Retention, 8) + err = BLS_CLIENT.UpdateLogStore("not"+EXIST_LOGSTORE, 22) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestDescribeLogStore(t *testing.T) { + res, err := BLS_CLIENT.DescribeLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.LogStoreName, EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res.Retention, 8) + res, err = BLS_CLIENT.DescribeLogStore("not" + EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res, nil) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestListLogStore(t *testing.T) { + args := &api.QueryConditions{ + NamePattern: "bls-rd", + Order: "asc", + OrderBy: "creationDateTime", + PageNo: 1, + PageSize: 10} + res, err := BLS_CLIENT.ListLogStore(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(res.Result) > 0, true) +} + +// LogRecord test +func TestPushLogRecord(t *testing.T) { + jsonRecords := []api.LogRecord{ + { + Message: "{\"body_bytes_sent\":184,\"bytes_sent\":398,\"client_ip\":\"120.193.204.39\",\"connection\":915958195,\"hit\":1,\"host\":\"cdbb.wonter.net\"}", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 1, + }, + { + Message: "{\"body_bytes_sent\":14,\"bytes_sent\":408,\"client_ip\":\"120.193.222.39\",\"connection\":91567195,\"hit\":1,\"host\":\"cdbb.wonter.net\"}", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 2, + }, + } + err := BLS_CLIENT.PushLogRecord(EXIST_LOGSTORE, JSON_LOGSTREAM, "JSON", jsonRecords) + ExpectEqual(t.Errorf, err, nil) + textRecords := []api.LogRecord{ + { + Message: "You know, for test", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 3, + }, + { + Message: "Baidu Log Service", + Timestamp: time.Now().UnixNano() / 1e6, + Sequence: 4, + }, + } + err = BLS_CLIENT.PushLogRecord(EXIST_LOGSTORE, TEXT_LOGSTREAM, "TEXT", textRecords) + ExpectEqual(t.Errorf, err, nil) + tooNewRecord := []api.LogRecord{ + { + "LogRecord from future", + time.Now().UnixNano() / 1e4, + 12, + }, + } + err = BLS_CLIENT.PushLogRecord(EXIST_LOGSTORE, TEXT_LOGSTREAM, "TEXT", tooNewRecord) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusBadRequest) + } + err = BLS_CLIENT.PushLogRecord("not"+EXIST_LOGSTORE, TEXT_LOGSTREAM, "TEXT", textRecords) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestPullLogRecord(t *testing.T) { + args := &api.PullLogRecordArgs{ + LogStreamName: JSON_LOGSTREAM, + StartDateTime: "2021-01-01T10:11:44Z", + EndDateTime: "2021-12-10T16:11:44Z", + Limit: 500, + Marker: "", + } + res, err := BLS_CLIENT.PullLogRecord(EXIST_LOGSTORE, args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(res.Result) >= 2, true) + res, err = BLS_CLIENT.PullLogRecord("not"+EXIST_LOGSTORE, args) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + ExpectEqual(t.Errorf, res, nil) + } +} + +func TestQueryLogRecord(t *testing.T) { + args := &api.QueryLogRecordArgs{ + LogStreamName: JSON_LOGSTREAM, + Query: "select count(*)", + StartDateTime: "2021-01-01T10:11:44Z", + EndDateTime: "2021-12-10T16:11:44Z", + } + res, err := BLS_CLIENT.QueryLogRecord(EXIST_LOGSTORE, args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.ResultSet.Columns[0], "count(*)") +} + +// LogStream test +func TestListLogStream(t *testing.T) { + args := &api.QueryConditions{ + NamePattern: LOGSTREAM_PATTERN, + Order: "asc", + OrderBy: "creationDateTime", + PageNo: 1, + PageSize: 23, + } + res, err := BLS_CLIENT.ListLogStream(EXIST_LOGSTORE, args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.PageSize, 23) + res, err = BLS_CLIENT.ListLogStream("not"+EXIST_LOGSTORE, args) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + ExpectEqual(t.Errorf, res, nil) + } +} + +// FastQuery test +func TestCreateFastQuery(t *testing.T) { + args := &api.CreateFastQueryBody{ + FastQueryName: FASTQUERY_NAME, + Query: "select count(*)", + Description: "calculate record number", + LogStoreName: EXIST_LOGSTORE, + LogStreamName: JSON_LOGSTREAM, + } + err := BLS_CLIENT.CreateFastQuery(args) + ExpectEqual(t.Errorf, err, nil) + // Not specify logStream + err = BLS_CLIENT.CreateFastQuery(&api.CreateFastQueryBody{ + FastQueryName: FASTQUERY_NAME, + Query: "select count(*)", + Description: "duplicate", + LogStoreName: EXIST_LOGSTORE, + LogStreamName: "", + }) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusConflict) + } +} + +func TestDescribeFastQuery(t *testing.T) { + res, err := BLS_CLIENT.DescribeFastQuery(FASTQUERY_NAME) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.FastQueryName, FASTQUERY_NAME) + res, err = BLS_CLIENT.DescribeFastQuery("not" + FASTQUERY_NAME) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + ExpectEqual(t.Errorf, res, nil) + } +} + +func TestUpdateFastQuery(t *testing.T) { + args := &api.UpdateFastQueryBody{ + Query: "select * limit 3", + Description: "Top 3", + LogStoreName: EXIST_LOGSTORE, + LogStreamName: JSON_LOGSTREAM, + } + err := BLS_CLIENT.UpdateFastQuery(FASTQUERY_NAME, args) + ExpectEqual(t.Errorf, err, nil) + res, err := BLS_CLIENT.DescribeFastQuery(FASTQUERY_NAME) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Query, "select * limit 3") + err = BLS_CLIENT.UpdateFastQuery("not"+FASTQUERY_NAME, &api.UpdateFastQueryBody{ + Query: "select * limit 3", + Description: "return top 3 records", + LogStoreName: EXIST_LOGSTORE, + LogStreamName: "", + }) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestListFastQuery(t *testing.T) { + args := &api.QueryConditions{ + NamePattern: "s", + } + res, err := BLS_CLIENT.ListFastQuery(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(res.Result) >= 1, true) +} + +func TestDeleteFastQuery(t *testing.T) { + err := BLS_CLIENT.DeleteFastQuery(FASTQUERY_NAME) + ExpectEqual(t.Errorf, err, nil) + res, err := BLS_CLIENT.DescribeFastQuery(FASTQUERY_NAME) + ExpectEqual(t.Errorf, res, nil) + err = BLS_CLIENT.DeleteFastQuery("not" + FASTQUERY_NAME) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +// Index test +func TestCreateIndex(t *testing.T) { + fields := map[string]api.LogField{ + "age": { + Type: "long", + }, + "salary": { + Type: "text", + }, + "name": { + Type: "object", + Fields: map[string]api.LogField{ + "firstName": { + Type: "text", + }, + "lastName": { + Type: "text", + }, + }, + }, + } + err := BLS_CLIENT.CreateIndex(EXIST_LOGSTORE, true, fields) + ExpectEqual(t.Errorf, err, nil) + err = BLS_CLIENT.CreateIndex(EXIST_LOGSTORE, true, fields) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusConflict) + } +} + +func TestUpdateIndex(t *testing.T) { + fields := map[string]api.LogField{ + "age": { + Type: "long", + }, + "wage": { + Type: "float", + }, + "name": { + Type: "object", + Fields: map[string]api.LogField{ + "firstName": { + Type: "text", + }, + "midName": { + Type: "text", + }, + "lastName": { + Type: "text", + }, + }, + }, + } + err := BLS_CLIENT.UpdateIndex(EXIST_LOGSTORE, false, fields) + ExpectEqual(t.Errorf, err, nil) + err = BLS_CLIENT.UpdateIndex("not"+EXIST_LOGSTORE, false, fields) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestDescribeIndex(t *testing.T) { + res, err := BLS_CLIENT.DescribeIndex(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Fields["name"].Fields["midName"].Type, "text") + ExpectEqual(t.Errorf, res.Fields["wage"].Type, "float") + res, err = BLS_CLIENT.DescribeIndex("not" + EXIST_LOGSTORE) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + ExpectEqual(t.Errorf, res, nil) + } +} + +func TestDeleteIndex(t *testing.T) { + err := BLS_CLIENT.DeleteIndex(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + res, _ := BLS_CLIENT.DescribeIndex(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res, nil) + err = BLS_CLIENT.DeleteIndex(EXIST_LOGSTORE) // delete twice + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestDeleteLogStore(t *testing.T) { + res, err := BLS_CLIENT.DescribeLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.LogStoreName, EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res.Retention, 8) + err = BLS_CLIENT.DeleteLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, err, nil) + res, err = BLS_CLIENT.DescribeLogStore(EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res, nil) + res, err = BLS_CLIENT.DescribeLogStore("not" + EXIST_LOGSTORE) + ExpectEqual(t.Errorf, res, nil) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestClient_CreateLogShipper(t *testing.T) { + args := &api.CreateLogShipperBody{ + LogShipperName: LOGSHIPPER_NAME, + LogStoreName: DEAFAULT_LOGSTORE, + StartTime: TEST_STARTTIME, + DestConfig: &api.ShipperDestConfig{ + BOSPath: DEAFAULT_BOSPATH, + }, + } + id, err := BLS_CLIENT.CreateLogShipper(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(id) > 0, true) + args = &api.CreateLogShipperBody{ + LogShipperName: "invalid", + LogStoreName: "not-exist", + DestConfig: &api.ShipperDestConfig{}, + } + id, err = BLS_CLIENT.CreateLogShipper(args) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusBadRequest) + ExpectEqual(t.Errorf, id, "") + } +} + +func TestClient_UpdateLogShipper(t *testing.T) { + args := &api.UpdateLogShipperBody{ + LogShipperName: "shipper-sdk", + DestConfig: &api.ShipperDestConfig{ + PartitionFormatLogStream: true, + MaxObjectSize: 50, + CompressType: "snappy", + DeliverInterval: 30, + StorageFormat: "json", + }, + } + err := BLS_CLIENT.UpdateLogShipper(LOGSHIPPER_ID, args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestClient_GetLogShipper(t *testing.T) { + logShipper, err := BLS_CLIENT.GetLogShipper(LOGSHIPPER_ID) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, logShipper.LogStoreName, DEAFAULT_LOGSTORE) + ExpectEqual(t.Errorf, logShipper.DestConfig.BOSPath, DEAFAULT_BOSPATH) +} + +func TestClient_ListLogShipper(t *testing.T) { + args := &api.ListLogShipperCondition{ + LogShipperID: LOGSHIPPER_ID, + LogStoreName: DEAFAULT_LOGSTORE, + Status: "Running", + } + shipperInfo, err := BLS_CLIENT.ListLogShipper(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, shipperInfo.TotalCount, 1) +} + +func TestClient_ListLogShipperRecord(t *testing.T) { + args := &api.ListShipperRecordCondition{ + SinceHours: 20 * 24, + } + records, err := BLS_CLIENT.ListLogShipperRecord(LOGSHIPPER_ID, args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, records.TotalCount > 0, true) + ExpectEqual(t.Errorf, records.Result[0].FinishedCount > 0, true) +} + +func TestClient_SetSingleLogShipperStatus(t *testing.T) { + args := &api.SetSingleShipperStatusCondition{DesiredStatus: "Paused"} + err := BLS_CLIENT.SetSingleLogShipperStatus(LOGSHIPPER_ID, args) + ExpectEqual(t.Errorf, err, nil) + logShipper, err := BLS_CLIENT.GetLogShipper(LOGSHIPPER_ID) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, logShipper.Status, "Paused") +} + +func TestClient_BulkSetLogShipperStatus(t *testing.T) { + args := &api.BulkSetShipperStatusCondition{ + LogShipperIDs: []string{LOGSHIPPER_ID}, + DesiredStatus: "Running", + } + err := BLS_CLIENT.BulkSetLogShipperStatus(args) + ExpectEqual(t.Errorf, err, nil) + logShipper, err := BLS_CLIENT.GetLogShipper(LOGSHIPPER_ID) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, logShipper.Status, "Running") +} + +func TestClient_DeleteSingleLogShipper(t *testing.T) { + args := &api.CreateLogShipperBody{ + LogShipperName: LOGSHIPPER_NAME, + LogStoreName: DEAFAULT_LOGSTORE, + StartTime: TEST_STARTTIME, + DestConfig: &api.ShipperDestConfig{ + BOSPath: DEAFAULT_BOSPATH, + }, + } + id, err := BLS_CLIENT.CreateLogShipper(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(id) > 0, true) + time.Sleep(2 * time.Second) + err = BLS_CLIENT.DeleteSingleLogShipper(id) + ExpectEqual(t.Errorf, err, nil) + logShipper, err := BLS_CLIENT.GetLogShipper(id) + ExpectEqual(t.Errorf, logShipper, nil) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } +} + +func TestClient_BulkDeleteLogShipper(t *testing.T) { + var ids []string + for i := 0; i < 3; i++ { + args := &api.CreateLogShipperBody{ + LogShipperName: LOGSHIPPER_NAME, + LogStoreName: DEAFAULT_LOGSTORE, + StartTime: TEST_STARTTIME, + DestConfig: &api.ShipperDestConfig{ + BOSPath: DEAFAULT_BOSPATH, + }, + } + id, err := BLS_CLIENT.CreateLogShipper(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, len(id) > 0, true) + ids = append(ids, id) + } + time.Sleep(time.Second * 2) + args := &api.BulkDeleteShipperCondition{LogShipperIDs: ids} + err := BLS_CLIENT.BulkDeleteLogShipper(args) + ExpectEqual(t.Errorf, err, nil) + for _, id := range ids { + logShipper, err := BLS_CLIENT.GetLogShipper(id) + ExpectEqual(t.Errorf, logShipper, nil) + if realErr, ok := err.(*bce.BceServiceError); ok { + ExpectEqual(t.Errorf, realErr.StatusCode, http.StatusNotFound) + } + } +} diff --git a/bce-sdk-go/services/bos/api/bucket.go b/bce-sdk-go/services/bos/api/bucket.go new file mode 100644 index 0000000..e3fd6a1 --- /dev/null +++ b/bce-sdk-go/services/bos/api/bucket.go @@ -0,0 +1,1241 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// bucket.go - the bucket APIs definition supported by the BOS service + +// Package api defines all APIs supported by the BOS service of BCE. +package api + +import ( + "encoding/json" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListBuckets - list all buckets of the account +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *ListBucketsResult: the result bucket list structure +// - error: nil if ok otherwise the specific error +func ListBuckets(cli bce.Client) (*ListBucketsResult, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListBucketsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ListObjects - list all objects of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - args: the optional arguments to list objects +// +// RETURNS: +// - *ListObjectsResult: the result object list structure +// - error: nil if ok otherwise the specific error +func ListObjects(cli bce.Client, bucket string, + args *ListObjectsArgs) (*ListObjectsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + if len(args.Delimiter) != 0 { + req.SetParam("delimiter", args.Delimiter) + } + if len(args.Marker) != 0 { + req.SetParam("marker", args.Marker) + } + if args.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(args.MaxKeys)) + } + if len(args.Prefix) != 0 { + req.SetParam("prefix", args.Prefix) + } + } + if args == nil || args.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send the request and get result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListObjectsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return result, nil +} + +// HeadBucket - test the given bucket existed and access authority +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if exists and have authority otherwise the specific error +func HeadBucket(cli bce.Client, bucket string) (error, *bce.BceResponse) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.HEAD) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err, resp + } + if resp.IsFail() { + return resp.ServiceError(), resp + } + defer func() { resp.Body().Close() }() + return nil, resp +} + +// PutBucket - create a new bucket with the given name +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the new bucket name +// +// RETURNS: +// - string: the location of the new bucket if create success +// - error: nil if create success otherwise the specific error +func PutBucket(cli bce.Client, bucket string) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return resp.Header(http.LOCATION), nil +} + +// DeleteBucket - delete an empty bucket by given name +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name to be deleted +// +// RETURNS: +// - error: nil if delete success otherwise the specific error +func DeleteBucket(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketLocation - get the location of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - string: the location of the bucket +// - error: nil if delete success otherwise the specific error +func GetBucketLocation(cli bce.Client, bucket string) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("location", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + result := &LocationType{} + if err := resp.ParseJsonBody(result); err != nil { + return "", err + } + defer func() { resp.Body().Close() }() + return result.LocationConstraint, nil +} + +// PutBucketAcl - set the acl of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - cannedAcl: support private, public-read, public-read-write +// - aclBody: the acl file body +// +// RETURNS: +// - error: nil if delete success otherwise the specific error +func PutBucketAcl(cli bce.Client, bucket, cannedAcl string, aclBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("acl", "") + + // The acl setting + if len(cannedAcl) != 0 && aclBody != nil { + return bce.NewBceClientError("BOS does not support cannedAcl and acl file at the same time") + } + if validCannedAcl(cannedAcl) { + req.SetHeader(http.BCE_ACL, cannedAcl) + } + if aclBody != nil { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(aclBody) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketAcl - get the acl of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - *GetBucketAclResult: the result of the bucket acl +// - error: nil if success otherwise the specific error +func GetBucketAcl(cli bce.Client, bucket string) (*GetBucketAclResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("acl", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketAclResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return result, nil +} + +// PutBucketLogging - set the logging prefix of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - logging: the logging prefix json string body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketLogging(cli bce.Client, bucket string, logging *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("logging", "") + req.SetBody(logging) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketLogging - get the logging config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - *GetBucketLoggingResult: the logging setting of the bucket +// - error: nil if success otherwise the specific error +func GetBucketLogging(cli bce.Client, bucket string) (*GetBucketLoggingResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("logging", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketLoggingResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteBucketLogging - delete the logging setting of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketLogging(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("logging", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketLifecycle - set the lifecycle rule of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - lifecycle: the lifecycle rule json string body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketLifecycle(cli bce.Client, bucket string, lifecycle *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("lifecycle", "") + req.SetBody(lifecycle) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketLifecycle - get the lifecycle rule of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - *GetBucketLifecycleResult: the lifecycle rule of the bucket +// - error: nil if success otherwise the specific error +func GetBucketLifecycle(cli bce.Client, bucket string) (*GetBucketLifecycleResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("lifecycle", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketLifecycleResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteBucketLifecycle - delete the lifecycle rule of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketLifecycle(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("lifecycle", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketStorageclass - set the storage class of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - storageClass: the storage class string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketStorageclass(cli bce.Client, bucket, storageClass string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("storageClass", "") + + obj := &StorageClassType{storageClass} + jsonBytes, jsonErr := json.Marshal(obj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketStorageclass - get the storage class of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - string: the storage class of the bucket +// - error: nil if success otherwise the specific error +func GetBucketStorageclass(cli bce.Client, bucket string) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("storageClass", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + result := &StorageClassType{} + if err := resp.ParseJsonBody(result); err != nil { + return "", err + } + return result.StorageClass, nil +} + +// PutBucketReplication - set the bucket replication of different region +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - replicationConf: the replication config body stream +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketReplication(cli bce.Client, bucket string, replicationConf *bce.Body, replicationRuleId string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("replication", "") + if len(replicationRuleId) > 0 { + req.SetParam("id", replicationRuleId) + } + + if replicationConf != nil { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(replicationConf) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketReplication - get the bucket replication config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - *GetBucketReplicationResult: the result of the bucket replication config +// - error: nil if success otherwise the specific error +func GetBucketReplication(cli bce.Client, bucket string, replicationRuleId string) (*GetBucketReplicationResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("replication", "") + if len(replicationRuleId) > 0 { + req.SetParam("id", replicationRuleId) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketReplicationResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ListBucketReplication - list all replication config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func ListBucketReplication(cli bce.Client, bucket string) (*ListBucketReplicationResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("replication", "") + req.SetParam("list", "") + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListBucketReplicationResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteBucketReplication - delete the bucket replication config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketReplication(cli bce.Client, bucket string, replicationRuleId string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("replication", "") + if len(replicationRuleId) > 0 { + req.SetParam("id", replicationRuleId) + } + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketReplicationProgress - get the bucket replication process of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - *GetBucketReplicationProgressResult: the result of the bucket replication process +// - error: nil if success otherwise the specific error +func GetBucketReplicationProgress(cli bce.Client, bucket string, replicationRuleId string) ( + *GetBucketReplicationProgressResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("replicationProgress", "") + if len(replicationRuleId) > 0 { + req.SetParam("id", replicationRuleId) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketReplicationProgressResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// PutBucketEncryption - set the bucket encrpytion config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - algorithm: the encryption algorithm +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketEncryption(cli bce.Client, bucket, algorithm string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("encryption", "") + + obj := &BucketEncryptionType{algorithm} + jsonBytes, jsonErr := json.Marshal(obj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketEncryption - get the encryption config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - algorithm: the bucket encryption algorithm +// - error: nil if success otherwise the specific error +func GetBucketEncryption(cli bce.Client, bucket string) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("encryption", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + result := &BucketEncryptionType{} + if err := resp.ParseJsonBody(result); err != nil { + return "", err + } + return result.EncryptionAlgorithm, nil +} + +// DeleteBucketEncryption - delete the encryption config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketEncryption(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("encryption", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketStaticWebsite - set the bucket static website config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - confBody: the static website config body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketStaticWebsite(cli bce.Client, bucket string, confBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("website", "") + if confBody != nil { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(confBody) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketStaticWebsite - get the static website config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - result: the bucket static website config result object +// - error: nil if success otherwise the specific error +func GetBucketStaticWebsite(cli bce.Client, bucket string) ( + *GetBucketStaticWebsiteResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("website", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketStaticWebsiteResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteBucketStaticWebsite - delete the static website config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketStaticWebsite(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("website", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketCors - set the bucket CORS config +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - confBody: the CORS config body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketCors(cli bce.Client, bucket string, confBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("cors", "") + if confBody != nil { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(confBody) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketCors - get the CORS config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - result: the bucket CORS config result object +// - error: nil if success otherwise the specific error +func GetBucketCors(cli bce.Client, bucket string) ( + *GetBucketCorsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("cors", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketCorsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteBucketCors - delete the CORS config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketCors(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("cors", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketCopyrightProtection - set the copyright protection config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - resources: the resource items in the bucket to be protected +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketCopyrightProtection(cli bce.Client, bucket string, resources ...string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("copyrightProtection", "") + if len(resources) == 0 { + return bce.NewBceClientError("the resource to set copyright protection is empty") + } + arg := &CopyrightProtectionType{resources} + jsonBytes, jsonErr := json.Marshal(arg) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetBucketCopyrightProtection - get the copyright protection config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - result: the bucket copyright protection resources array +// - error: nil if success otherwise the specific error +func GetBucketCopyrightProtection(cli bce.Client, bucket string) ([]string, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("copyrightProtection", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CopyrightProtectionType{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result.Resource, nil +} + +// DeleteBucketCopyrightProtection - delete the copyright protection config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteBucketCopyrightProtection(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("copyrightProtection", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutBucketTrash - put the trash setting of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - trashDir: the trash dir name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutBucketTrash(cli bce.Client, bucket string, trashReq PutBucketTrashReq) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("trash", "") + reqByte, _ := json.Marshal(trashReq) + body, err := bce.NewBodyFromString(string(reqByte)) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func GetBucketTrash(cli bce.Client, bucket string) (*GetBucketTrashResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("trash", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetBucketTrashResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func DeleteBucketTrash(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("trash", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func PutBucketNotification(cli bce.Client, bucket string, putBucketNotificationReq PutBucketNotificationReq) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("notification", "") + reqByte, _ := json.Marshal(putBucketNotificationReq) + body, err := bce.NewBodyFromString(string(reqByte)) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func GetBucketNotification(cli bce.Client, bucket string) (*PutBucketNotificationReq, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("notification", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &PutBucketNotificationReq{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func DeleteBucketNotification(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("notification", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func PutBucketMirror(cli bce.Client, bucket string, putBucketMirrorArgs *PutBucketMirrorArgs) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("mirroring", "") + reqByte, _ := json.Marshal(putBucketMirrorArgs) + body, err := bce.NewBodyFromString(string(reqByte)) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func GetBucketMirror(cli bce.Client, bucket string) (*PutBucketMirrorArgs, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("mirroring", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &PutBucketMirrorArgs{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func DeleteBucketMirror(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("mirroring", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func PutBucketTag(cli bce.Client, bucket string, putBucketTagArgs *PutBucketTagArgs) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.PUT) + req.SetParam("tagging", "") + reqByte, _ := json.Marshal(putBucketTagArgs) + body, err := bce.NewBodyFromString(string(reqByte)) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func GetBucketTag(cli bce.Client, bucket string) (*PutBucketTagArgs, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("tagging", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &PutBucketTagArgs{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func DeleteBucketTag(cli bce.Client, bucket string) error { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.DELETE) + req.SetParam("tagging", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} diff --git a/bce-sdk-go/services/bos/api/model.go b/bce-sdk-go/services/bos/api/model.go new file mode 100644 index 0000000..8c37ee4 --- /dev/null +++ b/bce-sdk-go/services/bos/api/model.go @@ -0,0 +1,622 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +import ( + "io" +) + +type OwnerType struct { + Id string `json:"id"` + DisplayName string `json:"displayName"` +} + +type BucketSummaryType struct { + Name string `json:"name"` + Location string `json:"location"` + CreationDate string `json:"creationDate"` +} + +// ListBucketsResult defines the result structure of ListBuckets api. +type ListBucketsResult struct { + Owner OwnerType `json:"owner"` + Buckets []BucketSummaryType `json:"buckets"` +} + +// ListObjectsArgs defines the optional arguments for ListObjects api. +type ListObjectsArgs struct { + Delimiter string `json:"delimiter"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + Prefix string `json:"prefix"` +} + +type ObjectSummaryType struct { + Key string `json:"key"` + LastModified string `json:"lastModified"` + ETag string `json:"eTag"` + Size int `json:"size"` + StorageClass string `json:"storageClass"` + Owner OwnerType `json:"owner"` +} + +type PrefixType struct { + Prefix string `json:"prefix"` +} + +// ListObjectsResult defines the result structure of ListObjects api. +type ListObjectsResult struct { + Name string `json:"name"` + Prefix string `json:"prefix"` + Delimiter string `json:"delimiter"` + Marker string `json:"marker"` + NextMarker string `json:"nextMarker,omitempty"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + Contents []ObjectSummaryType `json:"contents"` + CommonPrefixes []PrefixType `json:"commonPrefixes"` +} + +type LocationType struct { + LocationConstraint string `json:"locationConstraint"` +} + +// AclOwnerType defines the owner struct in ACL setting +type AclOwnerType struct { + Id string `json:"id"` +} + +// GranteeType defines the grantee struct in ACL setting +type GranteeType struct { + Id string `json:"id"` +} + +type AclRefererType struct { + StringLike []string `json:"stringLike"` + StringEquals []string `json:"stringEquals"` +} + +type AclCondType struct { + IpAddress []string `json:"ipAddress"` + Referer AclRefererType `json:"referer"` +} + +// GrantType defines the grant struct in ACL setting +type GrantType struct { + Grantee []GranteeType `json:"grantee"` + Permission []string `json:"permission"` + Resource []string `json:"resource,omitempty"` + NotResource []string `json:"notResource,omitempty"` + Condition AclCondType `json:"condition,omitempty"` + Effect string `json:"effect,omitempty"` +} + +// PutBucketAclArgs defines the input args structure for putting bucket acl. +type PutBucketAclArgs struct { + AccessControlList []GrantType `json:"accessControlList"` +} + +// GetBucketAclResult defines the result structure of getting bucket acl. +type GetBucketAclResult struct { + AccessControlList []GrantType `json:"accessControlList"` + Owner AclOwnerType `json:"owner"` +} + +// PutBucketLoggingArgs defines the input args structure for putting bucket logging. +type PutBucketLoggingArgs struct { + TargetBucket string `json:"targetBucket"` + TargetPrefix string `json:"targetPrefix"` +} + +// GetBucketLoggingResult defines the result structure for getting bucket logging. +type GetBucketLoggingResult struct { + Status string `json:"status"` + TargetBucket string `json:"targetBucket,omitempty"` + TargetPrefix string `json:"targetPrefix,omitempty"` +} + +// LifecycleConditionTimeType defines the structure of time condition +type LifecycleConditionTimeType struct { + DateGreaterThan string `json:"dateGreaterThan"` +} + +// LifecycleConditionType defines the structure of condition +type LifecycleConditionType struct { + Time LifecycleConditionTimeType `json:"time"` +} + +// LifecycleActionType defines the structure of lifecycle action +type LifecycleActionType struct { + Name string `json:"name"` + StorageClass string `json:"storageClass,omitempty"` +} + +// LifecycleRuleType defines the structure of a single lifecycle rule +type LifecycleRuleType struct { + Id string `json:"id"` + Status string `json:"status"` + Resource []string `json:"resource"` + Condition LifecycleConditionType `json:"condition"` + Action LifecycleActionType `json:"action"` +} + +// GetBucketLifecycleResult defines the lifecycle argument structure for putting +type PutBucketLifecycleArgs struct { + Rule []LifecycleRuleType `json:"rule"` +} + +// GetBucketLifecycleResult defines the lifecycle result structure for getting +type GetBucketLifecycleResult struct { + Rule []LifecycleRuleType `json:"rule"` +} + +type StorageClassType struct { + StorageClass string `json:"storageClass"` +} + +// BucketReplicationDescriptor defines the description data structure +type BucketReplicationDescriptor struct { + Bucket string `json:"bucket,omitempty"` + StorageClass string `json:"storageClass,omitempty"` +} + +// BucketReplicationType defines the data structure for Put and Get of bucket replication +type BucketReplicationType struct { + Id string `json:"id"` + Status string `json:"status"` + Resource []string `json:"resource"` + ReplicateDeletes string `json:"replicateDeletes"` + Destination *BucketReplicationDescriptor `json:"destination,omitempty"` + ReplicateHistory *BucketReplicationDescriptor `json:"replicateHistory,omitempty"` + CreateTime int64 `json:"createTime"` + DestRegion string `json:"destRegion"` +} + +type PutBucketReplicationArgs BucketReplicationType +type GetBucketReplicationResult BucketReplicationType + +// ListBucketReplicationResult defines output result for replication conf list +type ListBucketReplicationResult struct { + Rules []BucketReplicationType `json:"rules"` +} + +// GetBucketReplicationProgressResult defines output result for replication process +type GetBucketReplicationProgressResult struct { + Status string `json:"status"` + HistoryReplicationPercent float64 `json:"historyReplicationPercent"` + LatestReplicationTime string `json:"latestReplicationTime"` +} + +// BucketEncryptionType defines the data structure for Put and Get of bucket encryption +type BucketEncryptionType struct { + EncryptionAlgorithm string `json:"encryptionAlgorithm"` +} + +// BucketStaticWebsiteType defines the data structure for Put and Get of bucket static website +type BucketStaticWebsiteType struct { + Index string `json:"index"` + NotFound string `json:"notFound"` +} + +type PutBucketStaticWebsiteArgs BucketStaticWebsiteType +type GetBucketStaticWebsiteResult BucketStaticWebsiteType + +type BucketCORSType struct { + AllowedOrigins []string `json:"allowedOrigins"` + AllowedMethods []string `json:"allowedMethods"` + AllowedHeaders []string `json:"allowedHeaders,omitempty"` + AllowedExposeHeaders []string `json:"allowedExposeHeaders,omitempty"` + MaxAgeSeconds int64 `json:"maxAgeSeconds,omitempty"` +} + +// PutBucketCorsArgs defines the request argument for bucket CORS setting +type PutBucketCorsArgs struct { + CorsConfiguration []BucketCORSType `json:"corsConfiguration"` +} + +// GetBucketCorsResult defines the data structure of getting bucket CORS result +type GetBucketCorsResult struct { + CorsConfiguration []BucketCORSType `json:"corsConfiguration"` +} + +// CopyrightProtectionType defines the data structure for Put and Get copyright protection API +type CopyrightProtectionType struct { + Resource []string `json:"resource"` +} + +// ObjectAclType defines the data structure for Put and Get object acl API +type ObjectAclType struct { + AccessControlList []GrantType `json:"accessControlList"` +} + +type PutObjectAclArgs ObjectAclType +type GetObjectAclResult ObjectAclType + +// PutObjectArgs defines the optional args structure for the put object api. +type PutObjectArgs struct { + CacheControl string + ContentDisposition string + ContentMD5 string + ContentType string + ContentLength int64 + Expires string + UserMeta map[string]string + ContentSha256 string + ContentCrc32 string + StorageClass string + Process string + TrafficLimit int64 +} + +// CopyObjectArgs defines the optional args structure for the copy object api. +type CopyObjectArgs struct { + ObjectMeta + MetadataDirective string + IfMatch string + IfNoneMatch string + IfModifiedSince string + IfUnmodifiedSince string + TrafficLimit int64 + CannedAcl string +} + +type MultiCopyObjectArgs struct { + StorageClass string +} + +// CopyObjectResult defines the result json structure for the copy object api. +type CopyObjectResult struct { + LastModified string `json:"lastModified"` + ETag string `json:"eTag"` +} + +type ObjectMeta struct { + CacheControl string + ContentDisposition string + ContentEncoding string + ContentLength int64 + ContentRange string + ContentType string + ContentMD5 string + ContentSha256 string + ContentCrc32 string + Expires string + LastModified string + ETag string + UserMeta map[string]string + StorageClass string + NextAppendOffset string + ObjectType string + BceRestore string + BceObjectType string +} + +// GetObjectResult defines the result data of the get object api. +type GetObjectResult struct { + ObjectMeta + ContentLanguage string + Body io.ReadCloser +} + +// GetObjectMetaResult defines the result data of the get object meta api. +type GetObjectMetaResult struct { + ObjectMeta +} + +// SelectObjectResult defines the result data of the select object api. +type SelectObjectResult struct { + Body io.ReadCloser +} + +// selectObject request args +type SelectObjectArgs struct { + SelectType string `json:"-"` + SelectRequest *SelectObjectRequest `json:"selectRequest"` +} + +type SelectObjectRequest struct { + Expression string `json:"expression"` + ExpressionType string `json:"expressionType"` // SQL + InputSerialization *SelectObjectInput `json:"inputSerialization"` + OutputSerialization *SelectObjectOutput `json:"outputSerialization"` + RequestProgress *SelectObjectProgress `json:"requestProgress"` +} + +type SelectObjectInput struct { + CompressionType string `json:"compressionType"` + CsvParams map[string]string `json:"csv"` + JsonParams map[string]string `json:"json"` +} +type SelectObjectOutput struct { + OutputHeader bool `json:"outputHeader"` + CsvParams map[string]string `json:"csv"` + JsonParams map[string]string `json:"json"` +} +type SelectObjectProgress struct { + Enabled bool `json:"enabled"` +} + +type Prelude struct { + TotalLen uint32 + HeadersLen uint32 +} + +// selectObject response msg +type CommonMessage struct { + Prelude + Headers map[string]string // message-type/content-type…… + Crc32 uint32 // crc32 of RecordsMessage +} +type RecordsMessage struct { + CommonMessage + Records []string // csv/json seleted data, one or more records +} +type ContinuationMessage struct { + CommonMessage + BytesScanned uint64 + BytesReturned uint64 +} +type EndMessage struct { + CommonMessage +} + +// FetchObjectArgs defines the optional arguments structure for the fetch object api. +type FetchObjectArgs struct { + FetchMode string + StorageClass string +} + +// FetchObjectResult defines the result json structure for the fetch object api. +type FetchObjectResult struct { + Code string `json:"code"` + Message string `json:"message"` + RequestId string `json:"requestId"` + JobId string `json:"jobId"` +} + +// AppendObjectArgs defines the optional arguments structure for appending object. +type AppendObjectArgs struct { + Offset int64 + CacheControl string + ContentDisposition string + ContentMD5 string + ContentType string + Expires string + UserMeta map[string]string + ContentSha256 string + ContentCrc32 string + StorageClass string + TrafficLimit int64 +} + +// AppendObjectResult defines the result data structure for appending object. +type AppendObjectResult struct { + ContentMD5 string + NextAppendOffset int64 + ContentCrc32 string + ETag string +} + +// DeleteObjectArgs defines the input args structure for a single object. +type DeleteObjectArgs struct { + Key string `json:"key"` +} + +// DeleteMultipleObjectsResult defines the input args structure for deleting multiple objects. +type DeleteMultipleObjectsArgs struct { + Objects []DeleteObjectArgs `json:"objects"` +} + +// DeleteObjectResult defines the result structure for deleting a single object. +type DeleteObjectResult struct { + Key string `json:"key"` + Code string `json:"code"` + Message string `json:"message"` +} + +// DeleteMultipleObjectsResult defines the result structure for deleting multiple objects. +type DeleteMultipleObjectsResult struct { + Errors []DeleteObjectResult `json:"errors"` +} + +// InitiateMultipartUploadArgs defines the input arguments to initiate a multipart upload. +type InitiateMultipartUploadArgs struct { + CacheControl string + ContentDisposition string + Expires string + StorageClass string +} + +// InitiateMultipartUploadResult defines the result structure to initiate a multipart upload. +type InitiateMultipartUploadResult struct { + Bucket string `json:"bucket"` + Key string `json:"key"` + UploadId string `json:"uploadId"` +} + +// UploadPartArgs defines the optinoal argumets for uploading part. +type UploadPartArgs struct { + ContentMD5 string + ContentSha256 string + ContentCrc32 string + TrafficLimit int64 +} + +// UploadPartCopyArgs defines the optional arguments of UploadPartCopy. +type UploadPartCopyArgs struct { + SourceRange string + IfMatch string + IfNoneMatch string + IfModifiedSince string + IfUnmodifiedSince string + TrafficLimit int64 +} + +type PutSymlinkArgs struct { + ForbidOverwrite string + StorageClass string + UserMeta map[string]string + SymlinkBucket string +} + +// UploadInfoType defines an uploaded part info structure. +type UploadInfoType struct { + PartNumber int `json:"partNumber"` + ETag string `json:"eTag"` +} + +// CompleteMultipartUploadArgs defines the input arguments structure of CompleteMultipartUpload. +type CompleteMultipartUploadArgs struct { + Parts []UploadInfoType `json:"parts"` + UserMeta map[string]string `json:"-"` + Process string `json:"-"` + ContentCrc32 string `json:"-"` +} + +// CompleteMultipartUploadResult defines the result structure of CompleteMultipartUpload. +type CompleteMultipartUploadResult struct { + Location string `json:"location"` + Bucket string `json:"bucket"` + Key string `json:"key"` + ETag string `json:"eTag"` + ContentCrc32 string `json:"-"` +} + +// ListPartsArgs defines the input optional arguments of listing parts information. +type ListPartsArgs struct { + MaxParts int + PartNumberMarker string +} + +type ListPartType struct { + PartNumber int `json:"partNumber"` + LastModified string `json:"lastModified"` + ETag string `json:"eTag"` + Size int `json:"size"` +} + +// ListPartsResult defines the parts info result from ListParts. +type ListPartsResult struct { + Bucket string `json:"bucket"` + Key string `json:"key"` + UploadId string `json:"uploadId"` + Initiated string `json:"initiated"` + Owner OwnerType `json:"owner"` + StorageClass string `json:"storageClass"` + PartNumberMarker int `json:"partNumberMarker"` + NextPartNumberMarker int `json:"nextPartNumberMarker"` + MaxParts int `json:"maxParts"` + IsTruncated bool `json:"isTruncated"` + Parts []ListPartType `json:"parts"` +} + +// ListMultipartUploadsArgs defines the optional arguments for ListMultipartUploads. +type ListMultipartUploadsArgs struct { + Delimiter string + KeyMarker string + MaxUploads int + Prefix string +} + +type ListMultipartUploadsType struct { + Key string `json:"key"` + UploadId string `json:"uploadId"` + Owner OwnerType `json:"owner"` + Initiated string `json:"initiated"` + StorageClass string `json:"storageClass,omitempty"` +} + +// ListMultipartUploadsResult defines the multipart uploads result structure. +type ListMultipartUploadsResult struct { + Bucket string `json:"bucket"` + CommonPrefixes []PrefixType `json:"commonPrefixes"` + Delimiter string `json:"delimiter"` + Prefix string `json:"prefix"` + IsTruncated bool `json:"isTruncated"` + KeyMarker string `json:"keyMarker"` + MaxUploads int `json:"maxUploads"` + NextKeyMarker string `json:"nextKeyMarker"` + Uploads []ListMultipartUploadsType `json:"uploads"` +} + +type ArchiveRestoreArgs struct { + RestoreTier string + RestoreDays int +} + +type GetBucketTrashResult struct { + TrashDir string `json:"trashDir"` +} + +type PutBucketTrashReq struct { + TrashDir string `json:"trashDir"` +} + +type PutBucketNotificationReq struct { + Notifications []PutBucketNotificationSt `json:"notifications"` +} + +type PutBucketNotificationSt struct { + Id string `json:"id"` + Name string `json:"name"` + AppId string `json:"appId"` + Status string `json:"status"` + Resources []string `json:"resources"` + Events []string `json:"events"` + Apps []PutBucketNotificationAppsSt `json:"apps"` +} + +type PutBucketNotificationAppsSt struct { + Id string `json:"id"` + EventUrl string `json:"eventUrl"` + XVars string `json:"xVars"` +} + +type MirrorConfigurationRule struct { + Prefix string `json:"prefix,omitempty"` + SourceUrl string `json:"sourceUrl"` + PassQueryString bool `json:"passQuerystring"` + Mode string `json:"mode"` + StorageClass string `json:"storageClass"` + PassHeaders []string `json:"passHeaders"` + IgnoreHeaders []string `json:"ignoreHeaders"` + CustomHeaders []HeaderPair `json:"customHeaders"` + BackSourceUrl string `json:"backSourceUrl"` + Resource string `json:"resource"` + Suffix string `json:"suffix"` + FixedKey string `json:"fixedKey"` + PrefixReplace string `json:"prefixReplace"` + Version string `json:"version"` +} + +type HeaderPair struct { + HeaderName string `json:"headerName"` + HeaderValue string `json:"headerValue"` +} + +type PutBucketMirrorArgs struct { + BucketMirroringConfiguration []MirrorConfigurationRule `json:"bucketMirroringConfiguration"` +} + +type PutBucketTagArgs struct { + Tags []Tag `json:"tags"` +} + +type Tag struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} diff --git a/bce-sdk-go/services/bos/api/multipart.go b/bce-sdk-go/services/bos/api/multipart.go new file mode 100644 index 0000000..5e7dee5 --- /dev/null +++ b/bce-sdk-go/services/bos/api/multipart.go @@ -0,0 +1,452 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// multipart.go - the multipart-related APIs definition supported by the BOS service + +package api + +import ( + "bytes" + "fmt" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// InitiateMultipartUpload - initiate a multipart upload to get a upload ID +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// - contentType: the content type of the object to be uploaded which should be specified, +// otherwise use the default(application/octet-stream) +// - args: the optional arguments +// +// RETURNS: +// - *InitiateMultipartUploadResult: the result data structure +// - error: nil if ok otherwise the specific error +func InitiateMultipartUpload(cli bce.Client, bucket, object, contentType string, + args *InitiateMultipartUploadArgs) (*InitiateMultipartUploadResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.POST) + req.SetParam("uploads", "") + if len(contentType) == 0 { + contentType = RAW_CONTENT_TYPE + } + req.SetHeader(http.CONTENT_TYPE, contentType) + + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.CACHE_CONTROL: args.CacheControl, + http.CONTENT_DISPOSITION: args.ContentDisposition, + http.EXPIRES: args.Expires, + }) + + if validStorageClass(args.StorageClass) { + req.SetHeader(http.BCE_STORAGE_CLASS, args.StorageClass) + } else { + if len(args.StorageClass) != 0 { + return nil, bce.NewBceClientError("invalid storage class value: " + + args.StorageClass) + } + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &InitiateMultipartUploadResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return result, nil +} + +// UploadPart - upload the single part in the multipart upload process +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - content: the uploaded part content +// - args: the optional arguments +// +// RETURNS: +// - string: the etag of the uploaded part +// - error: nil if ok otherwise the specific error +func UploadPart(cli bce.Client, bucket, object, uploadId string, partNumber int, + content *bce.Body, args *UploadPartArgs) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + req.SetParam("uploadId", uploadId) + req.SetParam("partNumber", fmt.Sprintf("%d", partNumber)) + if content == nil { + return "", bce.NewBceClientError("upload part content should not be empty") + } + if content.Size() >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + req.SetBody(content) + + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.CONTENT_MD5: args.ContentMD5, + http.BCE_CONTENT_SHA256: args.ContentSha256, + http.BCE_CONTENT_CRC32: args.ContentCrc32, + }) + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return "", bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return strings.Trim(resp.Header(http.ETAG), "\""), nil +} + +// UploadPartFromBytes - upload the single part in the multipart upload process +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - content: the uploaded part content +// - args: the optional arguments +// +// RETURNS: +// - string: the etag of the uploaded part +// - error: nil if ok otherwise the specific error +func UploadPartFromBytes(cli bce.Client, bucket, object, uploadId string, partNumber int, + content []byte, args *UploadPartArgs) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + req.SetParam("uploadId", uploadId) + req.SetParam("partNumber", fmt.Sprintf("%d", partNumber)) + if content == nil { + return "", bce.NewBceClientError("upload part content should not be empty") + } + size := len(content) + if size >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + // set md5 and content-length + req.SetLength(int64(size)) + if size > 0 { + // calc md5 + if args == nil || args.ContentMD5 == "" { + buf := bytes.NewBuffer(content) + contentMD5, err := util.CalculateContentMD5(buf, int64(size)) + if err != nil { + return "", err + } + req.SetHeader(http.CONTENT_MD5, contentMD5) + } + req.SetHeader(http.CONTENT_LENGTH, fmt.Sprintf("%d", size)) + } + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.CONTENT_MD5: args.ContentMD5, + http.BCE_CONTENT_SHA256: args.ContentSha256, + http.BCE_CONTENT_CRC32: args.ContentCrc32, + }) + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return "", bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", + TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + } + // Send request and get the result + resp := &bce.BceResponse{} + if err := cli.SendRequestFromBytes(req, resp, content); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return strings.Trim(resp.Header(http.ETAG), "\""), nil +} + +// UploadPartCopy - copy the multipart data +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the destination bucket name +// - object: the destination object name +// - source: the copy source uri +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - args: the optional arguments +// +// RETURNS: +// - *CopyObjectResult: the lastModified and eTag of the part +// - error: nil if ok otherwise the specific error +func UploadPartCopy(cli bce.Client, bucket, object, source, uploadId string, partNumber int, + args *UploadPartCopyArgs) (*CopyObjectResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + req.SetParam("uploadId", uploadId) + req.SetParam("partNumber", fmt.Sprintf("%d", partNumber)) + if len(source) == 0 { + return nil, bce.NewBceClientError("upload part copy source should not be empty") + } + req.SetHeader(http.BCE_COPY_SOURCE, util.UriEncode(source, false)) + + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.BCE_COPY_SOURCE_RANGE: args.SourceRange, + http.BCE_COPY_SOURCE_IF_MATCH: args.IfMatch, + http.BCE_COPY_SOURCE_IF_NONE_MATCH: args.IfNoneMatch, + http.BCE_COPY_SOURCE_IF_MODIFIED_SINCE: args.IfModifiedSince, + http.BCE_COPY_SOURCE_IF_UNMODIFIED_SINCE: args.IfUnmodifiedSince, + }) + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return nil, bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CopyObjectResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// CompleteMultipartUpload - finish a multipart upload operation +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// - parts: all parts info stream +// - meta: user defined meta data +// +// RETURNS: +// - *CompleteMultipartUploadResult: the result data +// - error: nil if ok otherwise the specific error +func CompleteMultipartUpload(cli bce.Client, bucket, object, uploadId string, + body *bce.Body, args *CompleteMultipartUploadArgs) (*CompleteMultipartUploadResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.POST) + req.SetParam("uploadId", uploadId) + if body == nil { + return nil, bce.NewBceClientError("upload body info should not be emtpy") + } + if body.Size() >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + req.SetBody(body) + + // Optional arguments settings + if args.UserMeta != nil { + if err := setUserMetadata(req, args.UserMeta); err != nil { + return nil, err + } + } + if len(args.Process) != 0 { + req.SetHeader(http.BCE_PROCESS, args.Process) + } + if len(args.ContentCrc32) != 0 { + req.SetHeader(http.BCE_CONTENT_CRC32, args.ContentCrc32) + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CompleteMultipartUploadResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + headers := resp.Headers() + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_CRC32)]; ok { + result.ContentCrc32 = val + } + return result, nil +} + +// AbortMultipartUpload - abort a multipart upload operation +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func AbortMultipartUpload(cli bce.Client, bucket, object, uploadId string) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.DELETE) + req.SetParam("uploadId", uploadId) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// ListParts - list the successfully uploaded parts info by upload id +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// - args: the optional arguments +// partNumberMarker: return parts after this marker +// maxParts: the max number of return parts, default and maximum is 1000 +// +// RETURNS: +// - *ListPartsResult: the uploaded parts info result +// - error: nil if ok otherwise the specific error +func ListParts(cli bce.Client, bucket, object, uploadId string, + args *ListPartsArgs) (*ListPartsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.GET) + req.SetParam("uploadId", uploadId) + + // Optional arguments settings + if args != nil { + if len(args.PartNumberMarker) > 0 { + req.SetParam("partNumberMarker", args.PartNumberMarker) + } + if args.MaxParts > 0 { + req.SetParam("maxParts", fmt.Sprintf("%d", args.MaxParts)) + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListPartsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ListMultipartUploads - list the unfinished uploaded parts of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the destination bucket name +// - args: the optional arguments +// +// RETURNS: +// - *ListMultipartUploadsResult: the unfinished uploaded parts info result +// - error: nil if ok otherwise the specific error +func ListMultipartUploads(cli bce.Client, bucket string, + args *ListMultipartUploadsArgs) (*ListMultipartUploadsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.GET) + req.SetParam("uploads", "") + + // Optional arguments settings + if args != nil { + if len(args.Delimiter) > 0 { + req.SetParam("delimiter", args.Delimiter) + } + if len(args.KeyMarker) > 0 { + req.SetParam("keyMarker", args.KeyMarker) + } + if args.MaxUploads > 0 { + req.SetParam("maxUploads", fmt.Sprintf("%d", args.MaxUploads)) + } + if len(args.Prefix) > 0 { + req.SetParam("prefix", args.Prefix) + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListMultipartUploadsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bos/api/object.go b/bce-sdk-go/services/bos/api/object.go new file mode 100644 index 0000000..30b3b01 --- /dev/null +++ b/bce-sdk-go/services/bos/api/object.go @@ -0,0 +1,1016 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// object.go - the object APIs definition supported by the BOS service + +package api + +import ( + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// PutObject - put the object from the string or the stream +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// - body: the input content of the object +// - args: the optional arguments of this api +// +// RETURNS: +// - string: the etag of the object +// - error: nil if ok otherwise the specific error +func PutObject(cli bce.Client, bucket, object string, body *bce.Body, + args *PutObjectArgs) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + if body == nil { + return "", bce.NewBceClientError("PutObject body should not be emtpy") + } + if body.Size() >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + req.SetBody(body) + + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.CACHE_CONTROL: args.CacheControl, + http.CONTENT_DISPOSITION: args.ContentDisposition, + http.CONTENT_TYPE: args.ContentType, + http.EXPIRES: args.Expires, + http.BCE_CONTENT_SHA256: args.ContentSha256, + http.BCE_CONTENT_CRC32: args.ContentCrc32, + }) + if args.ContentLength > 0 { + // User specified Content-Length can be smaller than the body size, so the body should + // be reset. The `net/http.Client' does not support the Content-Length bigger than the + // body size. + if args.ContentLength > body.Size() { + return "", bce.NewBceClientError(fmt.Sprintf("ContentLength %d is bigger than body size %d", args.ContentLength, body.Size())) + } + body, err := bce.NewBodyFromSizedReader(body.Stream(), args.ContentLength) + if err != nil { + return "", bce.NewBceClientError(err.Error()) + } + req.SetHeader(http.CONTENT_LENGTH, fmt.Sprintf("%d", args.ContentLength)) + req.SetBody(body) // re-assign body + } + + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return "", bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + + // Reset the contentMD5 if set by user + if len(args.ContentMD5) != 0 { + req.SetHeader(http.CONTENT_MD5, args.ContentMD5) + } + + if validStorageClass(args.StorageClass) { + req.SetHeader(http.BCE_STORAGE_CLASS, args.StorageClass) + } else { + if len(args.StorageClass) != 0 { + return "", bce.NewBceClientError("invalid storage class value: " + + args.StorageClass) + } + } + + if err := setUserMetadata(req, args.UserMeta); err != nil { + return "", err + } + + if len(args.Process) != 0 { + req.SetHeader(http.BCE_PROCESS, args.Process) + } + } + // add content-type if not assigned by user + if req.Header(http.CONTENT_TYPE) == "" { + req.SetHeader(http.CONTENT_TYPE, getDefaultContentType(object)) + } + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return strings.Trim(resp.Header(http.ETAG), "\""), nil +} + +// CopyObject - copy one object to a new object with new bucket and/or name. It can alse set the +// metadata of the object with the same source and target. +// +// PARAMS: +// - cli: the client object which can perform sending request +// - bucket: the bucket name of the target object +// - object: the name of the target object +// - source: the source object uri +// - *CopyObjectArgs: the optional input args for copying object +// +// RETURNS: +// - *CopyObjectResult: the result object which contains etag and lastmodified +// - error: nil if ok otherwise the specific error +func CopyObject(cli bce.Client, bucket, object, source string, + args *CopyObjectArgs) (*CopyObjectResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + if len(source) == 0 { + return nil, bce.NewBceClientError("copy source should not be null") + } + req.SetHeader(http.BCE_COPY_SOURCE, util.UriEncode(source, false)) + + // Optional arguments settings + if args != nil { + setOptionalNullHeaders(req, map[string]string{ + http.CACHE_CONTROL: args.CacheControl, + http.CONTENT_DISPOSITION: args.ContentDisposition, + http.CONTENT_ENCODING: args.ContentEncoding, + http.CONTENT_RANGE: args.ContentRange, + http.CONTENT_TYPE: args.ContentType, + http.EXPIRES: args.Expires, + http.LAST_MODIFIED: args.LastModified, + http.ETAG: args.ETag, + http.CONTENT_MD5: args.ContentMD5, + http.BCE_CONTENT_SHA256: args.ContentSha256, + http.BCE_OBJECT_TYPE: args.ObjectType, + http.BCE_NEXT_APPEND_OFFSET: args.NextAppendOffset, + http.BCE_COPY_SOURCE_IF_MATCH: args.IfMatch, + http.BCE_COPY_SOURCE_IF_NONE_MATCH: args.IfNoneMatch, + http.BCE_COPY_SOURCE_IF_MODIFIED_SINCE: args.IfModifiedSince, + http.BCE_COPY_SOURCE_IF_UNMODIFIED_SINCE: args.IfUnmodifiedSince, + }) + if args.ContentLength != 0 { + req.SetHeader(http.CONTENT_LENGTH, fmt.Sprintf("%d", args.ContentLength)) + } + if validMetadataDirective(args.MetadataDirective) { + req.SetHeader(http.BCE_COPY_METADATA_DIRECTIVE, args.MetadataDirective) + } else { + if len(args.MetadataDirective) != 0 { + return nil, bce.NewBceClientError( + "invalid metadata directive value: " + args.MetadataDirective) + } + } + if validStorageClass(args.StorageClass) { + req.SetHeader(http.BCE_STORAGE_CLASS, args.StorageClass) + } else { + if len(args.StorageClass) != 0 { + return nil, bce.NewBceClientError("invalid storage class value: " + + args.StorageClass) + } + } + + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return nil, bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + + if validCannedAcl(args.CannedAcl) { + req.SetHeader(http.BCE_ACL, args.CannedAcl) + } + + if err := setUserMetadata(req, args.UserMeta); err != nil { + return nil, err + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CopyObjectResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetObject - get the object content with range and response-headers-specified support +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// - args: the optional args in querysring +// - ranges: the optional range start and end to get the given object +// +// RETURNS: +// - *GetObjectResult: the output content result of the object +// - error: nil if ok otherwise the specific error +func GetObject(cli bce.Client, bucket, object string, args map[string]string, // nolint:gocyclo + ranges ...int64) (*GetObjectResult, error) { + + if object == "" { + err := fmt.Errorf("Get Object don't accept \"\" as a parameter") + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.GET) + + // Optional arguments settings + if args != nil { + for k, v := range args { + if _, ok := GET_OBJECT_ALLOWED_RESPONSE_HEADERS[k]; ok { + req.SetParam("response"+k, v) + } + if strings.HasPrefix(k, http.BCE_PREFIX) { + req.SetParam(k, v) + } + } + } + if len(ranges) != 0 { + rangeStr := "bytes=" + if len(ranges) == 1 { + rangeStr += fmt.Sprintf("%d", ranges[0]) + "-" + } else { + rangeStr += fmt.Sprintf("%d", ranges[0]) + "-" + fmt.Sprintf("%d", ranges[1]) + } + req.SetHeader("Range", rangeStr) + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + headers := resp.Headers() + result := &GetObjectResult{} + if val, ok := headers[http.CACHE_CONTROL]; ok { + result.CacheControl = val + } + if val, ok := headers[http.CONTENT_DISPOSITION]; ok { + result.ContentDisposition = val + } + if val, ok := headers[http.CONTENT_LENGTH]; ok { + if length, err := strconv.ParseInt(val, 10, 64); err == nil { + result.ContentLength = length + } + } + if val, ok := headers[http.CONTENT_RANGE]; ok { + result.ContentRange = val + } + if val, ok := headers[http.CONTENT_TYPE]; ok { + result.ContentType = val + } + if val, ok := headers[http.CONTENT_MD5]; ok { + result.ContentMD5 = val + } + if val, ok := headers[http.EXPIRES]; ok { + result.Expires = val + } + if val, ok := headers[http.LAST_MODIFIED]; ok { + result.LastModified = val + } + if val, ok := headers[http.ETAG]; ok { + result.ETag = strings.Trim(val, "\"") + } + if val, ok := headers[http.CONTENT_LANGUAGE]; ok { + result.ContentLanguage = val + } + if val, ok := headers[http.CONTENT_ENCODING]; ok { + result.ContentEncoding = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_SHA256)]; ok { + result.ContentSha256 = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_CRC32)]; ok { + result.ContentCrc32 = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_STORAGE_CLASS)]; ok { + result.StorageClass = val + } + bcePrefix := toHttpHeaderKey(http.BCE_USER_METADATA_PREFIX) + for k, v := range headers { + if strings.Index(k, bcePrefix) == 0 { + if result.UserMeta == nil { + result.UserMeta = make(map[string]string) + } + result.UserMeta[k[len(bcePrefix):]] = v + } + } + if val, ok := headers[toHttpHeaderKey(http.BCE_OBJECT_TYPE)]; ok { + result.ObjectType = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_NEXT_APPEND_OFFSET)]; ok { + result.NextAppendOffset = val + } + result.Body = resp.Body() + return result, nil +} + +// GetObjectMeta - get the meta data of the given object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// +// RETURNS: +// - *GetObjectMetaResult: the result of this api +// - error: nil if ok otherwise the specific error +func GetObjectMeta(cli bce.Client, bucket, object string) (*GetObjectMetaResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.HEAD) + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + headers := resp.Headers() + result := &GetObjectMetaResult{} + if val, ok := headers[http.CACHE_CONTROL]; ok { + result.CacheControl = val + } + if val, ok := headers[http.CONTENT_DISPOSITION]; ok { + result.ContentDisposition = val + } + if val, ok := headers[http.CONTENT_LENGTH]; ok { + if length, err := strconv.ParseInt(val, 10, 64); err == nil { + result.ContentLength = length + } + } + if val, ok := headers[http.CONTENT_RANGE]; ok { + result.ContentRange = val + } + if val, ok := headers[http.CONTENT_TYPE]; ok { + result.ContentType = val + } + if val, ok := headers[http.CONTENT_MD5]; ok { + result.ContentMD5 = val + } + if val, ok := headers[http.EXPIRES]; ok { + result.Expires = val + } + if val, ok := headers[http.LAST_MODIFIED]; ok { + result.LastModified = val + } + if val, ok := headers[http.ETAG]; ok { + result.ETag = strings.Trim(val, "\"") + } + if val, ok := headers[http.CONTENT_ENCODING]; ok { + result.ContentEncoding = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_SHA256)]; ok { + result.ContentSha256 = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_CRC32)]; ok { + result.ContentCrc32 = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_STORAGE_CLASS)]; ok { + result.StorageClass = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_RESTORE)]; ok { + result.BceRestore = val + } + if val, ok := headers[http.BCE_OBJECT_TYPE]; ok { + result.BceObjectType = val + } + bcePrefix := toHttpHeaderKey(http.BCE_USER_METADATA_PREFIX) + for k, v := range headers { + if strings.Index(k, bcePrefix) == 0 { + if result.UserMeta == nil { + result.UserMeta = make(map[string]string) + } + result.UserMeta[k[len(bcePrefix):]] = v + } + } + if val, ok := headers[toHttpHeaderKey(http.BCE_OBJECT_TYPE)]; ok { + result.ObjectType = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_NEXT_APPEND_OFFSET)]; ok { + result.NextAppendOffset = val + } + defer func() { resp.Body().Close() }() + return result, nil +} + +// SelectObject - select the object content +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// - args: the optional arguments to perform the select operation +// +// RETURNS: +// - *SelectObjectResult: the output select content result of the object +// - error: nil if ok otherwise the specific error +func SelectObject(cli bce.Client, bucket, object string, args *SelectObjectArgs) (*SelectObjectResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.POST) + req.SetParam("select", "") + req.SetParam("type", args.SelectType) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + result := &SelectObjectResult{} + + result.Body = resp.Body() + return result, nil +} + +// FetchObject - fetch the object by the given url and store it to a bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name to store the object +// - object: the name of the object to be stored +// - source: the source url to fetch +// - args: the optional arguments to perform the fetch operation +// +// RETURNS: +// - *FetchObjectArgs: the result of this api +// - error: nil if ok otherwise the specific error +func FetchObject(cli bce.Client, bucket, object, source string, + args *FetchObjectArgs) (*FetchObjectResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.POST) + req.SetParam("fetch", "") + if len(source) == 0 { + return nil, bce.NewBceClientError("invalid fetch source value: " + source) + } + req.SetHeader(http.BCE_PREFIX+"fetch-source", source) + + // Optional arguments settings + if args != nil { + if validFetchMode(args.FetchMode) { + req.SetHeader(http.BCE_PREFIX+"fetch-mode", args.FetchMode) + } else { + if len(args.FetchMode) != 0 { + return nil, bce.NewBceClientError("invalid fetch mode value: " + args.FetchMode) + } + } + if validStorageClass(args.StorageClass) { + req.SetHeader(http.BCE_STORAGE_CLASS, args.StorageClass) + } else { + if len(args.StorageClass) != 0 { + return nil, bce.NewBceClientError("invalid storage class value: " + + args.StorageClass) + } + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &FetchObjectResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// AppendObject - append the given content to a new or existed object which is appendable +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// - content: the content to be appended +// - args: the optional arguments to perform the append operation +// +// RETURNS: +// - *AppendObjectResult: the result status for this api +// - error: nil if ok otherwise the specific error +func AppendObject(cli bce.Client, bucket, object string, content *bce.Body, + args *AppendObjectArgs) (*AppendObjectResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.POST) + req.SetParam("append", "") + if content == nil { + return nil, bce.NewBceClientError("AppendObject body should not be emtpy") + } + if content.Size() >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + req.SetBody(content) + + // Optional arguments settings + if args != nil { + if args.Offset < 0 { + return nil, bce.NewBceClientError( + fmt.Sprintf("invalid append offset value: %d", args.Offset)) + } + if args.Offset > 0 { + req.SetParam("offset", fmt.Sprintf("%d", args.Offset)) + } + setOptionalNullHeaders(req, map[string]string{ + http.CACHE_CONTROL: args.CacheControl, + http.CONTENT_DISPOSITION: args.ContentDisposition, + http.CONTENT_MD5: args.ContentMD5, + http.CONTENT_TYPE: args.ContentType, + http.EXPIRES: args.Expires, + http.BCE_CONTENT_SHA256: args.ContentSha256, + http.BCE_CONTENT_CRC32: args.ContentCrc32, + }) + + if validStorageClass(args.StorageClass) { + req.SetHeader(http.BCE_STORAGE_CLASS, args.StorageClass) + } else { + if len(args.StorageClass) != 0 { + return nil, bce.NewBceClientError("invalid storage class value: " + + args.StorageClass) + } + } + if err := setUserMetadata(req, args.UserMeta); err != nil { + return nil, err + } + //set traffic-limit + if args.TrafficLimit > 0 { + if args.TrafficLimit > TRAFFIC_LIMIT_MAX || args.TrafficLimit < TRAFFIC_LIMIT_MIN { + return nil, bce.NewBceClientError(fmt.Sprintf("TrafficLimit must between %d ~ %d, current value:%d", TRAFFIC_LIMIT_MIN, TRAFFIC_LIMIT_MAX, args.TrafficLimit)) + } + req.SetHeader(http.BCE_TRAFFIC_LIMIT, fmt.Sprintf("%d", args.TrafficLimit)) + } + } + + // Send request and get the result + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + headers := resp.Headers() + result := &AppendObjectResult{} + if val, ok := headers[http.CONTENT_MD5]; ok { + result.ContentMD5 = val + } + if val, ok := headers[toHttpHeaderKey(http.BCE_NEXT_APPEND_OFFSET)]; ok { + nextOffset, offsetErr := strconv.ParseInt(val, 10, 64) + if offsetErr != nil { + nextOffset = content.Size() + } + result.NextAppendOffset = nextOffset + } else { + result.NextAppendOffset = content.Size() + } + if val, ok := headers[toHttpHeaderKey(http.BCE_CONTENT_CRC32)]; ok { + result.ContentCrc32 = val + } + if val, ok := headers[http.ETAG]; ok { + result.ETag = strings.Trim(val, "\"") + } + defer func() { resp.Body().Close() }() + return result, nil +} + +// DeleteObject - delete the given object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object to be deleted +// - object: the name of the object +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func DeleteObject(cli bce.Client, bucket, object string) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// DeleteMultipleObjects - delete the given objects within a single http request +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the objects to be deleted +// - objectListStream: the objects list to be delete with json format +// +// RETURNS: +// - *DeleteMultipleObjectsResult: the objects failed to delete +// - error: nil if ok otherwise the specific error +func DeleteMultipleObjects(cli bce.Client, bucket string, + objectListStream *bce.Body) (*DeleteMultipleObjectsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getBucketUri(bucket)) + req.SetMethod(http.POST) + req.SetParam("delete", "") + req.SetHeader(http.CONTENT_TYPE, "application/json; charset=utf-8") + if objectListStream == nil { + return nil, bce.NewBceClientError("DeleteMultipleObjects body should not be emtpy") + } + if objectListStream.Size() >= THRESHOLD_100_CONTINUE { + req.SetHeader("Expect", "100-continue") + } + req.SetBody(objectListStream) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &DeleteMultipleObjectsResult{} + + if resp.Header(http.CONTENT_LENGTH) == "0" { + resp.Body().Close() + return jsonBody, nil + } + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GeneratePresignedUrl - generate an authorization url with expire time and optional arguments +// +// PARAMS: +// - conf: the client configuration +// - signer: the client signer object to generate the authorization string +// - bucket: the target bucket name +// - object: the target object name +// - expire: expire time in seconds +// - method: optional sign method, default is GET +// - headers: optional sign headers, default just set the Host +// - params: optional sign params, default is empty +// RETURNS: +// - string: the presigned url with authorization string + +func GeneratePresignedUrl(conf *bce.BceClientConfiguration, signer auth.Signer, bucket, + object string, expire int, method string, headers, params map[string]string) string { + return GeneratePresignedUrlInternal(conf, signer, bucket, object, expire, method, headers, params, false) +} +func GeneratePresignedUrlPathStyle(conf *bce.BceClientConfiguration, signer auth.Signer, bucket, + object string, expire int, method string, headers, params map[string]string) string { + return GeneratePresignedUrlInternal(conf, signer, bucket, object, expire, method, headers, params, true) +} + +func GeneratePresignedUrlInternal(conf *bce.BceClientConfiguration, signer auth.Signer, bucket, + object string, expire int, method string, headers, params map[string]string, path_style bool) string { + req := &bce.BceRequest{} + // Set basic arguments + if len(method) == 0 { + method = http.GET + } + req.SetMethod(method) + req.SetEndpoint(conf.Endpoint) + if req.Protocol() == "" { + req.SetProtocol(bce.DEFAULT_PROTOCOL) + } + domain := req.Host() + if pos := strings.Index(domain, ":"); pos != -1 { + domain = domain[:pos] + } + if path_style { + req.SetUri(getObjectUri(bucket, object)) + if conf.CnameEnabled || isCnameLikeHost(conf.Endpoint) { + req.SetUri(getCnameUri(req.Uri())) + } + } else { + if len(bucket) != 0 && net.ParseIP(domain) == nil { // not use an IP as the endpoint by client + req.SetUri(bce.URI_PREFIX + object) + if !conf.CnameEnabled && !isCnameLikeHost(conf.Endpoint) { + req.SetHost(bucket + "." + req.Host()) + } + } else { + req.SetUri(getObjectUri(bucket, object)) + if conf.CnameEnabled || isCnameLikeHost(conf.Endpoint) { + req.SetUri(getCnameUri(req.Uri())) + } + } + } + // Set headers and params if given. + req.SetHeader(http.HOST, req.Host()) + if headers != nil { + for k, v := range headers { + req.SetHeader(k, v) + } + } + if params != nil { + for k, v := range params { + req.SetParam(k, v) + } + } + // Copy one SignOptions object to rewrite it. + option := *conf.SignOption + if expire != 0 { + option.ExpireSeconds = expire + } + + if conf.Credentials.SessionToken != "" { + req.SetParam(http.BCE_SECURITY_TOKEN, conf.Credentials.SessionToken) + } + + // Generate the authorization string and return the signed url. + signer.Sign(&req.Request, conf.Credentials, &option) + req.SetParam("authorization", req.Header(http.AUTHORIZATION)) + return fmt.Sprintf("%s://%s%s?%s", req.Protocol(), req.Host(), + util.UriEncode(req.Uri(), false), req.QueryString()) +} + +// PutObjectAcl - set the ACL of the given object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// - cannedAcl: support private and public-read +// - grantRead: user id list +// - grantFullControl: user id list +// - aclBody: the acl file body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func PutObjectAcl(cli bce.Client, bucket, object, cannedAcl string, + grantRead, grantFullControl []string, aclBody *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.PUT) + req.SetParam("acl", "") + + // Joiner for generate the user id list string for grant acl header + joiner := func(ids []string) string { + for i := range ids { + ids[i] = "id=\"" + ids[i] + "\"" + } + return strings.Join(ids, ",") + } + + // Choose a acl setting method + methods := 0 + if len(cannedAcl) != 0 { + methods += 1 + if validCannedAcl(cannedAcl) { + req.SetHeader(http.BCE_ACL, cannedAcl) + } + } + if len(grantRead) != 0 { + methods += 1 + req.SetHeader(http.BCE_GRANT_READ, joiner(grantRead)) + } + if len(grantFullControl) != 0 { + methods += 1 + req.SetHeader(http.BCE_GRANT_FULL_CONTROL, joiner(grantFullControl)) + } + if aclBody != nil { + methods += 1 + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(aclBody) + } + if methods != 1 { + return bce.NewBceClientError("BOS only support one acl setting method at the same time") + } + + // Do sending request + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetObjectAcl - get the ACL of the given object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// +// RETURNS: +// - result: the object acl result object +// - error: nil if success otherwise the specific error +func GetObjectAcl(cli bce.Client, bucket, object string) (*GetObjectAclResult, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.GET) + req.SetParam("acl", "") + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetObjectAclResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteObjectAcl - delete the ACL of the given object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteObjectAcl(cli bce.Client, bucket, object string) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetMethod(http.DELETE) + req.SetParam("acl", "") + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// RestoreObject - restore the archive object +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - object: the object name +// - args: the restore args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func RestoreObject(cli bce.Client, bucket string, object string, args ArchiveRestoreArgs) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, object)) + req.SetParam("restore", "") + req.SetMethod(http.POST) + req.SetHeader(http.BCE_RESTORE_DAYS, strconv.Itoa(args.RestoreDays)) + req.SetHeader(http.BCE_RESTORE_TIER, args.RestoreTier) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +// PutObjectSymlink - put the object from the string or the stream +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - object: the name of the object +// - symlinkKey: the name of the symlink +// - symlinkArgs: the optional arguments of this api +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func PutObjectSymlink(cli bce.Client, bucket string, object string, symlinkKey string, symlinkArgs *PutSymlinkArgs) error { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, symlinkKey)) + req.SetParam("symlink", "") + req.SetMethod(http.PUT) + if symlinkArgs != nil { + if len(symlinkArgs.ForbidOverwrite) != 0 { + if !validForbidOverwrite(symlinkArgs.ForbidOverwrite) { + return bce.NewBceClientError("invalid forbid overwrite val," + symlinkArgs.ForbidOverwrite) + } + req.SetHeader(http.BCE_FORBID_OVERWRITE, symlinkArgs.ForbidOverwrite) + } + + if len(symlinkArgs.StorageClass) != 0 { + if !validStorageClass(symlinkArgs.StorageClass) { + return bce.NewBceClientError("invalid storage class val," + symlinkArgs.StorageClass) + } + if symlinkArgs.StorageClass == STORAGE_CLASS_ARCHIVE { + return bce.NewBceClientError("archive storage class not support") + } + req.SetHeader(http.BCE_STORAGE_CLASS, symlinkArgs.StorageClass) + } + + if len(symlinkArgs.UserMeta) != 0 { + if err := setUserMetadata(req, symlinkArgs.UserMeta); err != nil { + return err + } + } + if len(symlinkArgs.SymlinkBucket) != 0 { + req.SetHeader(http.BCE_SYMLINK_BUCKET, symlinkArgs.SymlinkBucket) + } + } + req.SetHeader(http.BCE_SYMLINK_TARGET, object) + + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// PutObjectSymlink - put the object from the string or the stream +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name of the object +// - symlinkKey: the name of the symlink +// +// RETURNS: +// - string: the name of the target object +// - error: nil if ok otherwise the specific error +func GetObjectSymlink(cli bce.Client, bucket string, symlinkKey string) (string, error) { + req := &bce.BceRequest{} + req.SetUri(getObjectUri(bucket, symlinkKey)) + req.SetParam("symlink", "") + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := SendRequest(cli, req, resp); err != nil { + return "", err + } + if resp.IsFail() { + return "", resp.ServiceError() + } + defer func() { resp.Body().Close() }() + if resp.Header(http.BCE_SYMLINK_BUCKET) != "" { + result := BOS_CONFIG_PREFIX + resp.Header(http.BCE_SYMLINK_BUCKET) + "/" + resp.Header(http.BCE_SYMLINK_TARGET) + return result, nil + } + return resp.Header(http.BCE_SYMLINK_TARGET), nil +} diff --git a/bce-sdk-go/services/bos/api/util.go b/bce-sdk-go/services/bos/api/util.go new file mode 100644 index 0000000..dd5d533 --- /dev/null +++ b/bce-sdk-go/services/bos/api/util.go @@ -0,0 +1,295 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BOS service + +package api + +import ( + "bytes" + net_http "net/http" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +const ( + METADATA_DIRECTIVE_COPY = "copy" + METADATA_DIRECTIVE_REPLACE = "replace" + + STORAGE_CLASS_STANDARD = "STANDARD" + STORAGE_CLASS_STANDARD_IA = "STANDARD_IA" + STORAGE_CLASS_COLD = "COLD" + STORAGE_CLASS_ARCHIVE = "ARCHIVE" + + FETCH_MODE_SYNC = "sync" + FETCH_MODE_ASYNC = "async" + + CANNED_ACL_PRIVATE = "private" + CANNED_ACL_PUBLIC_READ = "public-read" + CANNED_ACL_PUBLIC_READ_WRITE = "public-read-write" + + RAW_CONTENT_TYPE = "application/octet-stream" + + THRESHOLD_100_CONTINUE = 1 << 20 // add 100 continue header if body size bigger than 1MB + + TRAFFIC_LIMIT_MAX = 8 * (100 << 20) // 100M bit = 838860800 + TRAFFIC_LIMIT_MIN = 8 * (100 << 10) // 100K bit = 819200 + + STATUS_ENABLED = "enabled" + STATUS_DISABLED = "disabled" + + ENCRYPTION_AES256 = "AES256" + + RESTORE_TIER_STANDARD = "Standard" //标准取回对象 + RESTORE_TIER_EXPEDITED = "Expedited" //快速取回对象 + + FORBID_OVERWRITE_FALSE = "false" + FORBID_OVERWRITE_TRUE = "true" + + NAMESPACE_BUCKET = "namespace" + BOS_CONFIG_PREFIX = "bos://" +) + +var DEFAULT_CNAME_LIKE_LIST = []string{ + ".cdn.bcebos.com", +} + +var VALID_STORAGE_CLASS_TYPE = map[string]int{ + STORAGE_CLASS_STANDARD: 0, + STORAGE_CLASS_STANDARD_IA: 1, + STORAGE_CLASS_COLD: 2, + STORAGE_CLASS_ARCHIVE: 3, +} + +var VALID_RESTORE_TIER = map[string]int{ + RESTORE_TIER_STANDARD: 1, + RESTORE_TIER_EXPEDITED: 1, +} + +var VALID_FORBID_OVERWRITE = map[string]int{ + FORBID_OVERWRITE_FALSE: 1, + FORBID_OVERWRITE_TRUE: 1, +} + +var ( + GET_OBJECT_ALLOWED_RESPONSE_HEADERS = map[string]struct{}{ + "ContentDisposition": {}, + "ContentType": {}, + "ContentLanguage": {}, + "Expires": {}, + "CacheControl": {}, + "ContentEncoding": {}, + } +) + +func getBucketUri(bucketName string) string { + return bce.URI_PREFIX + bucketName +} + +func getObjectUri(bucketName, objectName string) string { + return bce.URI_PREFIX + bucketName + "/" + objectName +} + +func getCnameUri(uri string) string { + if len(uri) <= 0 { + return uri + } + slash_index := strings.Index(uri[1:], "/") + if slash_index == -1 { + return bce.URI_PREFIX + } else { + return uri[slash_index+1:] + } +} + +func validMetadataDirective(val string) bool { + if val == METADATA_DIRECTIVE_COPY || val == METADATA_DIRECTIVE_REPLACE { + return true + } + return false +} + +func validForbidOverwrite(val string) bool { + if _, ok := VALID_FORBID_OVERWRITE[val]; ok { + return true + } + return false +} + +func validStorageClass(val string) bool { + if _, ok := VALID_STORAGE_CLASS_TYPE[val]; ok { + return true + } + return false +} + +func validFetchMode(val string) bool { + if val == FETCH_MODE_SYNC || val == FETCH_MODE_ASYNC { + return true + } + return false +} + +func validCannedAcl(val string) bool { + if val == CANNED_ACL_PRIVATE || + val == CANNED_ACL_PUBLIC_READ || + val == CANNED_ACL_PUBLIC_READ_WRITE { + return true + } + return false +} + +func toHttpHeaderKey(key string) string { + var result bytes.Buffer + needToUpper := true + for _, c := range []byte(key) { + if needToUpper && (c >= 'a' && c <= 'z') { + result.WriteByte(c - 32) + needToUpper = false + } else if c == '-' { + result.WriteByte(c) + needToUpper = true + } else { + result.WriteByte(c) + } + } + return result.String() +} + +func setOptionalNullHeaders(req *bce.BceRequest, args map[string]string) { + for k, v := range args { + if len(v) == 0 { + continue + } + switch k { + case http.CACHE_CONTROL: + fallthrough + case http.CONTENT_DISPOSITION: + fallthrough + case http.CONTENT_ENCODING: + fallthrough + case http.CONTENT_RANGE: + fallthrough + case http.CONTENT_MD5: + fallthrough + case http.CONTENT_TYPE: + fallthrough + case http.EXPIRES: + fallthrough + case http.LAST_MODIFIED: + fallthrough + case http.ETAG: + fallthrough + case http.BCE_OBJECT_TYPE: + fallthrough + case http.BCE_NEXT_APPEND_OFFSET: + fallthrough + case http.BCE_CONTENT_SHA256: + fallthrough + case http.BCE_CONTENT_CRC32: + fallthrough + case http.BCE_COPY_SOURCE_RANGE: + fallthrough + case http.BCE_COPY_SOURCE_IF_MATCH: + fallthrough + case http.BCE_COPY_SOURCE_IF_NONE_MATCH: + fallthrough + case http.BCE_COPY_SOURCE_IF_MODIFIED_SINCE: + fallthrough + case http.BCE_COPY_SOURCE_IF_UNMODIFIED_SINCE: + req.SetHeader(k, v) + } + } +} + +func setUserMetadata(req *bce.BceRequest, meta map[string]string) error { + if meta == nil { + return nil + } + for k, v := range meta { + if len(k) == 0 { + continue + } + if len(k)+len(v) > 32*1024 { + return bce.NewBceClientError("MetadataTooLarge") + } + req.SetHeader(http.BCE_USER_METADATA_PREFIX+k, v) + } + return nil +} + +func isCnameLikeHost(host string) bool { + for _, suffix := range DEFAULT_CNAME_LIKE_LIST { + if strings.HasSuffix(strings.ToLower(host), suffix) { + return true + } + } + return false +} + +func SendRequest(cli bce.Client, req *bce.BceRequest, resp *bce.BceResponse) error { + var ( + err error + need_retry bool + ) + + req.SetEndpoint(cli.GetBceClientConfig().Endpoint) + origin_uri := req.Uri() + // set uri for cname or cdn endpoint + if cli.GetBceClientConfig().CnameEnabled || isCnameLikeHost(cli.GetBceClientConfig().Endpoint) { + req.SetUri(getCnameUri(origin_uri)) + } + + if err = cli.SendRequest(req, resp); err != nil { + if serviceErr, isServiceErr := err.(*bce.BceServiceError); isServiceErr { + if serviceErr.StatusCode == net_http.StatusInternalServerError || + serviceErr.StatusCode == net_http.StatusBadGateway || + serviceErr.StatusCode == net_http.StatusServiceUnavailable || + (serviceErr.StatusCode == net_http.StatusBadRequest && serviceErr.Code == "Http400") { + need_retry = true + } + } + if _, isClientErr := err.(*bce.BceClientError); isClientErr { + need_retry = true + } + // retry backup endpoint + if need_retry && cli.GetBceClientConfig().BackupEndpoint != "" { + req.SetEndpoint(cli.GetBceClientConfig().BackupEndpoint) + if cli.GetBceClientConfig().CnameEnabled || isCnameLikeHost(cli.GetBceClientConfig().BackupEndpoint) { + req.SetUri(getCnameUri(origin_uri)) + } + if err = cli.SendRequest(req, resp); err != nil { + return err + } + } + } + return err +} + +func getDefaultContentType(object string) string { + dot := strings.LastIndex(object, ".") + if dot == -1 { + return "application/octet-stream" + } + ext := object[dot:] + mimeMap := util.GetMimeMap() + if contentType, ok := mimeMap[ext]; ok { + return contentType + } + return "application/octet-stream" + +} diff --git a/bce-sdk-go/services/bos/client.go b/bce-sdk-go/services/bos/client.go new file mode 100644 index 0000000..c2cc352 --- /dev/null +++ b/bce-sdk-go/services/bos/client.go @@ -0,0 +1,2380 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for BOS service + +// Package bos defines the BOS services of BCE. The supported APIs are all defined in sub-package +// model with three types: 16 bucket APIs, 9 object APIs and 7 multipart APIs. +package bos + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "os" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + sdk_http "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/bos/api" + "github.com/baidubce/bce-sdk-go/services/sts" + "github.com/baidubce/bce-sdk-go/util/log" +) + +const ( + DEFAULT_SERVICE_DOMAIN = bce.DEFAULT_REGION + ".bcebos.com" + DEFAULT_MAX_PARALLEL = 10 + MULTIPART_ALIGN = 1 << 20 // 1MB + MIN_MULTIPART_SIZE = 100 * (1 << 10) // 100 KB + DEFAULT_MULTIPART_SIZE = 12 * (1 << 20) // 12MB + + MAX_PART_NUMBER = 10000 + MAX_SINGLE_PART_SIZE = 5 * (1 << 30) // 5GB + MAX_SINGLE_OBJECT_SIZE = 48.8 * (1 << 40) // 48.8TB +) + +// Client of BOS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient + + // Fileds that used in parallel operation for BOS service + MaxParallel int64 + MultipartSize int64 +} + +// BosClientConfiguration defines the config components structure by user. +type BosClientConfiguration struct { + Ak string + Sk string + Endpoint string + RedirectDisabled bool +} + +// NewClient make the BOS service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + return NewClientWithConfig(&BosClientConfiguration{ + Ak: ak, + Sk: sk, + Endpoint: endpoint, + RedirectDisabled: false, + }) +} + +// NewStsClient make the BOS service client with STS configuration, it will first apply stsAK,stsSK, sessionToken, then return bosClient using temporary sts Credential +func NewStsClient(ak, sk, endpoint string, expiration int) (*Client, error) { + stsClient, err := sts.NewClient(ak, sk) + if err != nil { + fmt.Println("create sts client object :", err) + return nil, err + } + sts, err := stsClient.GetSessionToken(expiration, "") + if err != nil { + fmt.Println("get session token failed:", err) + return nil, err + } + + bosClient, err := NewClient(sts.AccessKeyId, sts.SecretAccessKey, endpoint) + if err != nil { + fmt.Println("create bos client failed:", err) + return nil, err + } + stsCredential, err := auth.NewSessionBceCredentials( + sts.AccessKeyId, + sts.SecretAccessKey, + sts.SessionToken) + if err != nil { + fmt.Println("create sts credential object failed:", err) + return nil, err + } + bosClient.Config.Credentials = stsCredential + return bosClient, nil +} + +func NewClientWithConfig(config *BosClientConfiguration) (*Client, error) { + var credentials *auth.BceCredentials + var err error + ak, sk, endpoint := config.Ak, config.Sk, config.Endpoint + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS, + RedirectDisabled: config.RedirectDisabled} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer), + DEFAULT_MAX_PARALLEL, DEFAULT_MULTIPART_SIZE} + return client, nil +} + +// ListBuckets - list all buckets +// +// RETURNS: +// - *api.ListBucketsResult: the all buckets +// - error: the return error if any occurs +func (c *Client) ListBuckets() (*api.ListBucketsResult, error) { + return api.ListBuckets(c) +} + +// ListObjects - list all objects of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - args: the optional arguments to list objects +// +// RETURNS: +// - *api.ListObjectsResult: the all objects of the bucket +// - error: the return error if any occurs +func (c *Client) ListObjects(bucket string, + args *api.ListObjectsArgs) (*api.ListObjectsResult, error) { + return api.ListObjects(c, bucket, args) +} + +// SimpleListObjects - list all objects of the given bucket with simple arguments +// +// PARAMS: +// - bucket: the bucket name +// - prefix: the prefix for listing +// - maxKeys: the max number of result objects +// - marker: the marker to mark the beginning for the listing +// - delimiter: the delimiter for list objects +// +// RETURNS: +// - *api.ListObjectsResult: the all objects of the bucket +// - error: the return error if any occurs +func (c *Client) SimpleListObjects(bucket, prefix string, maxKeys int, marker, + delimiter string) (*api.ListObjectsResult, error) { + args := &api.ListObjectsArgs{delimiter, marker, maxKeys, prefix} + return api.ListObjects(c, bucket, args) +} + +// HeadBucket - test the given bucket existed and access authority +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if exists and have authority otherwise the specific error +func (c *Client) HeadBucket(bucket string) error { + err, _ := api.HeadBucket(c, bucket) + return err +} + +// DoesBucketExist - test the given bucket existed or not +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - bool: true if exists and false if not exists or occurs error +// - error: nil if exists or not exist, otherwise the specific error +func (c *Client) DoesBucketExist(bucket string) (bool, error) { + err, _ := api.HeadBucket(c, bucket) + if err == nil { + return true, nil + } + if realErr, ok := err.(*bce.BceServiceError); ok { + if realErr.StatusCode == http.StatusForbidden { + return true, nil + } + if realErr.StatusCode == http.StatusNotFound { + return false, nil + } + } + return false, err +} + +// IsNsBucket - test the given bucket is namespace bucket or not +func (c *Client) IsNsBucket(bucket string) bool { + err, resp := api.HeadBucket(c, bucket) + if err == nil && resp.Header(sdk_http.BCE_BUCKET_TYPE) == api.NAMESPACE_BUCKET { + return true + } + if realErr, ok := err.(*bce.BceServiceError); ok { + if realErr.StatusCode == http.StatusForbidden && + resp.Header(sdk_http.BCE_BUCKET_TYPE) == api.NAMESPACE_BUCKET { + return true + } + } + return false +} + +// PutBucket - create a new bucket +// +// PARAMS: +// - bucket: the new bucket name +// +// RETURNS: +// - string: the location of the new bucket if create success +// - error: nil if create success otherwise the specific error +func (c *Client) PutBucket(bucket string) (string, error) { + return api.PutBucket(c, bucket) +} + +// DeleteBucket - delete a empty bucket +// +// PARAMS: +// - bucket: the bucket name to be deleted +// +// RETURNS: +// - error: nil if delete success otherwise the specific error +func (c *Client) DeleteBucket(bucket string) error { + return api.DeleteBucket(c, bucket) +} + +// GetBucketLocation - get the location fo the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - string: the location of the bucket +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketLocation(bucket string) (string, error) { + return api.GetBucketLocation(c, bucket) +} + +// PutBucketAcl - set the acl of the given bucket with acl body stream +// +// PARAMS: +// - bucket: the bucket name +// - aclBody: the acl json body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketAcl(bucket string, aclBody *bce.Body) error { + return api.PutBucketAcl(c, bucket, "", aclBody) +} + +// PutBucketAclFromCanned - set the canned acl of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - cannedAcl: the cannedAcl string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketAclFromCanned(bucket, cannedAcl string) error { + return api.PutBucketAcl(c, bucket, cannedAcl, nil) +} + +// PutBucketAclFromFile - set the acl of the given bucket with acl json file name +// +// PARAMS: +// - bucket: the bucket name +// - aclFile: the acl file name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketAclFromFile(bucket, aclFile string) error { + body, err := bce.NewBodyFromFile(aclFile) + if err != nil { + return err + } + return api.PutBucketAcl(c, bucket, "", body) +} + +// PutBucketAclFromString - set the acl of the given bucket with acl json string +// +// PARAMS: +// - bucket: the bucket name +// - aclString: the acl string with json format +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketAclFromString(bucket, aclString string) error { + body, err := bce.NewBodyFromString(aclString) + if err != nil { + return err + } + return api.PutBucketAcl(c, bucket, "", body) +} + +// PutBucketAclFromStruct - set the acl of the given bucket with acl data structure +// +// PARAMS: +// - bucket: the bucket name +// - aclObj: the acl struct object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketAclFromStruct(bucket string, aclObj *api.PutBucketAclArgs) error { + jsonBytes, jsonErr := json.Marshal(aclObj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutBucketAcl(c, bucket, "", body) +} + +// GetBucketAcl - get the acl of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.GetBucketAclResult: the result of the bucket acl +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketAcl(bucket string) (*api.GetBucketAclResult, error) { + return api.GetBucketAcl(c, bucket) +} + +// PutBucketLogging - set the loging setting of the given bucket with json stream +// +// PARAMS: +// - bucket: the bucket name +// - body: the json body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketLogging(bucket string, body *bce.Body) error { + return api.PutBucketLogging(c, bucket, body) +} + +// PutBucketLoggingFromString - set the loging setting of the given bucket with json string +// +// PARAMS: +// - bucket: the bucket name +// - logging: the json format string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketLoggingFromString(bucket, logging string) error { + body, err := bce.NewBodyFromString(logging) + if err != nil { + return err + } + return api.PutBucketLogging(c, bucket, body) +} + +// PutBucketLoggingFromStruct - set the loging setting of the given bucket with args object +// +// PARAMS: +// - bucket: the bucket name +// - obj: the logging setting object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketLoggingFromStruct(bucket string, obj *api.PutBucketLoggingArgs) error { + jsonBytes, jsonErr := json.Marshal(obj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutBucketLogging(c, bucket, body) +} + +// GetBucketLogging - get the logging setting of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.GetBucketLoggingResult: the logging setting of the bucket +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketLogging(bucket string) (*api.GetBucketLoggingResult, error) { + return api.GetBucketLogging(c, bucket) +} + +// DeleteBucketLogging - delete the logging setting of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketLogging(bucket string) error { + return api.DeleteBucketLogging(c, bucket) +} + +// PutBucketLifecycle - set the lifecycle rule of the given bucket with raw stream +// +// PARAMS: +// - bucket: the bucket name +// - lifecycle: the lifecycle rule json body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketLifecycle(bucket string, lifecycle *bce.Body) error { + return api.PutBucketLifecycle(c, bucket, lifecycle) +} + +// PutBucketLifecycleFromString - set the lifecycle rule of the given bucket with string +// +// PARAMS: +// - bucket: the bucket name +// - lifecycle: the lifecycle rule json format string body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketLifecycleFromString(bucket, lifecycle string) error { + body, err := bce.NewBodyFromString(lifecycle) + if err != nil { + return err + } + return api.PutBucketLifecycle(c, bucket, body) +} + +// GetBucketLifecycle - get the lifecycle rule of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.GetBucketLifecycleResult: the lifecycle rule of the bucket +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketLifecycle(bucket string) (*api.GetBucketLifecycleResult, error) { + return api.GetBucketLifecycle(c, bucket) +} + +// DeleteBucketLifecycle - delete the lifecycle rule of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketLifecycle(bucket string) error { + return api.DeleteBucketLifecycle(c, bucket) +} + +// PutBucketStorageclass - set the storage class of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - storageClass: the storage class string value +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketStorageclass(bucket, storageClass string) error { + return api.PutBucketStorageclass(c, bucket, storageClass) +} + +// GetBucketStorageclass - get the storage class of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - string: the storage class string value +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketStorageclass(bucket string) (string, error) { + return api.GetBucketStorageclass(c, bucket) +} + +// PutBucketReplication - set the bucket replication config of different region +// +// PARAMS: +// - bucket: the bucket name +// - replicationConf: the replication config json body stream +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketReplication(bucket string, replicationConf *bce.Body, replicationRuleId string) error { + return api.PutBucketReplication(c, bucket, replicationConf, replicationRuleId) +} + +// PutBucketReplicationFromFile - set the bucket replication config with json file name +// +// PARAMS: +// - bucket: the bucket name +// - confFile: the config json file name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketReplicationFromFile(bucket, confFile string, replicationRuleId string) error { + body, err := bce.NewBodyFromFile(confFile) + if err != nil { + return err + } + return api.PutBucketReplication(c, bucket, body, replicationRuleId) +} + +// PutBucketReplicationFromString - set the bucket replication config with json string +// +// PARAMS: +// - bucket: the bucket name +// - confString: the config string with json format +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketReplicationFromString(bucket, confString string, replicationRuleId string) error { + body, err := bce.NewBodyFromString(confString) + if err != nil { + return err + } + return api.PutBucketReplication(c, bucket, body, replicationRuleId) +} + +// PutBucketReplicationFromStruct - set the bucket replication config with struct +// +// PARAMS: +// - bucket: the bucket name +// - confObj: the replication config struct object +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketReplicationFromStruct(bucket string, + confObj *api.PutBucketReplicationArgs, replicationRuleId string) error { + jsonBytes, jsonErr := json.Marshal(confObj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutBucketReplication(c, bucket, body, replicationRuleId) +} + +// GetBucketReplication - get the bucket replication config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - *api.GetBucketReplicationResult: the result of the bucket replication config +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketReplication(bucket string, replicationRuleId string) (*api.GetBucketReplicationResult, error) { + return api.GetBucketReplication(c, bucket, replicationRuleId) +} + +// ListBucketReplication - get all replication config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.ListBucketReplicationResult: the list of the bucket replication config +// - error: nil if success otherwise the specific error +func (c *Client) ListBucketReplication(bucket string) (*api.ListBucketReplicationResult, error) { + return api.ListBucketReplication(c, bucket) +} + +// DeleteBucketReplication - delete the bucket replication config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketReplication(bucket string, replicationRuleId string) error { + return api.DeleteBucketReplication(c, bucket, replicationRuleId) +} + +// GetBucketReplicationProgress - get the bucket replication process of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - replicationRuleId: the replication rule id composed of [0-9 A-Z a-z _ -] +// +// RETURNS: +// - *api.GetBucketReplicationProgressResult: the process of the bucket replication +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketReplicationProgress(bucket string, replicationRuleId string) ( + *api.GetBucketReplicationProgressResult, error) { + return api.GetBucketReplicationProgress(c, bucket, replicationRuleId) +} + +// PutBucketEncryption - set the bucket encryption config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// - algorithm: the encryption algorithm name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketEncryption(bucket, algorithm string) error { + return api.PutBucketEncryption(c, bucket, algorithm) +} + +// GetBucketEncryption - get the bucket encryption config +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - string: the encryption algorithm name +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketEncryption(bucket string) (string, error) { + return api.GetBucketEncryption(c, bucket) +} + +// DeleteBucketEncryption - delete the bucket encryption config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketEncryption(bucket string) error { + return api.DeleteBucketEncryption(c, bucket) +} + +// PutBucketStaticWebsite - set the bucket static website config +// +// PARAMS: +// - bucket: the bucket name +// - config: the static website config body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketStaticWebsite(bucket string, config *bce.Body) error { + return api.PutBucketStaticWebsite(c, bucket, config) +} + +// PutBucketStaticWebsiteFromString - set the bucket static website config from json string +// +// PARAMS: +// - bucket: the bucket name +// - jsonConfig: the static website config json string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketStaticWebsiteFromString(bucket, jsonConfig string) error { + body, err := bce.NewBodyFromString(jsonConfig) + if err != nil { + return err + } + return api.PutBucketStaticWebsite(c, bucket, body) +} + +// PutBucketStaticWebsiteFromStruct - set the bucket static website config from struct +// +// PARAMS: +// - bucket: the bucket name +// - confObj: the static website config object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketStaticWebsiteFromStruct(bucket string, + confObj *api.PutBucketStaticWebsiteArgs) error { + jsonBytes, jsonErr := json.Marshal(confObj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutBucketStaticWebsite(c, bucket, body) +} + +// SimplePutBucketStaticWebsite - simple set the bucket static website config +// +// PARAMS: +// - bucket: the bucket name +// - index: the static website config for index file name +// - notFound: the static website config for notFound file name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SimplePutBucketStaticWebsite(bucket, index, notFound string) error { + confObj := &api.PutBucketStaticWebsiteArgs{index, notFound} + return c.PutBucketStaticWebsiteFromStruct(bucket, confObj) +} + +// GetBucketStaticWebsite - get the bucket static website config +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - result: the static website config result object +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketStaticWebsite(bucket string) ( + *api.GetBucketStaticWebsiteResult, error) { + return api.GetBucketStaticWebsite(c, bucket) +} + +// DeleteBucketStaticWebsite - delete the bucket static website config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketStaticWebsite(bucket string) error { + return api.DeleteBucketStaticWebsite(c, bucket) +} + +// PutBucketCors - set the bucket CORS config +// +// PARAMS: +// - bucket: the bucket name +// - config: the bucket CORS config body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketCors(bucket string, config *bce.Body) error { + return api.PutBucketCors(c, bucket, config) +} + +// PutBucketCorsFromFile - set the bucket CORS config from json config file +// +// PARAMS: +// - bucket: the bucket name +// - filename: the bucket CORS json config file name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketCorsFromFile(bucket, filename string) error { + body, err := bce.NewBodyFromFile(filename) + if err != nil { + return err + } + return api.PutBucketCors(c, bucket, body) +} + +// PutBucketCorsFromString - set the bucket CORS config from json config string +// +// PARAMS: +// - bucket: the bucket name +// - filename: the bucket CORS json config string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketCorsFromString(bucket, jsonConfig string) error { + body, err := bce.NewBodyFromString(jsonConfig) + if err != nil { + return err + } + return api.PutBucketCors(c, bucket, body) +} + +// PutBucketCorsFromStruct - set the bucket CORS config from json config object +// +// PARAMS: +// - bucket: the bucket name +// - filename: the bucket CORS json config object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketCorsFromStruct(bucket string, confObj *api.PutBucketCorsArgs) error { + jsonBytes, jsonErr := json.Marshal(confObj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutBucketCors(c, bucket, body) +} + +// GetBucketCors - get the bucket CORS config +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - result: the bucket CORS config result object +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketCors(bucket string) (*api.GetBucketCorsResult, error) { + return api.GetBucketCors(c, bucket) +} + +// DeleteBucketCors - delete the bucket CORS config of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketCors(bucket string) error { + return api.DeleteBucketCors(c, bucket) +} + +// PutBucketCopyrightProtection - set the copyright protection config of the given bucket +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - bucket: the bucket name +// - resources: the resource items in the bucket to be protected +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketCopyrightProtection(bucket string, resources ...string) error { + return api.PutBucketCopyrightProtection(c, bucket, resources...) +} + +// GetBucketCopyrightProtection - get the bucket copyright protection config +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - result: the bucket copyright protection config resources +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketCopyrightProtection(bucket string) ([]string, error) { + return api.GetBucketCopyrightProtection(c, bucket) +} + +// DeleteBucketCopyrightProtection - delete the bucket copyright protection config +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketCopyrightProtection(bucket string) error { + return api.DeleteBucketCopyrightProtection(c, bucket) +} + +// PutObject - upload a new object or rewrite the existed object with raw stream +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - body: the object content body +// - args: the optional arguments +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) PutObject(bucket, object string, body *bce.Body, + args *api.PutObjectArgs) (string, error) { + return api.PutObject(c, bucket, object, body, args) +} + +// BasicPutObject - the basic interface of uploading an object +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - body: the object content body +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) BasicPutObject(bucket, object string, body *bce.Body) (string, error) { + return api.PutObject(c, bucket, object, body, nil) +} + +// PutObjectFromBytes - upload a new object or rewrite the existed object from a byte array +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - bytesArr: the content byte array +// - args: the optional arguments +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) PutObjectFromBytes(bucket, object string, bytesArr []byte, + args *api.PutObjectArgs) (string, error) { + body, err := bce.NewBodyFromBytes(bytesArr) + if err != nil { + return "", err + } + return api.PutObject(c, bucket, object, body, args) +} + +// PutObjectFromString - upload a new object or rewrite the existed object from a string +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - content: the content string +// - args: the optional arguments +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) PutObjectFromString(bucket, object, content string, + args *api.PutObjectArgs) (string, error) { + body, err := bce.NewBodyFromString(content) + if err != nil { + return "", err + } + return api.PutObject(c, bucket, object, body, args) +} + +// PutObjectFromFile - upload a new object or rewrite the existed object from a local file +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - fileName: the local file full path name +// - args: the optional arguments +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) PutObjectFromFile(bucket, object, fileName string, + args *api.PutObjectArgs) (string, error) { + body, err := bce.NewBodyFromFile(fileName) + if err != nil { + return "", err + } + return api.PutObject(c, bucket, object, body, args) +} + +// PutObjectFromStream - upload a new object or rewrite the existed object from stream +// +// PARAMS: +// - bucket: the name of the bucket to store the object +// - object: the name of the object +// - fileName: the local file full path name +// - args: the optional arguments +// +// RETURNS: +// - string: etag of the uploaded object +// - error: the uploaded error if any occurs +func (c *Client) PutObjectFromStream(bucket, object string, reader io.Reader, + args *api.PutObjectArgs) (string, error) { + body, err := bce.NewBodyFromSizedReader(reader, -1) + if err != nil { + return "", err + } + return api.PutObject(c, bucket, object, body, args) +} + +// CopyObject - copy a remote object to another one +// +// PARAMS: +// - bucket: the name of the destination bucket +// - object: the name of the destination object +// - srcBucket: the name of the source bucket +// - srcObject: the name of the source object +// - args: the optional arguments for copying object which are MetadataDirective, StorageClass, +// IfMatch, IfNoneMatch, ifModifiedSince, IfUnmodifiedSince +// +// RETURNS: +// - *api.CopyObjectResult: result struct which contains "ETag" and "LastModified" fields +// - error: any error if it occurs +func (c *Client) CopyObject(bucket, object, srcBucket, srcObject string, + args *api.CopyObjectArgs) (*api.CopyObjectResult, error) { + source := fmt.Sprintf("/%s/%s", srcBucket, srcObject) + return api.CopyObject(c, bucket, object, source, args) +} + +// BasicCopyObject - the basic interface of copying a object to another one +// +// PARAMS: +// - bucket: the name of the destination bucket +// - object: the name of the destination object +// - srcBucket: the name of the source bucket +// - srcObject: the name of the source object +// +// RETURNS: +// - *api.CopyObjectResult: result struct which contains "ETag" and "LastModified" fields +// - error: any error if it occurs +func (c *Client) BasicCopyObject(bucket, object, srcBucket, + srcObject string) (*api.CopyObjectResult, error) { + source := fmt.Sprintf("/%s/%s", srcBucket, srcObject) + return api.CopyObject(c, bucket, object, source, nil) +} + +// GetObject - get the given object with raw stream return +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - args: the optional args in querysring +// - ranges: the optional range start and end to get the given object +// +// RETURNS: +// - *api.GetObjectResult: result struct which contains "Body" and header fields +// for details reference https://cloud.baidu.com/doc/BOS/API.html#GetObject.E6.8E.A5.E5.8F.A3 +// - error: any error if it occurs +func (c *Client) GetObject(bucket, object string, args map[string]string, + ranges ...int64) (*api.GetObjectResult, error) { + return api.GetObject(c, bucket, object, args, ranges...) +} + +// BasicGetObject - the basic interface of geting the given object +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// +// RETURNS: +// - *api.GetObjectResult: result struct which contains "Body" and header fields +// for details reference https://cloud.baidu.com/doc/BOS/API.html#GetObject.E6.8E.A5.E5.8F.A3 +// - error: any error if it occurs +func (c *Client) BasicGetObject(bucket, object string) (*api.GetObjectResult, error) { + return api.GetObject(c, bucket, object, nil) +} + +// BasicGetObjectToFile - use basic interface to get the given object to the given file path +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - filePath: the file path to store the object content +// +// RETURNS: +// - error: any error if it occurs +func (c *Client) BasicGetObjectToFile(bucket, object, filePath string) error { + res, err := api.GetObject(c, bucket, object, nil) + if err != nil { + return err + } + defer res.Body.Close() + + file, fileErr := os.OpenFile(filePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) + if fileErr != nil { + return fileErr + } + defer file.Close() + + written, writeErr := io.CopyN(file, res.Body, res.ContentLength) + if writeErr != nil { + return writeErr + } + if written != res.ContentLength { + return fmt.Errorf("written content size does not match the response content") + } + return nil +} + +// GetObjectMeta - get the given object metadata +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// +// RETURNS: +// - *api.GetObjectMetaResult: metadata result, for details reference +// https://cloud.baidu.com/doc/BOS/API.html#GetObjectMeta.E6.8E.A5.E5.8F.A3 +// - error: any error if it occurs +func (c *Client) GetObjectMeta(bucket, object string) (*api.GetObjectMetaResult, error) { + return api.GetObjectMeta(c, bucket, object) +} + +// SelectObject - select the object content +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - args: the optional arguments to select the object +// +// RETURNS: +// - *api.SelectObjectResult: select object result +// - error: any error if it occurs +func (c *Client) SelectObject(bucket, object string, args *api.SelectObjectArgs) (*api.SelectObjectResult, error) { + return api.SelectObject(c, bucket, object, args) +} + +// FetchObject - fetch the object content from the given source and store +// +// PARAMS: +// - bucket: the name of the bucket to store +// - object: the name of the object to store +// - source: fetch source url +// - args: the optional arguments to fetch the object +// +// RETURNS: +// - *api.FetchObjectResult: result struct with Code, Message, RequestId and JobId fields +// - error: any error if it occurs +func (c *Client) FetchObject(bucket, object, source string, + args *api.FetchObjectArgs) (*api.FetchObjectResult, error) { + return api.FetchObject(c, bucket, object, source, args) +} + +// BasicFetchObject - the basic interface of the fetch object api +// +// PARAMS: +// - bucket: the name of the bucket to store +// - object: the name of the object to store +// - source: fetch source url +// +// RETURNS: +// - *api.FetchObjectResult: result struct with Code, Message, RequestId and JobId fields +// - error: any error if it occurs +func (c *Client) BasicFetchObject(bucket, object, source string) (*api.FetchObjectResult, error) { + return api.FetchObject(c, bucket, object, source, nil) +} + +// SimpleFetchObject - fetch object with simple arguments interface +// +// PARAMS: +// - bucket: the name of the bucket to store +// - object: the name of the object to store +// - source: fetch source url +// - mode: fetch mode which supports sync and async +// - storageClass: the storage class of the fetched object +// +// RETURNS: +// - *api.FetchObjectResult: result struct with Code, Message, RequestId and JobId fields +// - error: any error if it occurs +func (c *Client) SimpleFetchObject(bucket, object, source, mode, + storageClass string) (*api.FetchObjectResult, error) { + args := &api.FetchObjectArgs{mode, storageClass} + return api.FetchObject(c, bucket, object, source, args) +} + +// AppendObject - append the given content to a new or existed object which is appendable +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - content: the append object stream +// - args: the optional arguments to append object +// +// RETURNS: +// - *api.AppendObjectResult: the result of the appended object +// - error: any error if it occurs +func (c *Client) AppendObject(bucket, object string, content *bce.Body, + args *api.AppendObjectArgs) (*api.AppendObjectResult, error) { + return api.AppendObject(c, bucket, object, content, args) +} + +// SimpleAppendObject - the interface to append object with simple offset argument +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - content: the append object stream +// - offset: the offset of where to append +// +// RETURNS: +// - *api.AppendObjectResult: the result of the appended object +// - error: any error if it occurs +func (c *Client) SimpleAppendObject(bucket, object string, content *bce.Body, + offset int64) (*api.AppendObjectResult, error) { + return api.AppendObject(c, bucket, object, content, &api.AppendObjectArgs{Offset: offset}) +} + +// SimpleAppendObjectFromString - the simple interface of appending an object from a string +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - content: the object string to append +// - offset: the offset of where to append +// +// RETURNS: +// - *api.AppendObjectResult: the result of the appended object +// - error: any error if it occurs +func (c *Client) SimpleAppendObjectFromString(bucket, object, content string, + offset int64) (*api.AppendObjectResult, error) { + body, err := bce.NewBodyFromString(content) + if err != nil { + return nil, err + } + return api.AppendObject(c, bucket, object, body, &api.AppendObjectArgs{Offset: offset}) +} + +// SimpleAppendObjectFromFile - the simple interface of appending an object from a file +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - filePath: the full file path +// - offset: the offset of where to append +// +// RETURNS: +// - *api.AppendObjectResult: the result of the appended object +// - error: any error if it occurs +func (c *Client) SimpleAppendObjectFromFile(bucket, object, filePath string, + offset int64) (*api.AppendObjectResult, error) { + body, err := bce.NewBodyFromFile(filePath) + if err != nil { + return nil, err + } + return api.AppendObject(c, bucket, object, body, &api.AppendObjectArgs{Offset: offset}) +} + +// DeleteObject - delete the given object +// +// PARAMS: +// - bucket: the name of the bucket to delete +// - object: the name of the object to delete +// +// RETURNS: +// - error: any error if it occurs +func (c *Client) DeleteObject(bucket, object string) error { + return api.DeleteObject(c, bucket, object) +} + +// DeleteMultipleObjects - delete a list of objects +// +// PARAMS: +// - bucket: the name of the bucket to delete +// - objectListStream: the object list stream to be deleted +// +// RETURNS: +// - *api.DeleteMultipleObjectsResult: the delete information +// - error: any error if it occurs +func (c *Client) DeleteMultipleObjects(bucket string, + objectListStream *bce.Body) (*api.DeleteMultipleObjectsResult, error) { + return api.DeleteMultipleObjects(c, bucket, objectListStream) +} + +// DeleteMultipleObjectsFromString - delete a list of objects with json format string +// +// PARAMS: +// - bucket: the name of the bucket to delete +// - objectListString: the object list string to be deleted +// +// RETURNS: +// - *api.DeleteMultipleObjectsResult: the delete information +// - error: any error if it occurs +func (c *Client) DeleteMultipleObjectsFromString(bucket, + objectListString string) (*api.DeleteMultipleObjectsResult, error) { + body, err := bce.NewBodyFromString(objectListString) + if err != nil { + return nil, err + } + return api.DeleteMultipleObjects(c, bucket, body) +} + +// DeleteMultipleObjectsFromStruct - delete a list of objects with object list struct +// +// PARAMS: +// - bucket: the name of the bucket to delete +// - objectListStruct: the object list struct to be deleted +// +// RETURNS: +// - *api.DeleteMultipleObjectsResult: the delete information +// - error: any error if it occurs +func (c *Client) DeleteMultipleObjectsFromStruct(bucket string, + objectListStruct *api.DeleteMultipleObjectsArgs) (*api.DeleteMultipleObjectsResult, error) { + jsonBytes, jsonErr := json.Marshal(objectListStruct) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.DeleteMultipleObjects(c, bucket, body) +} + +// DeleteMultipleObjectsFromKeyList - delete a list of objects with given key string array +// +// PARAMS: +// - bucket: the name of the bucket to delete +// - keyList: the key string list to be deleted +// +// RETURNS: +// - *api.DeleteMultipleObjectsResult: the delete information +// - error: any error if it occurs +func (c *Client) DeleteMultipleObjectsFromKeyList(bucket string, + keyList []string) (*api.DeleteMultipleObjectsResult, error) { + if len(keyList) == 0 { + return nil, fmt.Errorf("the key list to be deleted is empty") + } + args := make([]api.DeleteObjectArgs, len(keyList)) + for i, k := range keyList { + args[i].Key = k + } + argsContainer := &api.DeleteMultipleObjectsArgs{args} + + jsonBytes, jsonErr := json.Marshal(argsContainer) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.DeleteMultipleObjects(c, bucket, body) +} + +// InitiateMultipartUpload - initiate a multipart upload to get a upload ID +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - contentType: the content type of the object to be uploaded which should be specified, +// otherwise use the default(application/octet-stream) +// - args: the optional arguments +// +// RETURNS: +// - *InitiateMultipartUploadResult: the result data structure +// - error: nil if ok otherwise the specific error +func (c *Client) InitiateMultipartUpload(bucket, object, contentType string, + args *api.InitiateMultipartUploadArgs) (*api.InitiateMultipartUploadResult, error) { + return api.InitiateMultipartUpload(c, bucket, object, contentType, args) +} + +// BasicInitiateMultipartUpload - basic interface to initiate a multipart upload +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// +// RETURNS: +// - *InitiateMultipartUploadResult: the result data structure +// - error: nil if ok otherwise the specific error +func (c *Client) BasicInitiateMultipartUpload(bucket, + object string) (*api.InitiateMultipartUploadResult, error) { + return api.InitiateMultipartUpload(c, bucket, object, "", nil) +} + +// UploadPart - upload the single part in the multipart upload process +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - content: the uploaded part content +// - args: the optional arguments +// +// RETURNS: +// - string: the etag of the uploaded part +// - error: nil if ok otherwise the specific error +func (c *Client) UploadPart(bucket, object, uploadId string, partNumber int, + content *bce.Body, args *api.UploadPartArgs) (string, error) { + return api.UploadPart(c, bucket, object, uploadId, partNumber, content, args) +} + +// BasicUploadPart - basic interface to upload the single part in the multipart upload process +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - content: the uploaded part content +// +// RETURNS: +// - string: the etag of the uploaded part +// - error: nil if ok otherwise the specific error +func (c *Client) BasicUploadPart(bucket, object, uploadId string, partNumber int, + content *bce.Body) (string, error) { + return api.UploadPart(c, bucket, object, uploadId, partNumber, content, nil) +} + +// UploadPartFromBytes - upload the single part in the multipart upload process +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - content: the uploaded part content +// - args: the optional arguments +// +// RETURNS: +// - string: the etag of the uploaded part +// - error: nil if ok otherwise the specific error +func (c *Client) UploadPartFromBytes(bucket, object, uploadId string, partNumber int, + content []byte, args *api.UploadPartArgs) (string, error) { + return api.UploadPartFromBytes(c, bucket, object, uploadId, partNumber, content, args) +} + +// UploadPartCopy - copy the multipart object +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - srcBucket: the source bucket +// - srcObject: the source object +// - uploadId: the multipart upload id +// - partNumber: the current part number +// - args: the optional arguments +// +// RETURNS: +// - *CopyObjectResult: the lastModified and eTag of the part +// - error: nil if ok otherwise the specific error +func (c *Client) UploadPartCopy(bucket, object, srcBucket, srcObject, uploadId string, + partNumber int, args *api.UploadPartCopyArgs) (*api.CopyObjectResult, error) { + source := fmt.Sprintf("/%s/%s", srcBucket, srcObject) + return api.UploadPartCopy(c, bucket, object, source, uploadId, partNumber, args) +} + +// BasicUploadPartCopy - basic interface to copy the multipart object +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - srcBucket: the source bucket +// - srcObject: the source object +// - uploadId: the multipart upload id +// - partNumber: the current part number +// +// RETURNS: +// - *CopyObjectResult: the lastModified and eTag of the part +// - error: nil if ok otherwise the specific error +func (c *Client) BasicUploadPartCopy(bucket, object, srcBucket, srcObject, uploadId string, + partNumber int) (*api.CopyObjectResult, error) { + source := fmt.Sprintf("/%s/%s", srcBucket, srcObject) + return api.UploadPartCopy(c, bucket, object, source, uploadId, partNumber, nil) +} + +// CompleteMultipartUpload - finish a multipart upload operation with parts stream +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// - parts: all parts info stream +// - meta: user defined meta data +// +// RETURNS: +// - *CompleteMultipartUploadResult: the result data +// - error: nil if ok otherwise the specific error +func (c *Client) CompleteMultipartUpload(bucket, object, uploadId string, + body *bce.Body, args *api.CompleteMultipartUploadArgs) (*api.CompleteMultipartUploadResult, error) { + return api.CompleteMultipartUpload(c, bucket, object, uploadId, body, args) +} + +// CompleteMultipartUploadFromStruct - finish a multipart upload operation with parts struct +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// - args: args info struct object +// +// RETURNS: +// - *CompleteMultipartUploadResult: the result data +// - error: nil if ok otherwise the specific error +func (c *Client) CompleteMultipartUploadFromStruct(bucket, object, uploadId string, + args *api.CompleteMultipartUploadArgs) (*api.CompleteMultipartUploadResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return api.CompleteMultipartUpload(c, bucket, object, uploadId, body, args) +} + +// AbortMultipartUpload - abort a multipart upload operation +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) AbortMultipartUpload(bucket, object, uploadId string) error { + return api.AbortMultipartUpload(c, bucket, object, uploadId) +} + +// ListParts - list the successfully uploaded parts info by upload id +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// - args: the optional arguments +// +// RETURNS: +// - *ListPartsResult: the uploaded parts info result +// - error: nil if ok otherwise the specific error +func (c *Client) ListParts(bucket, object, uploadId string, + args *api.ListPartsArgs) (*api.ListPartsResult, error) { + return api.ListParts(c, bucket, object, uploadId, args) +} + +// BasicListParts - basic interface to list the successfully uploaded parts info by upload id +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - uploadId: the multipart upload id +// +// RETURNS: +// - *ListPartsResult: the uploaded parts info result +// - error: nil if ok otherwise the specific error +func (c *Client) BasicListParts(bucket, object, uploadId string) (*api.ListPartsResult, error) { + return api.ListParts(c, bucket, object, uploadId, nil) +} + +// ListMultipartUploads - list the unfinished uploaded parts of the given bucket +// +// PARAMS: +// - bucket: the destination bucket name +// - args: the optional arguments +// +// RETURNS: +// - *ListMultipartUploadsResult: the unfinished uploaded parts info result +// - error: nil if ok otherwise the specific error +func (c *Client) ListMultipartUploads(bucket string, + args *api.ListMultipartUploadsArgs) (*api.ListMultipartUploadsResult, error) { + return api.ListMultipartUploads(c, bucket, args) +} + +// BasicListMultipartUploads - basic interface to list the unfinished uploaded parts +// +// PARAMS: +// - bucket: the destination bucket name +// +// RETURNS: +// - *ListMultipartUploadsResult: the unfinished uploaded parts info result +// - error: nil if ok otherwise the specific error +func (c *Client) BasicListMultipartUploads(bucket string) ( + *api.ListMultipartUploadsResult, error) { + return api.ListMultipartUploads(c, bucket, nil) +} + +// UploadSuperFile - parallel upload the super file by using the multipart upload interface +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - fileName: the local full path filename of the super file +// - storageClass: the storage class to be set to the uploaded file +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UploadSuperFile(bucket, object, fileName, storageClass string) error { + // Get the file size and check the size for multipart upload + file, fileErr := os.Open(fileName) + if fileErr != nil { + return fileErr + } + oldTimeout := c.Config.ConnectionTimeoutInMillis + c.Config.ConnectionTimeoutInMillis = 0 + defer func() { + c.Config.ConnectionTimeoutInMillis = oldTimeout + file.Close() + }() + fileInfo, infoErr := file.Stat() + if infoErr != nil { + return infoErr + } + size := fileInfo.Size() + if size < MIN_MULTIPART_SIZE || c.MultipartSize < MIN_MULTIPART_SIZE { + return bce.NewBceClientError("multipart size should not be less than 1MB") + } + + // Calculate part size and total part number + partSize := (c.MultipartSize + MULTIPART_ALIGN - 1) / MULTIPART_ALIGN * MULTIPART_ALIGN + partNum := (size + partSize - 1) / partSize + if partNum > MAX_PART_NUMBER { + partSize = (size + MAX_PART_NUMBER - 1) / MAX_PART_NUMBER + partSize = (partSize + MULTIPART_ALIGN - 1) / MULTIPART_ALIGN * MULTIPART_ALIGN + partNum = (size + partSize - 1) / partSize + } + log.Debugf("starting upload super file, total parts: %d, part size: %d", partNum, partSize) + + // Inner wrapper function of parallel uploading each part to get the ETag of the part + uploadPart := func(bucket, object, uploadId string, partNumber int, body *bce.Body, + result chan *api.UploadInfoType, ret chan error, id int64, pool chan int64) { + etag, err := c.BasicUploadPart(bucket, object, uploadId, partNumber, body) + if err != nil { + result <- nil + ret <- err + } else { + result <- &api.UploadInfoType{partNumber, etag} + } + pool <- id + } + + // Do the parallel multipart upload + resp, err := c.InitiateMultipartUpload(bucket, object, "", + &api.InitiateMultipartUploadArgs{StorageClass: storageClass}) + if err != nil { + return err + } + uploadId := resp.UploadId + uploadedResult := make(chan *api.UploadInfoType, partNum) + retChan := make(chan error, partNum) + workerPool := make(chan int64, c.MaxParallel) + for i := int64(0); i < c.MaxParallel; i++ { + workerPool <- i + } + for partId := int64(1); partId <= partNum; partId++ { + uploadSize := partSize + offset := (partId - 1) * partSize + left := size - offset + if uploadSize > left { + uploadSize = left + } + partBody, _ := bce.NewBodyFromSectionFile(file, offset, uploadSize) + select { // wait until get a worker to upload + case workerId := <-workerPool: + go uploadPart(bucket, object, uploadId, int(partId), partBody, + uploadedResult, retChan, workerId, workerPool) + case uploadPartErr := <-retChan: + c.AbortMultipartUpload(bucket, object, uploadId) + return uploadPartErr + } + } + + // Check the return of each part uploading, and decide to complete or abort it + completeArgs := &api.CompleteMultipartUploadArgs{ + Parts: make([]api.UploadInfoType, partNum), + } + for i := partNum; i > 0; i-- { + uploaded := <-uploadedResult + if uploaded == nil { // error occurs and not be caught in `select' statement + c.AbortMultipartUpload(bucket, object, uploadId) + return <-retChan + } + completeArgs.Parts[uploaded.PartNumber-1] = *uploaded + log.Debugf("upload part %d success, etag: %s", uploaded.PartNumber, uploaded.ETag) + } + if _, err := c.CompleteMultipartUploadFromStruct(bucket, object, + uploadId, completeArgs); err != nil { + c.AbortMultipartUpload(bucket, object, uploadId) + return err + } + return nil +} + +// DownloadSuperFile - parallel download the super file using the get object with range +// +// PARAMS: +// - bucket: the destination bucket name +// - object: the destination object name +// - fileName: the local full path filename to store the object +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DownloadSuperFile(bucket, object, fileName string) (err error) { + file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) + if err != nil { + return + } + oldTimeout := c.Config.ConnectionTimeoutInMillis + c.Config.ConnectionTimeoutInMillis = 0 + defer func() { + c.Config.ConnectionTimeoutInMillis = oldTimeout + file.Close() + if err != nil { + os.Remove(fileName) + } + }() + + meta, err := c.GetObjectMeta(bucket, object) + if err != nil { + return + } + size := meta.ContentLength + partSize := (c.MultipartSize + MULTIPART_ALIGN - 1) / MULTIPART_ALIGN * MULTIPART_ALIGN + partNum := (size + partSize - 1) / partSize + log.Debugf("starting download super file, total parts: %d, part size: %d", partNum, partSize) + + doneChan := make(chan struct{}, partNum) + abortChan := make(chan struct{}) + + // Set up multiple goroutine workers to download the object + workerPool := make(chan int64, c.MaxParallel) + for i := int64(0); i < c.MaxParallel; i++ { + workerPool <- i + } + for i := int64(0); i < partNum; i++ { + rangeStart := i * partSize + rangeEnd := (i+1)*partSize - 1 + if rangeEnd > size-1 { + rangeEnd = size - 1 + } + select { + case workerId := <-workerPool: + go func(rangeStart, rangeEnd, workerId int64) { + res, rangeGetErr := c.GetObject(bucket, object, nil, rangeStart, rangeEnd) + if rangeGetErr != nil { + log.Errorf("download object part(offset:%d, size:%d) failed: %v", + rangeStart, res.ContentLength, rangeGetErr) + abortChan <- struct{}{} + err = rangeGetErr + return + } + defer res.Body.Close() + log.Debugf("writing part %d with offset=%d, size=%d", rangeStart/partSize, + rangeStart, res.ContentLength) + buf := make([]byte, 4096) + offset := rangeStart + for { + n, e := res.Body.Read(buf) + if e != nil && e != io.EOF { + abortChan <- struct{}{} + err = e + return + } + if n == 0 { + break + } + if _, writeErr := file.WriteAt(buf[:n], offset); writeErr != nil { + abortChan <- struct{}{} + err = writeErr + return + } + offset += int64(n) + } + log.Debugf("writing part %d done", rangeStart/partSize) + workerPool <- workerId + doneChan <- struct{}{} + }(rangeStart, rangeEnd, workerId) + case <-abortChan: // abort range get if error occurs during downloading any part + return + } + } + + // Wait for writing to local file done + for i := partNum; i > 0; i-- { + <-doneChan + } + return nil +} + +// GeneratePresignedUrl - generate an authorization url with expire time and optional arguments +// +// PARAMS: +// - bucket: the target bucket name +// - object: the target object name +// - expireInSeconds: the expire time in seconds of the signed url +// - method: optional sign method, default is GET +// - headers: optional sign headers, default just set the Host +// - params: optional sign params, default is empty +// +// RETURNS: +// - string: the presigned url with authorization string +func (c *Client) GeneratePresignedUrl(bucket, object string, expireInSeconds int, method string, + headers, params map[string]string) string { + return api.GeneratePresignedUrl(c.Config, c.Signer, bucket, object, + expireInSeconds, method, headers, params) +} + +func (c *Client) GeneratePresignedUrlPathStyle(bucket, object string, expireInSeconds int, method string, + headers, params map[string]string) string { + return api.GeneratePresignedUrlPathStyle(c.Config, c.Signer, bucket, object, + expireInSeconds, method, headers, params) +} + +// BasicGeneratePresignedUrl - basic interface to generate an authorization url with expire time +// +// PARAMS: +// - bucket: the target bucket name +// - object: the target object name +// - expireInSeconds: the expire time in seconds of the signed url +// +// RETURNS: +// - string: the presigned url with authorization string +func (c *Client) BasicGeneratePresignedUrl(bucket, object string, expireInSeconds int) string { + return api.GeneratePresignedUrl(c.Config, c.Signer, bucket, object, + expireInSeconds, "", nil, nil) +} + +// PutObjectAcl - set the ACL of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - aclBody: the acl json body stream +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAcl(bucket, object string, aclBody *bce.Body) error { + return api.PutObjectAcl(c, bucket, object, "", nil, nil, aclBody) +} + +// PutObjectAclFromCanned - set the canned acl of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - cannedAcl: the cannedAcl string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclFromCanned(bucket, object, cannedAcl string) error { + return api.PutObjectAcl(c, bucket, object, cannedAcl, nil, nil, nil) +} + +// PutObjectAclGrantRead - set the canned grant read acl of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - ids: the user id list to grant read for this object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclGrantRead(bucket, object string, ids ...string) error { + return api.PutObjectAcl(c, bucket, object, "", ids, nil, nil) +} + +// PutObjectAclGrantFullControl - set the canned grant full-control acl of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - ids: the user id list to grant full-control for this object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclGrantFullControl(bucket, object string, ids ...string) error { + return api.PutObjectAcl(c, bucket, object, "", nil, ids, nil) +} + +// PutObjectAclFromFile - set the acl of the given object with acl json file name +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - aclFile: the acl file name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclFromFile(bucket, object, aclFile string) error { + body, err := bce.NewBodyFromFile(aclFile) + if err != nil { + return err + } + return api.PutObjectAcl(c, bucket, object, "", nil, nil, body) +} + +// PutObjectAclFromString - set the acl of the given object with acl json string +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - aclString: the acl string with json format +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclFromString(bucket, object, aclString string) error { + body, err := bce.NewBodyFromString(aclString) + if err != nil { + return err + } + return api.PutObjectAcl(c, bucket, object, "", nil, nil, body) +} + +// PutObjectAclFromStruct - set the acl of the given object with acl data structure +// +// PARAMS: +// - bucket: the bucket name +// - aclObj: the acl struct object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutObjectAclFromStruct(bucket, object string, aclObj *api.PutObjectAclArgs) error { + jsonBytes, jsonErr := json.Marshal(aclObj) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + return api.PutObjectAcl(c, bucket, object, "", nil, nil, body) +} + +// GetObjectAcl - get the acl of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// +// RETURNS: +// - *api.GetObjectAclResult: the result of the object acl +// - error: nil if success otherwise the specific error +func (c *Client) GetObjectAcl(bucket, object string) (*api.GetObjectAclResult, error) { + return api.GetObjectAcl(c, bucket, object) +} + +// DeleteObjectAcl - delete the acl of the given object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteObjectAcl(bucket, object string) error { + return api.DeleteObjectAcl(c, bucket, object) +} + +// RestoreObject - restore the archive object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - restoreDays: the effective time of restore +// - restoreTier: the tier of restore +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RestoreObject(bucket string, object string, restoreDays int, restoreTier string) error { + if _, ok := api.VALID_RESTORE_TIER[restoreTier]; !ok { + return errors.New("invalid restore tier") + } + + if restoreDays <= 0 { + return errors.New("invalid restore days") + } + + args := api.ArchiveRestoreArgs{ + RestoreTier: restoreTier, + RestoreDays: restoreDays, + } + return api.RestoreObject(c, bucket, object, args) +} + +// PutBucketTrash - put the bucket trash +// +// PARAMS: +// - bucket: the bucket name +// - trashReq: the trash request +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketTrash(bucket string, trashReq api.PutBucketTrashReq) error { + return api.PutBucketTrash(c, bucket, trashReq) +} + +// GetBucketTrash - get the bucket trash +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.GetBucketTrashResult,: the result of the bucket trash +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketTrash(bucket string) (*api.GetBucketTrashResult, error) { + return api.GetBucketTrash(c, bucket) +} + +// DeleteBucketTrash - delete the trash of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketTrash(bucket string) error { + return api.DeleteBucketTrash(c, bucket) +} + +// PutBucketNotification - put the bucket notification +// +// PARAMS: +// - bucket: the bucket name +// - putBucketNotificationReq: the bucket notification request +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PutBucketNotification(bucket string, putBucketNotificationReq api.PutBucketNotificationReq) error { + return api.PutBucketNotification(c, bucket, putBucketNotificationReq) +} + +// GetBucketNotification - get the bucket notification +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - *api.PutBucketNotificationReq,: the result of the bucket notification +// - error: nil if success otherwise the specific error +func (c *Client) GetBucketNotification(bucket string) (*api.PutBucketNotificationReq, error) { + return api.GetBucketNotification(c, bucket) +} + +// DeleteBucketNotification - delete the notification of the given bucket +// +// PARAMS: +// - bucket: the bucket name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBucketNotification(bucket string) error { + return api.DeleteBucketNotification(c, bucket) +} + +// ParallelUpload - auto multipart upload object +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - filename: the filename +// - contentType: the content type default(application/octet-stream) +// - args: the bucket name nil using default +// +// RETURNS: +// - *api.CompleteMultipartUploadResult: multipart upload result +// - error: nil if success otherwise the specific error +func (c *Client) ParallelUpload(bucket string, object string, filename string, contentType string, args *api.InitiateMultipartUploadArgs) (*api.CompleteMultipartUploadResult, error) { + + initiateMultipartUploadResult, err := api.InitiateMultipartUpload(c, bucket, object, contentType, args) + if err != nil { + return nil, err + } + + partEtags, err := c.parallelPartUpload(bucket, object, filename, initiateMultipartUploadResult.UploadId) + if err != nil { + c.AbortMultipartUpload(bucket, object, initiateMultipartUploadResult.UploadId) + return nil, err + } + + completeMultipartUploadResult, err := c.CompleteMultipartUploadFromStruct(bucket, object, initiateMultipartUploadResult.UploadId, &api.CompleteMultipartUploadArgs{Parts: partEtags}) + if err != nil { + c.AbortMultipartUpload(bucket, object, initiateMultipartUploadResult.UploadId) + return nil, err + } + return completeMultipartUploadResult, nil +} + +// parallelPartUpload - single part upload +// +// PARAMS: +// - bucket: the bucket name +// - object: the object name +// - filename: the uploadId +// - uploadId: the uploadId +// +// RETURNS: +// - []api.UploadInfoType: multipart upload result +// - error: nil if success otherwise the specific error +func (c *Client) parallelPartUpload(bucket string, object string, filename string, uploadId string) ([]api.UploadInfoType, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + // 分块大小按MULTIPART_ALIGN=1MB对齐 + partSize := (c.MultipartSize + + MULTIPART_ALIGN - 1) / MULTIPART_ALIGN * MULTIPART_ALIGN + + // 获取文件大小,并计算分块数目,最大分块数MAX_PART_NUMBER=10000 + fileInfo, _ := file.Stat() + fileSize := fileInfo.Size() + partNum := (fileSize + partSize - 1) / partSize + if partNum > MAX_PART_NUMBER { // 超过最大分块数,需调整分块大小 + partSize = (fileSize + MAX_PART_NUMBER + 1) / MAX_PART_NUMBER + partSize = (partSize + MULTIPART_ALIGN - 1) / MULTIPART_ALIGN * MULTIPART_ALIGN + partNum = (fileSize + partSize - 1) / partSize + } + + parallelChan := make(chan int, c.MaxParallel) + + errChan := make(chan error, c.MaxParallel) + + resultChan := make(chan api.UploadInfoType, partNum) + + // 逐个分块上传 + for i := int64(1); i <= partNum; i++ { + // 计算偏移offset和本次上传的大小uploadSize + uploadSize := partSize + offset := partSize * (i - 1) + left := fileSize - offset + if left < partSize { + uploadSize = left + } + + // 创建指定偏移、指定大小的文件流 + partBody, _ := bce.NewBodyFromSectionFile(file, offset, uploadSize) + + select { + case err = <-errChan: + return nil, err + default: + select { + case err = <-errChan: + return nil, err + case parallelChan <- 1: + go c.singlePartUpload(bucket, object, uploadId, int(i), partBody, parallelChan, errChan, resultChan) + } + + } + } + + partEtags := make([]api.UploadInfoType, partNum) + for i := int64(0); i < partNum; i++ { + select { + case err := <-errChan: + return nil, err + case result := <-resultChan: + partEtags[result.PartNumber-1].PartNumber = result.PartNumber + partEtags[result.PartNumber-1].ETag = result.ETag + } + } + return partEtags, nil +} + +// singlePartUpload - single part upload +// +// PARAMS: +// - pararelChan: the pararelChan +// - errChan: the error chan +// - result: the upload result chan +// - bucket: the bucket name +// - object: the object name +// - uploadId: the uploadId +// - partNumber: the part number of the object +// - content: the content of current part +func (c *Client) singlePartUpload( + bucket string, object string, uploadId string, + partNumber int, content *bce.Body, + parallelChan chan int, errChan chan error, result chan api.UploadInfoType) { + + defer func() { + if r := recover(); r != nil { + log.Fatal("parallelPartUpload recovered in f:", r) + errChan <- errors.New("parallelPartUpload panic") + } + <-parallelChan + }() + + var args api.UploadPartArgs + args.ContentMD5 = content.ContentMD5() + + etag, err := api.UploadPart(c, bucket, object, uploadId, partNumber, content, &args) + if err != nil { + errChan <- err + log.Error("upload part fail,err:%v", err) + return + } + result <- api.UploadInfoType{PartNumber: partNumber, ETag: etag} + return +} + +// ParallelCopy - auto multipart copy object +// +// PARAMS: +// - srcBucketName: the src bucket name +// - srcObjectName: the src object name +// - destBucketName: the dest bucket name +// - destObjectName: the dest object name +// - args: the copy args +// - srcClient: the src region client +// +// RETURNS: +// - *api.CompleteMultipartUploadResult: multipart upload result +// - error: nil if success otherwise the specific error +func (c *Client) ParallelCopy(srcBucketName string, srcObjectName string, + destBucketName string, destObjectName string, + args *api.MultiCopyObjectArgs, srcClient *Client) (*api.CompleteMultipartUploadResult, error) { + + if srcClient == nil { + srcClient = c + } + objectMeta, err := srcClient.GetObjectMeta(srcBucketName, srcObjectName) + if err != nil { + return nil, err + } + + initArgs := api.InitiateMultipartUploadArgs{ + CacheControl: objectMeta.CacheControl, + ContentDisposition: objectMeta.ContentDisposition, + Expires: objectMeta.Expires, + StorageClass: objectMeta.StorageClass, + } + if args != nil { + if len(args.StorageClass) != 0 { + initArgs.StorageClass = args.StorageClass + } + } + initiateMultipartUploadResult, err := api.InitiateMultipartUpload(c, destBucketName, destObjectName, objectMeta.ContentType, &initArgs) + + if err != nil { + return nil, err + } + + source := fmt.Sprintf("/%s/%s", srcBucketName, srcObjectName) + partEtags, err := c.parallelPartCopy(*objectMeta, source, destBucketName, destObjectName, initiateMultipartUploadResult.UploadId) + + if err != nil { + c.AbortMultipartUpload(destBucketName, destObjectName, initiateMultipartUploadResult.UploadId) + return nil, err + } + + completeMultipartUploadResult, err := c.CompleteMultipartUploadFromStruct(destBucketName, destObjectName, initiateMultipartUploadResult.UploadId, &api.CompleteMultipartUploadArgs{Parts: partEtags}) + if err != nil { + c.AbortMultipartUpload(destBucketName, destObjectName, initiateMultipartUploadResult.UploadId) + return nil, err + } + return completeMultipartUploadResult, nil +} + +// parallelPartCopy - parallel part copy +// +// PARAMS: +// - srcMeta: the copy source object meta +// - source: the copy source +// - bucket: the dest bucket name +// - object: the dest object name +// - uploadId: the uploadId +// +// RETURNS: +// - []api.UploadInfoType: multipart upload result +// - error: nil if success otherwise the specific error +func (c *Client) parallelPartCopy(srcMeta api.GetObjectMetaResult, source string, bucket string, object string, uploadId string) ([]api.UploadInfoType, error) { + var err error + size := srcMeta.ContentLength + partSize := int64(DEFAULT_MULTIPART_SIZE) + if partSize*MAX_PART_NUMBER < size { + lowerLimit := int64(math.Ceil(float64(size) / MAX_PART_NUMBER)) + partSize = int64(math.Ceil(float64(lowerLimit)/float64(partSize))) * partSize + } + partNum := (size + partSize - 1) / partSize + + parallelChan := make(chan int, c.MaxParallel) + + errChan := make(chan error, c.MaxParallel) + + resultChan := make(chan api.UploadInfoType, partNum) + + for i := int64(1); i <= partNum; i++ { + // 计算偏移offset和本次上传的大小uploadSize + uploadSize := partSize + offset := partSize * (i - 1) + left := size - offset + if left < partSize { + uploadSize = left + } + + partCopyArgs := api.UploadPartCopyArgs{ + SourceRange: fmt.Sprintf("bytes=%d-%d", (i-1)*partSize, (i-1)*partSize+uploadSize-1), + IfMatch: srcMeta.ETag, + } + + select { + case err = <-errChan: + return nil, err + default: + select { + case err = <-errChan: + return nil, err + case parallelChan <- 1: + go c.singlePartCopy(source, bucket, object, uploadId, int(i), &partCopyArgs, parallelChan, errChan, resultChan) + } + + } + } + + partEtags := make([]api.UploadInfoType, partNum) + for i := int64(0); i < partNum; i++ { + select { + case err := <-errChan: + return nil, err + case result := <-resultChan: + partEtags[result.PartNumber-1].PartNumber = result.PartNumber + partEtags[result.PartNumber-1].ETag = result.ETag + } + } + return partEtags, nil +} + +// singlePartCopy - single part copy +// +// PARAMS: +// - pararelChan: the pararelChan +// - errChan: the error chan +// - result: the upload result chan +// - source: the copy source +// - bucket: the bucket name +// - object: the object name +// - uploadId: the uploadId +// - partNumber: the part number of the object +// - args: the copy args +func (c *Client) singlePartCopy(source string, bucket string, object string, uploadId string, + partNumber int, args *api.UploadPartCopyArgs, + parallelChan chan int, errChan chan error, result chan api.UploadInfoType) { + + defer func() { + if r := recover(); r != nil { + log.Fatal("parallelPartUpload recovered in f:", r) + errChan <- errors.New("parallelPartUpload panic") + } + <-parallelChan + }() + + copyObjectResult, err := api.UploadPartCopy(c, bucket, object, source, uploadId, partNumber, args) + if err != nil { + errChan <- err + log.Error("upload part fail,err:%v", err) + return + } + result <- api.UploadInfoType{PartNumber: partNumber, ETag: copyObjectResult.ETag} + return +} + +// PutSymlink - create symlink for exist target object +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the object +// - symlinkKey: the name of the symlink +// - symlinkArgs: the optional arguments +// +// RETURNS: +// - error: the put error if any occurs +func (c *Client) PutSymlink(bucket string, object string, symlinkKey string, symlinkArgs *api.PutSymlinkArgs) error { + return api.PutObjectSymlink(c, bucket, object, symlinkKey, symlinkArgs) +} + +// PutSymlink - create symlink for exist target object +// +// PARAMS: +// - bucket: the name of the bucket +// - object: the name of the symlink +// +// RETURNS: +// - string: the target of the symlink +// - error: the put error if any occurs +func (c *Client) GetSymlink(bucket string, object string) (string, error) { + return api.GetObjectSymlink(c, bucket, object) +} + +func (c *Client) PutBucketMirror(bucket string, putBucketMirrorArgs *api.PutBucketMirrorArgs) error { + return api.PutBucketMirror(c, bucket, putBucketMirrorArgs) +} + +func (c *Client) GetBucketMirror(bucket string) (*api.PutBucketMirrorArgs, error) { + return api.GetBucketMirror(c, bucket) +} + +func (c *Client) DeleteBucketMirror(bucket string) error { + return api.DeleteBucketMirror(c, bucket) +} + +func (c *Client) PutBucketTag(bucket string, putBucketTagArgs *api.PutBucketTagArgs) error { + return api.PutBucketTag(c, bucket, putBucketTagArgs) +} + +func (c *Client) GetBucketTag(bucket string) (*api.PutBucketTagArgs, error) { + return api.GetBucketTag(c, bucket) +} + +func (c *Client) DeleteBucketTag(bucket string) error { + return api.DeleteBucketTag(c, bucket) +} diff --git a/bce-sdk-go/services/bos/client_test.go b/bce-sdk-go/services/bos/client_test.go new file mode 100644 index 0000000..59b8157 --- /dev/null +++ b/bce-sdk-go/services/bos/client_test.go @@ -0,0 +1,1177 @@ +package bos + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bos/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + BOS_CLIENT *Client + EXISTS_BUCKET = "gosdk-unittest-bucket" + EXISTS_OBJECT = "gosdk-unittest-object" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string +} + +// init client with your ak/sk written in config.json +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + BOS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, "") + //log.SetLogHandler(log.STDERR | log.FILE) + //log.SetRotateType(log.ROTATE_SIZE) + log.SetLogLevel(log.WARN) + //log.SetLogHandler(log.STDERR) + //log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} +func TestListBuckets(t *testing.T) { + res, err := BOS_CLIENT.ListBuckets() + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestListObjects(t *testing.T) { + args := &api.ListObjectsArgs{Prefix: "test", MaxKeys: 10} + res, err := BOS_CLIENT.ListObjects(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestSimpleListObjects(t *testing.T) { + res, err := BOS_CLIENT.SimpleListObjects(EXISTS_BUCKET, "test", 10, "", "") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestHeadBucket(t *testing.T) { + err := BOS_CLIENT.HeadBucket(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) +} +func TestDoesBucketExist(t *testing.T) { + exist, err := BOS_CLIENT.DoesBucketExist(EXISTS_BUCKET) + ExpectEqual(t.Errorf, exist, true) + ExpectEqual(t.Errorf, err, nil) + exist, err = BOS_CLIENT.DoesBucketExist("xxx") + ExpectEqual(t.Errorf, exist, false) +} +func TestPutBucket(t *testing.T) { + res, err := BOS_CLIENT.PutBucket("test-put-bucket") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} +func TestDeleteBucket(t *testing.T) { + err := BOS_CLIENT.DeleteBucket("test-put-bucket") + ExpectEqual(t.Errorf, err, nil) +} +func TestGetBucketLocation(t *testing.T) { + res, err := BOS_CLIENT.GetBucketLocation(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} +func TestPutBucketAcl(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" + }], + "permission":["FULL_CONTROL"] + } + ] +}` + body, _ := bce.NewBodyFromString(acl) + err := BOS_CLIENT.PutBucketAcl(EXISTS_BUCKET, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketAcl(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[0].Id, + "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutBucketAclFromCanned(t *testing.T) { + err := BOS_CLIENT.PutBucketAclFromCanned(EXISTS_BUCKET, api.CANNED_ACL_PUBLIC_READ) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketAcl(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[0].Id, "*") + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "READ") +} +func TestPutBucketAclFromFile(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[ + {"id":"e13b12d0131b4c8bae959df4969387b8"}, + {"id":"a13b12d0131b4c8bae959df4969387b8"} + ], + "permission":["FULL_CONTROL"] + } + ] +}` + fname := "/tmp/test-put-bucket-acl-by-file" + f, _ := os.Create(fname) + f.WriteString(acl) + f.Close() + err := BOS_CLIENT.PutBucketAclFromFile(EXISTS_BUCKET, fname) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketAcl(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + os.Remove(fname) + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[0].Id, + "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[1].Id, + "a13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutBucketAclFromString(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[ + {"id":"e13b12d0131b4c8bae959df4969387b8"}, + {"id":"a13b12d0131b4c8bae959df4969387b8"} + ], + "permission":["FULL_CONTROL"] + } + ] +}` + err := BOS_CLIENT.PutBucketAclFromString(EXISTS_BUCKET, acl) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketAcl(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[0].Id, + "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[1].Id, + "a13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutBucketAclFromStruct(t *testing.T) { + args := &api.PutBucketAclArgs{ + []api.GrantType{ + api.GrantType{ + Grantee: []api.GranteeType{ + api.GranteeType{"e13b12d0131b4c8bae959df4969387b8"}, + }, + Permission: []string{ + "FULL_CONTROL", + }, + }, + }, + } + err := BOS_CLIENT.PutBucketAclFromStruct(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketAcl(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.AccessControlList[0].Grantee[0].Id, + "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutBucketLogging(t *testing.T) { + body, _ := bce.NewBodyFromString( + `{"targetBucket": "gosdk-unittest-bucket", "targetPrefix": "my-log/"}`) + err := BOS_CLIENT.PutBucketLogging(EXISTS_BUCKET, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLogging(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.TargetBucket, "gosdk-unittest-bucket") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.TargetPrefix, "my-log/") +} +func TestPutBucketLoggingFromString(t *testing.T) { + logging := `{"targetBucket": "gosdk-unittest-bucket", "targetPrefix": "my-log2/"}` + err := BOS_CLIENT.PutBucketLoggingFromString(EXISTS_BUCKET, logging) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLogging(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.TargetBucket, "gosdk-unittest-bucket") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.TargetPrefix, "my-log2/") +} +func TestPutBucketLoggingFromStruct(t *testing.T) { + obj := &api.PutBucketLoggingArgs{ + TargetBucket: "gosdk-unittest-bucket", + TargetPrefix: "my-log3/", + } + err := BOS_CLIENT.PutBucketLoggingFromStruct(EXISTS_BUCKET, obj) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLogging(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.TargetBucket, "gosdk-unittest-bucket") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.TargetPrefix, "my-log3/") +} +func TestDeleteBucketLogging(t *testing.T) { + err := BOS_CLIENT.DeleteBucketLogging(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLogging(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Status, "disabled") +} +func TestPutBucketLifecycle(t *testing.T) { + str := `{ + "rule": [ + { + "id": "transition-to-cold", + "status": "enabled", + "resource": ["gosdk-unittest-bucket/test*"], + "condition": { + "time": { + "dateGreaterThan": "2018-09-07T00:00:00Z" + } + }, + "action": { + "name": "DeleteObject" + } + } + ] +}` + body, _ := bce.NewBodyFromString(str) + err := BOS_CLIENT.PutBucketLifecycle(EXISTS_BUCKET, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLifecycle(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Rule[0].Id, "transition-to-cold") + ExpectEqual(t.Errorf, res.Rule[0].Status, "enabled") + ExpectEqual(t.Errorf, res.Rule[0].Resource[0], "gosdk-unittest-bucket/test*") + ExpectEqual(t.Errorf, res.Rule[0].Condition.Time.DateGreaterThan, "2018-09-07T00:00:00Z") + ExpectEqual(t.Errorf, res.Rule[0].Action.Name, "DeleteObject") +} +func TestPutBucketLifecycleFromString(t *testing.T) { + obj := `{ + "rule": [ + { + "id": "transition-to-cold", + "status": "enabled", + "resource": ["gosdk-unittest-bucket/test*"], + "condition": { + "time": { + "dateGreaterThan": "2018-09-07T00:00:00Z" + } + }, + "action": { + "name": "DeleteObject" + } + } + ] +}` + err := BOS_CLIENT.PutBucketLifecycleFromString(EXISTS_BUCKET, obj) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketLifecycle(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Rule[0].Id, "transition-to-cold") + ExpectEqual(t.Errorf, res.Rule[0].Status, "enabled") + ExpectEqual(t.Errorf, res.Rule[0].Resource[0], "gosdk-unittest-bucket/test*") + ExpectEqual(t.Errorf, res.Rule[0].Condition.Time.DateGreaterThan, "2018-09-07T00:00:00Z") + ExpectEqual(t.Errorf, res.Rule[0].Action.Name, "DeleteObject") +} +func TestDeleteBucketLifecycle(t *testing.T) { + err := BOS_CLIENT.DeleteBucketLifecycle(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, _ := BOS_CLIENT.GetBucketLifecycle(EXISTS_BUCKET) + ExpectEqual(t.Errorf, res, nil) +} +func TestPutBucketStorageClass(t *testing.T) { + err := BOS_CLIENT.PutBucketStorageclass(EXISTS_BUCKET, api.STORAGE_CLASS_STANDARD_IA) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStorageclass(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res, api.STORAGE_CLASS_STANDARD_IA) +} +func TestGetBucketStorageClass(t *testing.T) { + res, err := BOS_CLIENT.GetBucketStorageclass(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestPutBucketReplication(t *testing.T) { + BOS_CLIENT.DeleteBucketReplication(EXISTS_BUCKET, "") + str := `{ + "id": "abc", + "status":"enabled", + "resource": ["gosdk-unittest-bucket/films"], + "destination": { + "bucket": "bos-rd-su-test", + "storageClass": "COLD" + }, + "replicateDeletes": "disabled" +}` + body, _ := bce.NewBodyFromString(str) + err := BOS_CLIENT.PutBucketReplication(EXISTS_BUCKET, body, "") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Id, "abc") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.Resource[0], "gosdk-unittest-bucket/films") + ExpectEqual(t.Errorf, res.Destination.Bucket, "bos-rd-su-test") + ExpectEqual(t.Errorf, res.ReplicateDeletes, "disabled") +} +func TestPutBucketReplicationFromFile(t *testing.T) { + BOS_CLIENT.DeleteBucketReplication(EXISTS_BUCKET, "") + str := `{ + "id": "abc", + "status":"enabled", + "resource": ["gosdk-unittest-bucket/films"], + "destination": { + "bucket": "bos-rd-su-test", + "storageClass": "COLD" + }, + "replicateDeletes": "disabled" +}` + fname := "/tmp/test-put-bucket-replication-by-file" + f, _ := os.Create(fname) + f.WriteString(str) + f.Close() + err := BOS_CLIENT.PutBucketReplicationFromFile(EXISTS_BUCKET, fname, "") + os.Remove(fname) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Id, "abc") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.Resource[0], "gosdk-unittest-bucket/films") + ExpectEqual(t.Errorf, res.Destination.Bucket, "bos-rd-su-test") + ExpectEqual(t.Errorf, res.ReplicateDeletes, "disabled") +} +func TestPutBucketReplicationFromString(t *testing.T) { + BOS_CLIENT.DeleteBucketReplication(EXISTS_BUCKET, "") + str := `{ + "id": "abc", + "status":"enabled", + "resource": ["gosdk-unittest-bucket/films"], + "destination": { + "bucket": "bos-rd-su-test", + "storageClass": "COLD" + }, + "replicateDeletes": "disabled" +}` + err := BOS_CLIENT.PutBucketReplicationFromString(EXISTS_BUCKET, str, "") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Id, "abc") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.Resource[0], "gosdk-unittest-bucket/films") + ExpectEqual(t.Errorf, res.Destination.Bucket, "bos-rd-su-test") + ExpectEqual(t.Errorf, res.ReplicateDeletes, "disabled") +} +func TestPutBucketReplicationFromStruct(t *testing.T) { + BOS_CLIENT.DeleteBucketReplication(EXISTS_BUCKET, "") + args := &api.PutBucketReplicationArgs{ + Id: "abc", + Status: "enabled", + Resource: []string{"gosdk-unittest-bucket/films"}, + Destination: &api.BucketReplicationDescriptor{"bos-rd-su-test", "COLD"}, + ReplicateDeletes: "disabled", + } + err := BOS_CLIENT.PutBucketReplicationFromStruct(EXISTS_BUCKET, args, "") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Id, "abc") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.Resource[0], "gosdk-unittest-bucket/films") + ExpectEqual(t.Errorf, res.Destination.Bucket, "bos-rd-su-test") + ExpectEqual(t.Errorf, res.ReplicateDeletes, "disabled") +} +func TestGetBucketReplication(t *testing.T) { + res, err := BOS_CLIENT.GetBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Id, "abc") + ExpectEqual(t.Errorf, res.Status, "enabled") + ExpectEqual(t.Errorf, res.Resource[0], "gosdk-unittest-bucket/films") + ExpectEqual(t.Errorf, res.Destination.Bucket, "bos-rd-su-test") + ExpectEqual(t.Errorf, res.ReplicateDeletes, "disabled") +} +func TestGetBucketReplicationProcess(t *testing.T) { + res, err := BOS_CLIENT.GetBucketReplicationProgress(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} +func TestDeleteBucketReplication(t *testing.T) { + err := BOS_CLIENT.DeleteBucketReplication(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err, nil) +} +func TestPutBucketEncryption(t *testing.T) { + err := BOS_CLIENT.PutBucketEncryption(EXISTS_BUCKET, api.ENCRYPTION_AES256) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketEncryption(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res, api.ENCRYPTION_AES256) +} +func TestGetBucketEncryption(t *testing.T) { + res, err := BOS_CLIENT.GetBucketEncryption(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestDeleteBucketEncryption(t *testing.T) { + err := BOS_CLIENT.DeleteBucketEncryption(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketEncryption(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestPutBucketStaticWebsite(t *testing.T) { + BOS_CLIENT.DeleteBucketStaticWebsite(EXISTS_BUCKET) + body, _ := bce.NewBodyFromString(`{"index": "index.html", "notFound":"blank.html"}`) + err := BOS_CLIENT.PutBucketStaticWebsite(EXISTS_BUCKET, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Index, "index.html") + ExpectEqual(t.Errorf, res.NotFound, "blank.html") +} +func TestPutBucketStaticWebsiteFromString(t *testing.T) { + BOS_CLIENT.DeleteBucketStaticWebsite(EXISTS_BUCKET) + jsonConf := `{"index": "index.html", "notFound":"blank.html"}` + err := BOS_CLIENT.PutBucketStaticWebsiteFromString(EXISTS_BUCKET, jsonConf) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Index, "index.html") + ExpectEqual(t.Errorf, res.NotFound, "blank.html") +} +func TestPutBucketStaticWebsiteFromStruct(t *testing.T) { + BOS_CLIENT.DeleteBucketStaticWebsite(EXISTS_BUCKET) + obj := &api.PutBucketStaticWebsiteArgs{"index.html", "blank.html"} + err := BOS_CLIENT.PutBucketStaticWebsiteFromStruct(EXISTS_BUCKET, obj) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Index, "index.html") + ExpectEqual(t.Errorf, res.NotFound, "blank.html") +} +func TestSimplePutBucketStaticWebsite(t *testing.T) { + BOS_CLIENT.DeleteBucketStaticWebsite(EXISTS_BUCKET) + err := BOS_CLIENT.SimplePutBucketStaticWebsite(EXISTS_BUCKET, "index.html", "blank.html") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.Index, "index.html") + ExpectEqual(t.Errorf, res.NotFound, "blank.html") +} +func TestGetBucketStaticWebsite(t *testing.T) { + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} +func TestDeleteBucketStaticWebsite(t *testing.T) { + err := BOS_CLIENT.DeleteBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketStaticWebsite(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v", res) +} +func TestPutBucketCors(t *testing.T) { + body, _ := bce.NewBodyFromString(` + { + "corsConfiguration": [ + { + "allowedOrigins": ["https://www.baidu.com"], + "allowedMethods": ["GET"], + "maxAgeSeconds": 1800 + } + ] + } + `) + err := BOS_CLIENT.PutBucketCors(EXISTS_BUCKET, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedOrigins[0], "https://www.baidu.com") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedMethods[0], "GET") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].MaxAgeSeconds, 1800) +} +func TestPutBucketCorsFromFile(t *testing.T) { + str := `{ + "corsConfiguration": [ + { + "allowedOrigins": ["https://www.baidu.com"], + "allowedMethods": ["GET"], + "maxAgeSeconds": 1800 + } + ] + }` + fname := "/tmp/test-put-bucket-cors-by-file" + f, _ := os.Create(fname) + f.WriteString(str) + f.Close() + err := BOS_CLIENT.PutBucketCorsFromFile(EXISTS_BUCKET, fname) + os.Remove(fname) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedOrigins[0], "https://www.baidu.com") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedMethods[0], "GET") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].MaxAgeSeconds, 1800) + err = BOS_CLIENT.PutBucketCorsFromFile(EXISTS_BUCKET, "/tmp/not-exist") + ExpectEqual(t.Errorf, err != nil, true) +} +func TestPutBucketCorsFromString(t *testing.T) { + str := `{ + "corsConfiguration": [ + { + "allowedOrigins": ["https://www.baidu.com"], + "allowedMethods": ["GET"], + "maxAgeSeconds": 1800 + } + ] + }` + err := BOS_CLIENT.PutBucketCorsFromString(EXISTS_BUCKET, str) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedOrigins[0], "https://www.baidu.com") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedMethods[0], "GET") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].MaxAgeSeconds, 1800) + err = BOS_CLIENT.PutBucketCorsFromString(EXISTS_BUCKET, "") + ExpectEqual(t.Errorf, err != nil, true) +} +func TestPutBucketCorsFromStruct(t *testing.T) { + obj := &api.PutBucketCorsArgs{ + []api.BucketCORSType{ + api.BucketCORSType{ + AllowedOrigins: []string{"https://www.baidu.com"}, + AllowedMethods: []string{"GET"}, + MaxAgeSeconds: 1200, + }, + }, + } + err := BOS_CLIENT.PutBucketCorsFromStruct(EXISTS_BUCKET, obj) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedOrigins[0], "https://www.baidu.com") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedMethods[0], "GET") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].MaxAgeSeconds, 1200) + err = BOS_CLIENT.PutBucketCorsFromStruct(EXISTS_BUCKET, nil) + ExpectEqual(t.Errorf, err != nil, true) +} +func TestGetBucketCors(t *testing.T) { + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedOrigins[0], "https://www.baidu.com") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].AllowedMethods[0], "GET") + ExpectEqual(t.Errorf, res.CorsConfiguration[0].MaxAgeSeconds, 1200) +} +func TestDeleteBucketCors(t *testing.T) { + err := BOS_CLIENT.DeleteBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCors(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v, %v", res, err) +} +func TestPutBucketCopyrightProtection(t *testing.T) { + err := BOS_CLIENT.PutBucketCopyrightProtection(EXISTS_BUCKET, + "gosdk-unittest-bucket/test-put-object", "gosdk-unittest-bucket/films/*") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCopyrightProtection(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res[0], "gosdk-unittest-bucket/test-put-object") + ExpectEqual(t.Errorf, res[1], "gosdk-unittest-bucket/films/*") +} +func TestGetBucketCopyrightProtection(t *testing.T) { + res, err := BOS_CLIENT.GetBucketCopyrightProtection(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v, %v", res, err) +} +func TestDeleteBucketCopyrightProtection(t *testing.T) { + err := BOS_CLIENT.DeleteBucketCopyrightProtection(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketCopyrightProtection(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v, %v", res, err) +} + +func TestPutObject(t *testing.T) { + args := &api.PutObjectArgs{StorageClass: api.STORAGE_CLASS_COLD} + body, _ := bce.NewBodyFromString("12345") + etag, err := BOS_CLIENT.PutObject(EXISTS_BUCKET, "test-put-object", body, args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) +} + +func TestBasicPutObject(t *testing.T) { + body, _ := bce.NewBodyFromString("12345") + etag, err := BOS_CLIENT.BasicPutObject(EXISTS_BUCKET, "test-put-object", body) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) +} + +func TestPutObjectFromBytes(t *testing.T) { + arr := []byte("12345") + etag, err := BOS_CLIENT.PutObjectFromBytes(EXISTS_BUCKET, "test-put-object", arr, nil) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) +} + +func TestPutObjectFromString(t *testing.T) { + etag, err := BOS_CLIENT.PutObjectFromString(EXISTS_BUCKET, "test-put-object", "12345", nil) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) +} + +func TestPutObjectFromFile(t *testing.T) { + fname := "/tmp/test-put-file" + f, _ := os.Create(fname) + f.WriteString("12345") + f.Close() + etag, err := BOS_CLIENT.PutObjectFromFile(EXISTS_BUCKET, "test-put-object", fname, nil) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) + args := &api.PutObjectArgs{ContentLength: 6} + etag, err = BOS_CLIENT.PutObjectFromFile(EXISTS_BUCKET, "test-put-object", fname, args) + ExpectEqual(t.Errorf, true, err != nil) + ExpectEqual(t.Errorf, "", etag) + os.Remove(fname) +} + +func TestPutObjectFromStream(t *testing.T) { + fname := "/tmp/test-put-file" + fw, _ := os.Create(fname) + defer os.Remove(fname) + fw.WriteString("12345") + fw.Close() + fr, _ := os.Open(fname) + defer fr.Close() + etag, err := BOS_CLIENT.PutObjectFromStream(EXISTS_BUCKET, "test-put-object", fr, nil) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "827ccb0eea8a706c4c34a16891f84e7b", etag) +} + +func TestCopyObject(t *testing.T) { + args := new(api.CopyObjectArgs) + args.StorageClass = api.STORAGE_CLASS_COLD + res, err := BOS_CLIENT.CopyObject(EXISTS_BUCKET, "test-copy-object", + EXISTS_BUCKET, "test-put-object", args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("copy result: %+v", res) +} +func TestBasicCopyObject(t *testing.T) { + res, err := BOS_CLIENT.BasicCopyObject(EXISTS_BUCKET, "test-copy-object", + EXISTS_BUCKET, "test-put-object") + ExpectEqual(t.Errorf, err, nil) + t.Logf("copy result: %+v", res) +} +func TestGetObject(t *testing.T) { + res, err := BOS_CLIENT.GetObject(EXISTS_BUCKET, "test-put-object", nil) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + t.Logf("%v", res.ContentLength) + buf := make([]byte, 1024) + n, _ := res.Body.Read(buf) + t.Logf("%s", buf[0:n]) + res.Body.Close() + /* + respHeaders := map[string]string{"ContentEncoding": "qqqqqqqqqqqqq"} + + res, err = BOS_CLIENT.GetObject(EXISTS_BUCKET, "test-put-object", respHeaders) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + t.Logf("%v", res.ContentLength) + n, _ = res.Body.Read(buf) + t.Logf("%s", buf[0:n]) + res.Body.Close() + res, err = BOS_CLIENT.GetObject(EXISTS_BUCKET, "test-put-object", respHeaders, 2) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + t.Logf("%v", res.ContentLength) + n, _ = res.Body.Read(buf) + t.Logf("%s", buf[0:n]) + res, err = BOS_CLIENT.GetObject(EXISTS_BUCKET, "test-put-object", respHeaders, 2, 4) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + t.Logf("%v", res.ContentLength) + n, _ = res.Body.Read(buf) + t.Logf("%s", buf[0:n]) + res.Body.Close() + */ +} +func TestBasicGetObject(t *testing.T) { + res, err := BOS_CLIENT.BasicGetObject(EXISTS_BUCKET, "test-put-object") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + defer res.Body.Close() + t.Logf("%v", res.ContentLength) + for { + buf := make([]byte, 1024) + n, e := res.Body.Read(buf) + t.Logf("%s", buf[0:n]) + if e != nil { + break + } + } +} +func TestBasicGetObjectToFile(t *testing.T) { + fname := "/tmp/test-get-object" + err := BOS_CLIENT.BasicGetObjectToFile(EXISTS_BUCKET, "test-put-object", fname) + ExpectEqual(t.Errorf, err, nil) + os.Remove(fname) + fname = "/bin/test-get-object" + err = BOS_CLIENT.BasicGetObjectToFile(EXISTS_BUCKET, "test-put-object", fname) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v", err) + err = BOS_CLIENT.BasicGetObjectToFile(EXISTS_BUCKET, "not-exist-object-name", fname) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v", err) +} + +/* + func TestGetObjectMeta(t *testing.T) { + res, err := BOS_CLIENT.GetObjectMeta(EXISTS_BUCKET, "test-put-object") + ExpectEqual(t.Errorf, err, nil) + t.Logf("get object meta result: %+v", res) + } +*/ +func TestFetchObject(t *testing.T) { + args := &api.FetchObjectArgs{api.FETCH_MODE_ASYNC, api.STORAGE_CLASS_COLD} + res, err := BOS_CLIENT.FetchObject(EXISTS_BUCKET, "test-fetch-object", + "https://cloud.baidu.com/doc/BOS/API.html", args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("result: %+v", res) +} +func TestBasicFetchObject(t *testing.T) { + res, err := BOS_CLIENT.BasicFetchObject(EXISTS_BUCKET, "test-fetch-object", + "https://bj.bcebos.com/gosdk-unittest-bucket/testsumlink") + ExpectEqual(t.Errorf, err, nil) + t.Logf("result: %+v", res) + res1, err1 := BOS_CLIENT.GetObjectMeta(EXISTS_BUCKET, "test-fetch-object") + ExpectEqual(t.Errorf, err1, nil) + t.Logf("meta: %+v", res1) +} +func TestSimpleFetchObject(t *testing.T) { + res, err := BOS_CLIENT.SimpleFetchObject(EXISTS_BUCKET, "test-fetch-object", + "https://bj.bcebos.com/gosdk-unittest-bucket/testsumlink", + api.FETCH_MODE_ASYNC, api.STORAGE_CLASS_COLD) + ExpectEqual(t.Errorf, err, nil) + t.Logf("result: %+v", res) +} +func TestAppendObject(t *testing.T) { + args := &api.AppendObjectArgs{} + body, _ := bce.NewBodyFromString("aaaaaaaaaaa") + res, err := BOS_CLIENT.AppendObject(EXISTS_BUCKET, "test-append-object", body, args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestSimpleAppendObject(t *testing.T) { + body, _ := bce.NewBodyFromString("bbbbbbbbbbb") + res, err := BOS_CLIENT.SimpleAppendObject(EXISTS_BUCKET, "test-append-object", body, 11) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestSimpleAppendObjectFromString(t *testing.T) { + res, err := BOS_CLIENT.SimpleAppendObjectFromString( + EXISTS_BUCKET, "test-append-object", "123", 22) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestSimpleAppendObjectFromFile(t *testing.T) { + fname := "/tmp/test-append-file" + f, _ := os.Create(fname) + f.WriteString("12345") + f.Close() + res, err := BOS_CLIENT.SimpleAppendObjectFromFile(EXISTS_BUCKET, "test-append-object", fname, 25) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + os.Remove(fname) +} +func TestDeleteObject(t *testing.T) { + err := BOS_CLIENT.DeleteObject(EXISTS_BUCKET, "test-put-object") + ExpectEqual(t.Errorf, err, nil) +} +func TestDeleteMultipleObjectsFromString(t *testing.T) { + multiDeleteStr := `{ + "objects":[ + {"key": "aaaa"}, + {"key": "test-copy-object"}, + {"key": "test-append-object"}, + {"key": "cccc"} + ] +}` + res, err := BOS_CLIENT.DeleteMultipleObjectsFromString(EXISTS_BUCKET, multiDeleteStr) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestDeleteMultipleObjectsFromStruct(t *testing.T) { + multiDeleteObj := &api.DeleteMultipleObjectsArgs{[]api.DeleteObjectArgs{ + api.DeleteObjectArgs{"1"}, api.DeleteObjectArgs{"test-fetch-object"}}} + res, err := BOS_CLIENT.DeleteMultipleObjectsFromStruct(EXISTS_BUCKET, multiDeleteObj) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestDeleteMultipleObjectsFromKeyList(t *testing.T) { + keyList := []string{"aaaa", "test-copy-object", "test-append-object", "cccc"} + res, err := BOS_CLIENT.DeleteMultipleObjectsFromKeyList(EXISTS_BUCKET, keyList) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestInitiateMultipartUpload(t *testing.T) { + args := &api.InitiateMultipartUploadArgs{Expires: "aaaaaaa"} + res, err := BOS_CLIENT.InitiateMultipartUpload(EXISTS_BUCKET, "test-multipart-upload", "", args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + err1 := BOS_CLIENT.AbortMultipartUpload(EXISTS_BUCKET, + "test-multipart-upload", res.UploadId) + ExpectEqual(t.Errorf, err1, nil) +} +func TestBasicInitiateMultipartUpload(t *testing.T) { + res, err := BOS_CLIENT.BasicInitiateMultipartUpload(EXISTS_BUCKET, "test-multipart-upload") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + err1 := BOS_CLIENT.AbortMultipartUpload(EXISTS_BUCKET, + "test-multipart-upload", res.UploadId) + ExpectEqual(t.Errorf, err1, nil) +} +func TestUploadPart(t *testing.T) { + res, err := BOS_CLIENT.UploadPart(EXISTS_BUCKET, "a", "b", 1, nil, nil) + t.Logf("%+v, %+v", res, err) +} +func TestUploadPartCopy(t *testing.T) { + res, err := BOS_CLIENT.UploadPartCopy(EXISTS_BUCKET, "test-multipart-upload", + EXISTS_BUCKET, "test-multipart-copy", "12345", 1, nil) + t.Logf("%+v, %+v", res, err) +} +func TestBasicUploadPartCopy(t *testing.T) { + res, err := BOS_CLIENT.BasicUploadPartCopy(EXISTS_BUCKET, "test-multipart-upload", + EXISTS_BUCKET, "test-multipart-copy", "12345", 1) + t.Logf("%+v, %+v", res, err) +} +func TestListMultipartUploads(t *testing.T) { + args := &api.ListMultipartUploadsArgs{MaxUploads: 10} + res, err := BOS_CLIENT.ListMultipartUploads(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestBasicListMultipartUploads(t *testing.T) { + res, err := BOS_CLIENT.BasicListMultipartUploads(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) +} +func TestUploadSuperFile(t *testing.T) { + err := BOS_CLIENT.UploadSuperFile(EXISTS_BUCKET, "super-object", "test-object", "") + ExpectEqual(t.Errorf, err, nil) + err = BOS_CLIENT.UploadSuperFile(EXISTS_BUCKET, "not-exist", "not-exist", "") + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%+v", err) +} +func TestDownloadSuperFile(t *testing.T) { + err := BOS_CLIENT.DownloadSuperFile(EXISTS_BUCKET, "super-object", "/dev/null") + ExpectEqual(t.Errorf, err, nil) + err = BOS_CLIENT.DownloadSuperFile(EXISTS_BUCKET, "not-exist", "/tmp/not-exist") + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%+v", err) +} + +func TestGeneratePresignedUrl(t *testing.T) { + url := BOS_CLIENT.BasicGeneratePresignedUrl(EXISTS_BUCKET, EXISTS_OBJECT, 100) + resp, err := http.Get(url) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, resp.StatusCode, 200) + params := map[string]string{"responseContentType": "text"} + url = BOS_CLIENT.GeneratePresignedUrl(EXISTS_BUCKET, EXISTS_OBJECT, 1000, "HEAD", nil, params) + resp, err = http.Head(url) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, resp.StatusCode, 200) + BOS_CLIENT.Config.Endpoint = "10.180.112.31" + url = BOS_CLIENT.GeneratePresignedUrl(EXISTS_BUCKET, EXISTS_OBJECT, 100, "HEAD", nil, params) + resp, err = http.Head(url) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, resp.StatusCode, 200) + BOS_CLIENT.Config.Endpoint = "10.180.112.31:80" + url = BOS_CLIENT.GeneratePresignedUrl(EXISTS_BUCKET, EXISTS_OBJECT, 100, "HEAD", nil, params) + resp, err = http.Head(url) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, resp.StatusCode, 200) +} + +func TestGeneratePresignedUrl1(t *testing.T) { + + url := BOS_CLIENT.GeneratePresignedUrlPathStyle(EXISTS_BUCKET, EXISTS_OBJECT, 1000, "", nil, nil) + t.Logf("res:%v\n", url) +} + +func TestPutObjectAcl(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" + }], + "permission":["READ"] + } + ] +}` + body, _ := bce.NewBodyFromString(acl) + err := BOS_CLIENT.PutObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT, body) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "READ") +} +func TestPutObjectAclFromCanned(t *testing.T) { + err := BOS_CLIENT.PutObjectAclFromCanned(EXISTS_BUCKET, EXISTS_OBJECT, api.CANNED_ACL_PUBLIC_READ) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} +func TestPutObjectAclGrantRead(t *testing.T) { + err := BOS_CLIENT.PutObjectAclGrantRead(EXISTS_BUCKET, + EXISTS_OBJECT, "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "READ") +} +func TestPutObjectAclGrantFullControl(t *testing.T) { + err := BOS_CLIENT.PutObjectAclGrantFullControl(EXISTS_BUCKET, + EXISTS_OBJECT, "e13b12d0131b4c8bae959df4969387b8") + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutObjectAclFromFile(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" + }], + "permission":["READ"] + } + ] +}` + fname := "/tmp/test-put-object-acl-by-file" + f, _ := os.Create(fname) + f.WriteString(acl) + f.Close() + err := BOS_CLIENT.PutObjectAclFromFile(EXISTS_BUCKET, EXISTS_OBJECT, fname) + os.Remove(fname) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "READ") +} +func TestPutObjectAclFromString(t *testing.T) { + acl := `{ + "accessControlList":[ + { + "grantee":[{ + "id":"e13b12d0131b4c8bae959df4969387b8" + }], + "permission":["FULL_CONTROL"] + } + ] +}` + err := BOS_CLIENT.PutObjectAclFromString(EXISTS_BUCKET, EXISTS_OBJECT, acl) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "FULL_CONTROL") +} +func TestPutObjectAclFromStruct(t *testing.T) { + aclObj := &api.PutObjectAclArgs{ + []api.GrantType{ + api.GrantType{ + Grantee: []api.GranteeType{ + api.GranteeType{"e13b12d0131b4c8bae959df4969387b8"}, + }, + Permission: []string{ + "READ", + }, + }, + }, + } + err := BOS_CLIENT.PutObjectAclFromStruct(EXISTS_BUCKET, EXISTS_OBJECT, aclObj) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) + ExpectEqual(t.Errorf, res.AccessControlList[0].Permission[0], "READ") +} +func TestDeleteObjectAcl(t *testing.T) { + err := BOS_CLIENT.DeleteObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetObjectAcl(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v, %v", res, err) +} + +func TestPutBucketTrash(t *testing.T) { + args := api.PutBucketTrashReq{ + TrashDir: ".trash", + } + + err := BOS_CLIENT.PutBucketTrash(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + + res, err := BOS_CLIENT.GetBucketTrash(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, false) + t.Logf("%v, %v", res, err) +} + +func TestDeleteBucketTrash(t *testing.T) { + err := BOS_CLIENT.DeleteBucketTrash(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + + res, err := BOS_CLIENT.GetBucketTrash(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v, %v", res, err) +} + +func TestPutBucketNotification(t *testing.T) { + notificationReq := api.PutBucketNotificationReq{ + Notifications: []api.PutBucketNotificationSt{ + { + Id: "water-test-1", + Name: "water-rule-1", + AppId: "water-app-id-1", + Status: "enabled", + Resources: []string{EXISTS_BUCKET + "/path1", "/path2"}, + Events: []string{"PutObject"}, + Apps: []api.PutBucketNotificationAppsSt{ + { + Id: "app-id-1", + EventUrl: "http://xxx.com/event", + XVars: "", + }, + }, + }, + }, + } + + err := BOS_CLIENT.PutBucketNotification(EXISTS_BUCKET, notificationReq) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketNotification(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, false) + t.Logf("%v, %v", res, err) +} + +func TestDeleteBucketNotification(t *testing.T) { + err := BOS_CLIENT.DeleteBucketNotification(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + res, err := BOS_CLIENT.GetBucketNotification(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err != nil, true) + t.Logf("%v, %v", res, err) +} + +func TestParallelUpload(t *testing.T) { + res, err := BOS_CLIENT.ParallelUpload(EXISTS_BUCKET, "test_multiupload", "test_object", "", nil) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v,%v", res, err) +} + +func TestParallelCopy(t *testing.T) { + args := api.MultiCopyObjectArgs{ + StorageClass: api.STORAGE_CLASS_COLD, + } + res, err := BOS_CLIENT.ParallelCopy(EXISTS_BUCKET, "test_multiupload", EXISTS_BUCKET, "test_multiupload_copy", &args, nil) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v,%v", res, err) +} + +func TestBucketTag(t *testing.T) { + args := &api.PutBucketTagArgs{ + Tags: []api.Tag{ + { + TagKey: "key1", + TagValue: "value1", + }, + }, + } + err := BOS_CLIENT.PutBucketTag(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + _, err = BOS_CLIENT.GetBucketTag(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + err = BOS_CLIENT.DeleteBucketTag(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) +} + +func TestSymLink(t *testing.T) { + args := &api.PutSymlinkArgs{} + + err := BOS_CLIENT.PutSymlink(EXISTS_BUCKET, "test-symlink", EXISTS_OBJECT, args) + ExpectEqual(t.Errorf, err, nil) + + res, err := BOS_CLIENT.GetSymlink(EXISTS_BUCKET, EXISTS_OBJECT) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, res, "test-symlink") + t.Logf("%v, %v", res, err) +} + +func TestBucketMirror(t *testing.T) { + args := &api.PutBucketMirrorArgs{ + BucketMirroringConfiguration: []api.MirrorConfigurationRule{ + { + SourceUrl: "http://gosdk-unittest-bucket.bj.bcebos.com", + Prefix: "testprefix", + StorageClass: api.STORAGE_CLASS_STANDARD, + Mode: "fetch", + }, + }, + } + + err := BOS_CLIENT.PutBucketMirror(EXISTS_BUCKET, args) + ExpectEqual(t.Errorf, err, nil) + MirroConfig, err := BOS_CLIENT.GetBucketMirror(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, MirroConfig.BucketMirroringConfiguration[0].SourceUrl, "http://gosdk-unittest-bucket.bj.bcebos.com") + ExpectEqual(t.Errorf, MirroConfig.BucketMirroringConfiguration[0].Prefix, "testprefix") + err = BOS_CLIENT.DeleteBucketMirror(EXISTS_BUCKET) + ExpectEqual(t.Errorf, err, nil) +} diff --git a/bce-sdk-go/services/bvw/api/edit.go b/bce-sdk-go/services/bvw/api/edit.go new file mode 100644 index 0000000..d6e9abd --- /dev/null +++ b/bce-sdk-go/services/bvw/api/edit.go @@ -0,0 +1,152 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +func CreateDraft(cli bce.Client, request *CreateDraftRequest) (*MatlibTaskResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getDraftURL()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &MatlibTaskResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func GetSingleDraft(cli bce.Client, id int) (*GetDraftResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getDraftURL() + "/" + strconv.Itoa(id) + "/" + "draftAndTimeline") + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetDraftResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetDraftList(cli bce.Client, request *DraftListRequest) (*ListByPageResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getDraftURL()) + req.SetMethod(http.GET) + + paramsMap := make(map[string]string) + paramsMap["status"] = request.Status + paramsMap["beginTime"] = request.BeginTime + paramsMap["endTime"] = request.EndTime + paramsMap["pageNo"] = strconv.Itoa(request.PageNo) + paramsMap["pageSize"] = strconv.Itoa(request.PageSize) + + req.SetParams(paramsMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListByPageResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func UpdateDraft(cli bce.Client, id int, request *MatlibTaskRequest) error { + req := &bce.BceRequest{} + req.SetUri(getDraftURL() + "/" + strconv.Itoa(id) + "/" + "draftAndTimeline") + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func PollingVideoEdit(cli bce.Client, id int) (*VideoEditPollingResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getPollingVideoURL() + strconv.Itoa(id)) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &VideoEditPollingResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func CreateVideoEdit(cli bce.Client, request *VideoEditCreateRequest) (*VideoEditCreateResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getCreateVideoURL()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &VideoEditCreateResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/bvw/api/matlib.go b/bce-sdk-go/services/bvw/api/matlib.go new file mode 100644 index 0000000..c2650b4 --- /dev/null +++ b/bce-sdk-go/services/bvw/api/matlib.go @@ -0,0 +1,289 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +func UploadMaterial(client bce.Client, matlibUploadRequest *MatlibUploadRequest) (*MatlibUploadResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getUploadMaterial()) + req.SetParam("upload", "") + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(matlibUploadRequest) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := client.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &MatlibUploadResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func SearchMaterial(cli bce.Client, materialSearchRequest *MaterialSearchRequest) (*MaterialSearchResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMaterialLibrary()) + req.SetMethod(http.GET) + + paramsMap := make(map[string]string) + if materialSearchRequest.End != "" { + paramsMap["end"] = materialSearchRequest.End + } + if materialSearchRequest.Begin != "" { + paramsMap["begin"] = materialSearchRequest.Begin + } + if materialSearchRequest.InfoType != "" { + paramsMap["infoType"] = materialSearchRequest.InfoType + } + if materialSearchRequest.MediaType != "" { + paramsMap["mediaType"] = materialSearchRequest.MediaType + } + if materialSearchRequest.Status != "" { + paramsMap["status"] = materialSearchRequest.Status + } + if materialSearchRequest.SourceType != "" { + paramsMap["begin"] = materialSearchRequest.Begin + } + if materialSearchRequest.TitleKeyword != "" { + paramsMap["titleKeyword"] = materialSearchRequest.TitleKeyword + } + if materialSearchRequest.PageNo >= 1 { + paramsMap["pageNo"] = strconv.Itoa(materialSearchRequest.PageNo) + } + if materialSearchRequest.Size >= 1 { + paramsMap["size"] = strconv.Itoa(materialSearchRequest.Size) + } + + req.SetParams(paramsMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &MaterialSearchResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetMaterial(cli bce.Client, id string) (*MaterialGetResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMaterialLibrary() + "/" + id) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &MaterialGetResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func DeleteMaterial(cli bce.Client, id string) error { + req := &bce.BceRequest{} + req.SetUri(getMaterialLibrary() + "/" + id) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func UploadMaterialPreset(cli bce.Client, fileType string, request *MatlibUploadRequest) (*MaterialPresetUploadResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMateriaPresrtURL() + fileType) + req.SetParam("upload", "") + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &MaterialPresetUploadResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func SearchMaterialPreset(cli bce.Client, request *MaterialPresetSearchRequest) (*MaterialPresetSearchResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMateriaPresrtURL()) + req.SetMethod(http.GET) + + paramsMap := make(map[string]string) + + if request.MediaType != "" { + paramsMap["type"] = request.MediaType + } + if request.Status != "" { + paramsMap["status"] = request.Status + } + if request.SourceType != "" { + paramsMap["sourceType"] = request.SourceType + } + if request.PageNo != "" { + paramsMap["pageNo"] = request.PageNo + } else { + paramsMap["pageNo"] = "1" + } + if request.PageSize != "" { + paramsMap["pageSize"] = request.PageSize + } else { + paramsMap["pageSize"] = "10" + } + + req.SetParams(paramsMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &MaterialPresetSearchResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetMaterialPreset(cli bce.Client, id string) (*MaterialPresetGetResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMateriaPresrtURL() + id) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &MaterialPresetGetResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func DeleteMaterialPreset(cli bce.Client, id string) error { + req := &bce.BceRequest{} + req.SetUri(getMateriaPresrtURL() + id) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func CreateMatlibConfig(cli bce.Client, request *MatlibConfigBaseRequest) (*bce.BceResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMatlibConfigURL()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + return resp, nil +} + +func GetMatlibConfig(cli bce.Client) (*MatlibConfigGetResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMatlibConfigURL()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &MatlibConfigGetResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func UpdateMatlibConfig(cli bce.Client, request *MatlibConfigUpdateRequest) error { + req := &bce.BceRequest{} + req.SetUri(getMatlibConfigURL()) + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(request) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/bvw/api/model.go b/bce-sdk-go/services/bvw/api/model.go new file mode 100644 index 0000000..1ab31a3 --- /dev/null +++ b/bce-sdk-go/services/bvw/api/model.go @@ -0,0 +1,300 @@ +package api + +type MatlibUploadRequest struct { + MediaType string `json:"mediaType"` + Title string `json:"title"` + Bucket string `json:"bucket"` + Key string `json:"key"` + Notification string `json:"notification"` +} + +type MatlibUploadResponse struct { + Id string `json:"materialId"` +} + +type MaterialSearchRequest struct { + InfoType string `json:"infoType"` + MediaType string `json:"mediaType"` + SourceType string `json:"sourceType"` + Status string `json:"status"` + TitleKeyword string `json:"titleKeyword"` + PageNo int `json:"pageNo"` + Size int `json:"size"` + Begin string `json:"begin"` + End string `json:"end"` +} + +type MaterialSearchResponse struct { + Items []MaterialGetResponse `json:"items,omitempty"` +} + +type MaterialGetResponse struct { + ID string `json:"id"` + UserID string `json:"userId"` + InfoType string `json:"infoType,omitempty"` + MediaType string `json:"mediaType,omitempty"` + SourceType string `json:"sourceType"` + Status string `json:"status"` + Title string `json:"title"` + SourceURL string `json:"sourceUrl"` + SourceURL360P string `json:"sourceUrl360p"` + ThumbnailList []string `json:"thumbnailList"` + SubtitleUrls []string `json:"subtitleUrls"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` + Duration float64 `json:"duration"` + Height int `json:"height"` + Width int `json:"width"` + Bucket string `json:"bucket"` + Key string `json:"key"` + Key360P string `json:"key360p"` + Key720P string `json:"key720p"` + AudioKey string `json:"audioKey"` + ThumbnailKeys []string `json:"thumbnailKeys"` + Subtitles []string `json:"subtitles"` +} + +type MaterialPresetUploadResponse struct { + Id string `json:"id"` +} + +type MaterialPresetSearchRequest struct { + SourceType string `json:"sourceType"` + Status string `json:"status"` + MediaType string `json:"type"` + PageNo string `json:"pageNo"` + PageSize string `json:"pageSize"` +} + +type MaterialPresetSearchResponse struct { + Result []PresetResponseWrapper `json:"result"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type PresetResponseWrapper struct { + MaterialType string `json:"type"` + Addons []MaterialPresetGetResponse `json:"addons"` +} + +type MaterialPresetGetResponse struct { + ID string `json:"id"` + Status string `json:"status"` + UserID string `json:"userId"` + Title string `json:"title"` + Tag string `json:"tag"` + Type string `json:"type"` + SourceType string `json:"sourceType"` + PreviewMaterialIds map[string]interface{} `json:"previewMaterialIds"` + PreviewBucket map[string]interface{} `json:"previewBucket"` + PreviewKeys map[string]interface{} `json:"previewKeys"` + PreviewUrls map[string]interface{} `json:"previewUrls"` + MaterialID string `json:"materialId"` + Bucket string `json:"bucket"` + Key string `json:"key"` + SourceURL string `json:"sourceUrl"` + Config string `json:"config"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type MatlibConfigGetResponse struct { + Bucket string `json:"bucket"` +} + +type MatlibConfigBaseRequest struct { + Bucket string `json:"bucket"` +} + +type MatlibConfigUpdateRequest struct { + Bucket string `json:"bucket"` +} + +type CreateDraftRequest struct { + Titile string `json:"title"` + Ratio string `json:"ratio"` + Duration string `json:"duration"` +} + +type MatlibTaskResponse struct { + Id int `json:"id"` +} + +type Otimeline struct { + Itimeline Itimeline `json:"timeline"` + Meta interface{} `json:"meta"` +} + +type Itimeline struct { + Video []MediaPair `json:"video"` + Audio []MediaPair `json:"audio"` + Sticker []MediaPair `json:"sticker"` + Subtitle []interface{} `json:"subtitle"` +} + +type MatlibTaskRequest struct { + Title string `json:"title"` + Ratio string `json:"ratio"` + Duration string `json:"duration"` + Timeline Otimeline `json:"timeline"` + ResourceList []MaterialGetResponse `json:"resourceList"` + CoverBucekt string `json:"coverBucket"` + CoverKey string `json:"coverKey"` + LastUpdateTime string `json:"lastUpdateTime"` +} + +type GetDraftResponse struct { + Otimeline Otimeline `json:"timeline"` + ResourceList []GetMaterialResponse `json:"resourceList,omitempty"` + Title string `json:"title"` + Ratio string `json:"ratio"` + CoverBucket string `json:"coverBucket"` + CoverKey string `json:"coverKey"` + Endpoint string `json:"endpoint"` + LastUpdateTime string `json:"lastUpdateTime"` +} +type GetMaterialResponse struct { + ID string `json:"id,omitempty"` + UserID string `json:"userId,omitempty"` + ActualUserID string `json:"actualUserId,omitempty"` + SaasType string `json:"saasType,omitempty"` + InfoType string `json:"infoType,omitempty"` + MediaType string `json:"mediaType,omitempty"` + SourceType string `json:"sourceType,omitempty"` + Status string `json:"status,omitempty"` + Title string `json:"title,omitempty"` + SourceURL string `json:"sourceUrl,omitempty"` + SourceURL360P string `json:"sourceUrl360p,omitempty"` + AudioURL string `json:"audioUrl,omitempty"` + ThumbnailList []string `json:"thumbnailList,omitempty"` + SubtitleUrls []string `json:"subtitleUrls,omitempty"` + CreateTime string `json:"createTime,omitempty"` + UpdateTime string `json:"updateTime,omitempty"` + Duration float64 `json:"duration,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + FileSizeInByte int `json:"fileSizeInByte,omitempty"` + Bucket string `json:"bucket,omitempty"` + Key string `json:"key,omitempty"` + Key360P string `json:"key360p,omitempty"` + Key720P string `json:"key720p,omitempty"` + AudioKey string `json:"audioKey,omitempty"` + ThumbnailKeys []string `json:"thumbnailKeys,omitempty"` + Subtitles []string `json:"subtitles,omitempty"` + Endpoint string `json:"endpoint,omitempty"` +} + +type MediaPair struct { + Key string `json:"key"` + List []TimelineMaterial `json:"list"` + IsMaster bool `json:"isMaster"` +} + +type MediaInfo struct { + FileType string `json:"fileType,omitempty"` + SourceType string `json:"sourceType,omitempty"` + SourceURL string `json:"sourceUrl,omitempty"` + AudioURL string `json:"audioUrl,omitempty"` + Bucket string `json:"bucket,omitempty"` + Key string `json:"key,omitempty"` + AudioKey string `json:"audioKey,omitempty"` + InstanceID string `json:"instanceId,omitempty"` + CoverImage string `json:"coverImage,omitempty"` + Duration float64 `json:"duration,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Errmsg string `json:"errmsg,omitempty"` + Status string `json:"status,omitempty"` + Progress string `json:"progress,omitempty"` + Action string `json:"action,omitempty"` + Size int `json:"size,omitempty"` + Name string `json:"name,omitempty"` + ThumbnailPrefix string `json:"thumbnailPrefix,omitempty"` + ThumbnailKeys []string `json:"thumbnailKeys,omitempty"` + ThumbnailList []string `json:"thumbnailList,omitempty"` + SubtitleKeys []string `json:"subtitleKeys,omitempty"` + MediaID string `json:"mediaId,omitempty"` + Offstandard bool `json:"offstandard,omitempty"` +} + +type TimelineMaterial struct { + Start float64 `json:"start"` + End float64 `json:"end"` + ShowStart float64 `json:"showStart"` + ShowEnd float64 `json:"showEnd"` + Duration float64 `json:"duration"` + Xpos float64 `json:"xpos"` + Ypos float64 `json:"ypos"` + Width float64 `json:"width"` + Height float64 `json:"height"` + MediaInfo MediaInfo `json:"mediaInfo"` + Type string `json:"type"` + UID string `json:"uid"` + Name string `json:"name"` + ExtInfo interface{} `json:"extInfo"` +} + +type ListByPageResponse struct { + Data []MatlibTaskGetResponse `json:"data"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type MatlibTaskGetResponse struct { + ID int `json:"id"` + ResMaterialID string `json:"resMaterialId"` + UserID string `json:"userId"` + Title string `json:"title"` + Status string `json:"status"` + ErrorMessage string `json:"errorMessage"` + CoverURL string `json:"coverUrl"` + LastUpdateTime string `json:"lastUpdateTime"` +} + +type DraftListRequest struct { + Status string `json:"status"` + BeginTime string `json:"beginTime"` + EndTime string `json:"endTime"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type VideoEditPollingResponse struct { + Errorno int `json:"errorno"` + Errmsg string `json:"errmsg"` + Data Data `json:"data"` +} + +type Data struct { + EditID int `json:"editId"` + EditStatus string `json:"editStatus"` + Bucket string `json:"bucket"` + Key string `json:"key"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type VideoEditCreateRequest struct { + Title string `json:"title"` + Bucket string `json:"bucket"` + KeyPath string `json:"keyPath"` + TaskId string `json:"taskId"` + Notification string `json:"notification"` + ExtInfo interface{} `json:"extInfo"` + Cmd interface{} `json:"cmd"` + Endpoint string `json:"endpoint"` +} + +type VideoEditCreateResponse struct { + Errorno int `json:"errorno"` + Errmsg string `json:"errmsg"` + Data string `json:"data"` + Ret Ret `json:"ret"` + Ie string `json:"ie"` +} +type Ret struct { + EditID int `json:"editId"` + BosKey string `json:"bosKey"` +} diff --git a/bce-sdk-go/services/bvw/api/utils.go b/bce-sdk-go/services/bvw/api/utils.go new file mode 100644 index 0000000..989b9fc --- /dev/null +++ b/bce-sdk-go/services/bvw/api/utils.go @@ -0,0 +1,41 @@ +package api + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + BVW_PREFIX = bce.URI_PREFIX + "v1/" + MATERIALIBRARY = "materialLibrary" + MATLIB = "matlib" + PRESET = "/preset/" + CONFIG = "/config" + POLLINGVIDEO = "videoEdit/pollingVideo/" + CREATEVIDEO = "videoEdit/createNewVideo" +) + +func getMaterialLibrary() string { + return BVW_PREFIX + MATERIALIBRARY +} + +func getUploadMaterial() string { + return BVW_PREFIX + MATLIB +} + +func getMateriaPresrtURL() string { + return BVW_PREFIX + MATERIALIBRARY + PRESET +} + +func getMatlibConfigURL() string { + return BVW_PREFIX + MATLIB + CONFIG +} + +func getDraftURL() string { + return BVW_PREFIX + MATLIB +} + +func getPollingVideoURL() string { + return BVW_PREFIX + POLLINGVIDEO +} + +func getCreateVideoURL() string { + return BVW_PREFIX + CREATEVIDEO +} diff --git a/bce-sdk-go/services/bvw/client.go b/bce-sdk-go/services/bvw/client.go new file mode 100644 index 0000000..bd164fd --- /dev/null +++ b/bce-sdk-go/services/bvw/client.go @@ -0,0 +1,126 @@ +package bvw + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/bvw/api" +) + +type Client struct { + *bce.BceClient +} + +const DEFAULT_SERVICE_DOMAIN = "bvw.bj.baidubce.com" + +// NewClient make BVW service client with defualt configuration +// endPoint value only bj can be chosed and enPoint value defualt bj +func NewClient(ak, sk, endPoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endPoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// upload video /audio/picture to MaterialLibaray +func (cli *Client) UploadMaterial(request *api.MatlibUploadRequest) (*api.MatlibUploadResponse, error) { + return api.UploadMaterial(cli, request) +} + +// search material +func (cli *Client) SearchMaterial(materialSearchRequest *api.MaterialSearchRequest) (*api.MaterialSearchResponse, error) { + return api.SearchMaterial(cli, materialSearchRequest) +} + +// get material by materialId +func (cli *Client) GetMaterial(id string) (*api.MaterialGetResponse, error) { + return api.GetMaterial(cli, id) +} + +// delete material by materialId +func (cli *Client) DeleteMaterial(id string) error { + return api.DeleteMaterial(cli, id) +} + +// upload preset materials to the media library +func (cli *Client) UploadMaterialPreset(fileType string, request *api.MatlibUploadRequest) ( + *api.MaterialPresetUploadResponse, error) { + return api.UploadMaterialPreset(cli, fileType, request) +} + +// search preset materials +func (cli Client) SearchMaterialPreset(request *api.MaterialPresetSearchRequest) (*api.MaterialPresetSearchResponse, error) { + return api.SearchMaterialPreset(cli, request) +} + +// get preset matertials by id +func (cli Client) GetMaterialPreset(id string) (*api.MaterialPresetGetResponse, error) { + return api.GetMaterialPreset(cli, id) +} + +// delete preset matertials by id +func (cli *Client) DeleteMaterialPreset(id string) error { + return api.DeleteMaterialPreset(cli, id) +} + +// create matlib config +func (cli *Client) CreateMatlibConfig(request *api.MatlibConfigBaseRequest) (*bce.BceResponse, error) { + return api.CreateMatlibConfig(cli, request) +} + +// get matlib config +func (cli *Client) GetMatlibConfig() (*api.MatlibConfigGetResponse, error) { + return api.GetMatlibConfig(cli) +} + +// update matlib config +func (cli *Client) UpdateMatlibConfig(request *api.MatlibConfigUpdateRequest) error { + return api.UpdateMatlibConfig(cli, request) +} + +// create edit draft +func (cli *Client) CreateDraft(request *api.CreateDraftRequest) (*api.MatlibTaskResponse, error) { + return api.CreateDraft(cli, request) +} + +// get draft and timeline with matlib task id +func (cli *Client) GetSingleDraft(id int) (*api.GetDraftResponse, error) { + return api.GetSingleDraft(cli, id) +} + +// get draft list +func (cli *Client) GetDraftList(request *api.DraftListRequest) (*api.ListByPageResponse, error) { + return api.GetDraftList(cli, request) +} + +// update draft by id +func (cli *Client) UpdateDraft(id int, request *api.MatlibTaskRequest) error { + return api.UpdateDraft(cli, id, request) +} + +// query video edit job status +func (cli *Client) PollingVideoEdit(id int) (*api.VideoEditPollingResponse, error) { + return api.PollingVideoEdit(cli, id) +} + +// create edit job +func (cli *Client) CreateVideoEdit(request *api.VideoEditCreateRequest) (*api.VideoEditCreateResponse, error) { + return api.CreateVideoEdit(cli, request) +} diff --git a/bce-sdk-go/services/bvw/client_test.go b/bce-sdk-go/services/bvw/client_test.go new file mode 100644 index 0000000..7f05ee7 --- /dev/null +++ b/bce-sdk-go/services/bvw/client_test.go @@ -0,0 +1,362 @@ +package bvw + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/services/bvw/api" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + BVW_CLIENT *Client +) + +type Conf struct { + AK string + SK string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + BVW_CLIENT, _ = NewClient(confObj.AK, confObj.SK, "") + log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestUploadMaterial(t *testing.T) { + req := &api.MatlibUploadRequest{} + req.Key = "dog.mp4" + req.Bucket = "bvw-console" + req.Title = "split_result_267392.mp4" + req.MediaType = "video" + uploadResponse, err := BVW_CLIENT.UploadMaterial(req) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", uploadResponse) +} + +func TestSearchMaterial(t *testing.T) { + req := &api.MaterialSearchRequest{} + req.Size = 5 + req.PageNo = 1 + req.Status = "FINISHED" + req.MediaType = "video" + req.Begin = "2023-01-11T16:00:00Z" + req.End = "2023-04-12T15:59:59Z" + materialResp, err := BVW_CLIENT.SearchMaterial(req) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", materialResp) +} + +func TestGetMaterial(t *testing.T) { + materialGetResponse, err := BVW_CLIENT.GetMaterial("d9b9f08ef1e0a28967fa0f7b5819db30") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", materialGetResponse) +} + +func TestDeleteMaterial(t *testing.T) { + err := BVW_CLIENT.DeleteMaterial("d9b9f08ef1e0a28967fa0f7b5819db30") + ExpectEqual(t.Errorf, err, nil) +} + +func TestUploadMaterialPreset(t *testing.T) { + req := &api.MatlibUploadRequest{} + req.Key = "item2.jpeg" + req.Bucket = "bvw-console" + req.Title = "item2.jpeg" + req.MediaType = "image" + uploadResponse, err := BVW_CLIENT.UploadMaterialPreset("PICTURE", req) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", uploadResponse) +} + +func TestSearchMaterialPreset(t *testing.T) { + req := &api.MaterialPresetSearchRequest{} + req.PageSize = "10" + req.Status = "FINISHED" + req.PageNo = "1" + req.SourceType = "USER" + req.MediaType = "PICTURE" + response, err := BVW_CLIENT.SearchMaterialPreset(req) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestGetMaterialPreset(t *testing.T) { + response, err := BVW_CLIENT.GetMaterialPreset("cc0aabdc71421abaa17e80a26caa009f") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestDeleteMaterialPreset(t *testing.T) { + err := BVW_CLIENT.DeleteMaterialPreset("cc0aabdc71421abaa17e80a26caa009f") + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateMatlibConfig(t *testing.T) { + request := &api.MatlibConfigBaseRequest{ + Bucket: "go-test"} + response, err := BVW_CLIENT.CreateMatlibConfig(request) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestGetMatlibConfig(t *testing.T) { + response, err := BVW_CLIENT.GetMatlibConfig() + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestUpdateMatlibConfig(t *testing.T) { + err := BVW_CLIENT.UpdateMatlibConfig(&api.MatlibConfigUpdateRequest{Bucket: "go-test"}) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateDraft(t *testing.T) { + response, err := BVW_CLIENT.CreateDraft(&api.CreateDraftRequest{ + Duration: "0", + Titile: "testCreateDraft3", + Ratio: "hori16x9"}) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestGetSingleDraft(t *testing.T) { + response, err := BVW_CLIENT.GetSingleDraft(1017834) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestGetDraftList(t *testing.T) { + response, err := BVW_CLIENT.GetDraftList(&api.DraftListRequest{ + PageNo: 1, + PageSize: 20, + BeginTime: "2023-01-11T16:00:00Z", + EndTime: "2023-04-12T15:59:59Z"}) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestUpdateDraft(t *testing.T) { + var request api.MatlibTaskRequest + jsonStr := "{\"title\":\"updatesucess\",\"draftId\":\"1017890\",\"timeline\":{\"timeline\":{\"video\":[{\"key\"" + + ":\"name\",\"isMaster\":true,\"list\":[{\"type\":\"video\",\"start\":0,\"end\":5.859375,\"showStart\":0," + + "\"showEnd\":5.859375,\"xpos\":0,\"ypos\":0,\"width\":1280,\"height\":720,\"duration\":5.859375,\"extInfo" + + "\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0,\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1" + + ",\"transitionStart\":\"\",\"transitionEnd\":\"black\",\"transitionDuration\":1,\"volume\":1,\"rotate\":0," + + "\"mirror\":\"\",\"blankArea\":\"\"},\"mediaInfo\":{\"fileType\":\"video\",\"sourceType\":\"USER\",\"" + + "sourceUrl\":\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-token=" + + "ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7x" + + "G%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hb" + + "SJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRp" + + "OC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ" + + "%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2" + + "F%2Fb227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\":\"" + + "https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjY" + + "yYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ" + + "5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3" + + "OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzT" + + "Cgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=" + + "bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1154f" + + "f0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\",\"audioKey\":" + + "\"audio/dog.mp3\",\"coverImage\":\"https://bj.bcebos.com/v1/bvw-console/thumbnail/dog00000500.jpg" + + "?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZp" + + "Ppsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQz" + + "FlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6P" + + "z36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQj" + + "Wm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cb" + + "c0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f9028fb85e00ff5dc7df6" + + "daf\",\"duration\":18.73,\"width\":1920,\"height\":1080,\"status\":\"FINISHED\",\"name\":\"dog.mp4\"," + + "\"thumbnailPrefix\":\"\",\"thumbnailKeys\":[\"thumbnail/dog00000500.jpg\"],\"mediaId\":\"" + + "1f10ce0db10b8eb5b2f2755daf544900\",\"offstandard\":false},\"uid\":\"a081f1c6-9dc9-4e7b-a00e-6eb5217e771d" + + "\"},{\"type\":\"video\",\"start\":5.859375,\"end\":18.73,\"showStart\":5.859375,\"showEnd\":18.73,\"xpos" + + "\":0,\"ypos\":0,\"width\":1280,\"height\":720,\"duration\":12.870625,\"extInfo\":{\"style\":\"\",\"lightness" + + "\":0,\"gifMode\":0,\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"black\",\"" + + "transitionEnd\":\"\",\"transitionDuration\":1,\"volume\":1,\"rotate\":0,\"mirror\":\"\",\"blankArea\":\"\"}," + + "\"mediaInfo\":{\"fileType\":\"video\",\"sourceType\":\"USER\",\"sourceUrl\":" + + "\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyN" + + "jk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9" + + "RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7D" + + "GO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df" + + "5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%" + + "3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2Fb" + + "227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\":" + + "\"https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjY" + + "yYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5" + + "Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3O" + + "HDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCg" + + "h8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=" + + "bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1" + + "154ff0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\",\"" + + "audioKey\":\"audio/dog.mp3\",\"coverImage\":\"https://bj.bcebos.com/v1/bvw-console/thumbnail/dog00000500" + + ".jpg?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZp" + + "Ppsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQz" + + "FlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz" + + "36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm" + + "231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0" + + "fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f9028fb85e00ff5dc7df6da" + + "f\",\"duration\":18.73,\"width\":1920,\"height\":1080,\"status\":\"FINISHED\",\"name\":\"dog.mp4\",\"thumb" + + "nailPrefix\":\"\",\"thumbnailKeys\":[\"thumbnail/dog00000500.jpg\"],\"mediaId\":\"1f10ce0db10b8eb5b2f2755d" + + "af544900\",\"offstandard\":false},\"uid\":\"70af482e-0bf5-4c38-8f05-03c9e3b8ae03\"}],\"unlinkMaster\":true" + + "}],\"audio\":[{\"key\":\"\",\"isMaster\":false,\"list\":[{\"start\":0,\"end\":155.99,\"showStart\":" + + "0.234375,\"showEnd\":156.224375,\"duration\":155.99,\"xpos\":0,\"ypos\":0,\"hidden\":false,\"mediaInfo\"" + + ":{\"fileType\":\"audio\",\"sourceUrl\":\"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset" + + "/music/audio/%E5%8F%A4%E9%A3%8E%E9%A3%98%E6%89%AC.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772" + + "a1409590%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F2ddcf78c92de29ae3d7c3166a4e17e7c5d07fa38dcefd24c29c4f4d5b" + + "5ba46fe\",\"audioUrl\":\"https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/music/audio/" + + "%E5%8F%A4%E9%A3%8E%E9%A3%98%E6%89%AC.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2" + + "023-04-14T07%3A16%3A35Z%2F86400%2F%2F2ddcf78c92de29ae3d7c3166a4e17e7c5d07fa38dcefd24c29c4f4d5b5ba46fe\"," + + "\"bucket\":\"videoworks-system-preprocess\",\"key\":\"systemPreset/music/古风飘扬.aac\",\"audioKey\":\"" + + "systemPreset/music/audio/古风飘扬.mp3\",\"coverImage\":\"\",\"duration\":155.99,\"name\":\"\",\"thumbnailList" + + "\":[],\"mediaId\":\"\",\"offstandard\":false},\"type\":\"audio\",\"uid\":\"bd52be7f-1f19-4368-8c41-44e991af" + + "8164\",\"name\":\"古风飘扬\",\"extInfo\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0,\"contrast\":0,\"" + + "saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"\",\"transitionDuration" + + "\":1,\"volume\":1,\"rotate\":0},\"boxDataLeft\":4,\"dragBoxWidth\":1996.6720000000003,\"lineType\":\"audio" + + "\"}]}],\"subtitle\":[{\"key\":\"\",\"list\":[{\"duration\":3,\"hidden\":false,\"name\":\"time-place\",\"" + + "tagExtInfo\":{\"marginBottom\":0,\"textFadeIn\":1,\"textFadeOut\":1,\"textOutMaskDur\":1},\"showStart\":" + + "5.859375,\"showEnd\":8.859,\"templateId\":\"6764ce3331ea7e406e4ab4475d1dff18\",\"type\":\"subtitle\",\"uid" + + "\":\"5aaa35f4-8fae-4b8c-b7ed-761a54550244\",\"xpos\":\"0\",\"ypos\":\"309\",\"config\":[{\"alpha\":0,\"" + + "fontColor\":\"#ffffff\",\"fontSize\":50,\"fontStyle\":\"normal\",\"fontType\":\"方正时代宋 简 Extrabold\"" + + ",\"lineHeight\":1.2,\"name\":\"时间\",\"text\":\"haha\",\"backgroundColor\":\"#2468F2\",\"backgroundAlpha" + + "\":0,\"fontx\":0,\"fonty\":0,\"invisible\":false},{\"alpha\":0,\"fontColor\":\"#000000\",\"fontSize\":50," + + "\"fontStyle\":\"normal\",\"fontType\":\"方正时代宋 简 Extrabold\",\"lineHeight\":1.2,\"name\":\"地点\",\"" + + "text\":\"cd\",\"backgroundColor\":\"#ffffff\",\"backgroundAlpha\":0,\"fontx\":0,\"fonty\":0,\"invisible\"" + + ":false}],\"boxDataLeft\":76,\"dragBoxWidth\":38.400000000000006,\"lineType\":\"subtitle\"}],\"master\":" + + "false}],\"sticker\":[{\"key\":\"\",\"isMaster\":false,\"list\":[{\"showStart\":0,\"showEnd\":3,\"duration\"" + + ":3,\"xpos\":0,\"ypos\":0,\"width\":215,\"height\":120.9375,\"hidden\":false,\"mediaInfo\":{\"sourceUrl\":\"" + + "https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/picture/%E9%9D%A2%E5%8C%85%E3%80%81%E8" + + "%82%A0.png?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2023-04-14T07%3A16%3A35Z%2F8640" + + "0%2F%2F84867e4874cc94eb374898017cb4367ed8c24a5750a9d8ebd14d8ca989cf2e53\",\"audioUrl\":\"" + + "https://bj.bcebos.com/v1/videoworks-system-preprocess/systemPreset/picture/audio/%E9%9D%A2%E5%8C%85%E3%80%" + + "81%E8%82%A0.mp3?authorization=bce-auth-v1%2F66c557960e7a4822bd82c772a1409590%2F2023-04-14T07%3A16%3A35Z%2F" + + "86400%2F%2F1807d3005f5f8fc270fa17238ec550c20162252c19952e6a45ae497ef7148086\",\"bucket\":\"" + + "videoworks-system-preprocess\",\"key\":\"systemPreset/picture/面包、肠.png\",\"audioKey\":\"systemPreset" + + "/picture/audio/面包、肠.mp3\",\"coverImage\":\"\",\"width\":215,\"height\":120,\"name\":\"\",\"thumbnailList" + + "\":[],\"mediaId\":\"\",\"offstandard\":false},\"type\":\"image\",\"uid\":\"e419b583-aedb-4265-" + + "9a67-7e21fc621f85\",\"name\":\"面包、肠\",\"extInfo\":{\"style\":\"\",\"lightness\":0,\"gifMode\":0," + + "\"contrast\":0,\"saturation\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"\"," + + "\"transitionDuration\":1,\"volume\":1,\"rotate\":0},\"lineType\":\"sticker\",\"boxDataLeft\":1,\"" + + "dragBoxWidth\":38.400000000000006}]}]}},\"ratio\":\"hori16x9\",\"resourceList\":[{\"id\":\"1f10ce0db10" + + "b8eb5b2f2755daf544900\",\"userId\":\"e7e47aa53fbb47dfb1e4c86424bb7ad3\",\"mediaType\":\"video\",\"" + + "sourceType\":\"USER\",\"status\":\"FINISHED\",\"title\":\"dog.mp4\",\"sourceUrl\":\"https://bj.bcebos.com" + + "/v1/bvw-console/dog.mp4?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM" + + "1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Z" + + "zgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkh" + + "MLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7" + + "D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1" + + "a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F4a3c399db912e35f6b6c008faffebe5752c47e36fcd21d4bf03" + + "bc908c3a29e5e\",\"sourceUrl360p\":\"https://bj.bcebos.com/v1/bvw-console/360p/dog.mp4?x-bce-security-toke" + + "n=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZT" + + "NAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwb" + + "rCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F" + + "9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujH" + + "KfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07" + + "%3A16%3A35Z%2F86400%2F%2Fb227edbf73344bdfc9fed00ba491c5c0c3abe229792d7b3d026604cfbe541b68\",\"audioUrl\"" + + ":\"https://bj.bcebos.com/v1/bvw-console/audio/dog.mp3?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1Z" + + "jYyYjlkZjNjODB8AAAAAM0BAABgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8" + + "sJ5Dc75AlLyVko5U4CFsiaEE9xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46Nx" + + "S3OHDvxtr95gIpW592MxArSISjn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOF" + + "zTCgh8qeej6RXfYjBKn0pvmWCKr%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorizatio" + + "n=bce-auth-v1%2F4a2cac88da9411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F3dcc823c9497aca1" + + "154ff0007eca86af4e682363c3ceddba0b3c74ca14e2d154\",\"thumbnailList\":[\"https://bj.bcebos.com/v1/bvw-cons" + + "ole/thumbnail/dog00000500.jpg?x-bce-security-token=ZjkyZmQ2YmQxZTQ3NDcyNjk0ZTg1ZjYyYjlkZjNjODB8AAAAAM0BAA" + + "BgAa0YM1kG5uQI39UZkqCZpPpsi8DEL63qLoYtl2x5OFqZTNAWS7xG%2FfhP%2BlWF9RNJhYFABpfrg8sJ5Dc75AlLyVko5U4CFsiaEE9" + + "xGdGQU4r3Zzgl1fJothQzFlDKfhH9hh9NXykFPkd4OXwbrCmrl902hbSJu8e6Q7DGO0tOi444b9K46NxS3OHDvxtr95gIpW592MxArSIS" + + "jn%2FpMVkhMLtymxh6Pz36iVdo0ErJnD1JIozvKo%2F9bV7pIjpIAysjRpOC8Df5Mh5cSG96BBwftUOFzTCgh8qeej6RXfYjBKn0pvmWCK" + + "r%2BM6bV7D39wKiQjWm231giBr3teGDbG%2BfujHKfC4tNAYpzSrCwEFCyCQ%3D%3D&authorization=bce-auth-v1%2F4a2cac88da9" + + "411edaf1a4f67d1cbc0fc%2F2023-04-14T07%3A16%3A35Z%2F86400%2F%2F21a92744dfb9fc3f46745e75d095da327bb04677f902" + + "8fb85e00ff5dc7df6daf\"],\"subtitleUrls\":[],\"createTime\":\"2023-04-11 16:55:32\",\"updateTime\":\"2023-0" + + "4-11 16:55:43\",\"duration\":18.73,\"height\":1080,\"width\":1920,\"fileSizeInByte\":8948434,\"thumbnailK" + + "eys\":[\"thumbnail/dog00000500.jpg\"],\"subtitles\":[\"\"],\"bucket\":\"bvw-console\",\"key\":\"dog.mp4\"" + + ",\"key360p\":\"360p/dog.mp4\",\"key720p\":\"720p/dog.mp4\",\"audioKey\":\"audio/dog.mp3\",\"used\":true}]" + + ",\"coverBucket\":\"bvw-console\",\"coverKey\":\"thumbnail/dog00000500.jpg\"}" + jsonErr := json.Unmarshal([]byte(jsonStr), &request) + ExpectEqual(t.Errorf, jsonErr, nil) + t.Logf("%+v", &request) + err := BVW_CLIENT.UpdateDraft(1017890, &request) + ExpectEqual(t.Errorf, err, nil) +} + +func TestPollingVideoEdit(t *testing.T) { + response, err := BVW_CLIENT.PollingVideoEdit(267539) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestCreateVideoEdit(t *testing.T) { + var request api.VideoEditCreateRequest + jsonStr := "{\"title\":\"新建作品-202304141603\",\"taskId\":\"1017895\",\"bucket\":\"vwdemo\",\"cmd\":{\"timeline" + + "\":{\"video\":[{\"list\":[{\"type\":\"video\",\"start\":0,\"end\":7.65625,\"showStart\":0,\"showEnd\":" + + "7.65625,\"xpos\":0,\"ypos\":0,\"width\":1,\"height\":1,\"duration\":7.65625,\"extInfo\":{\"style\":\"\"" + + ",\"lightness\":0,\"contrast\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"\",\"transitionEnd\":\"black\"" + + ",\"transitionDuration\":1,\"mirror\":\"\",\"rotate\":0,\"blankArea\":\"\",\"volume\":1},\"mediaInfo\":{\"" + + "mediaId\":\"1f10ce0db10b8eb5b2f2755daf544900\",\"key\":\"dog.mp4\",\"bucket\":\"bvw-console\",\"fileType\"" + + ":\"video\",\"width\":1920,\"height\":1080}},{\"type\":\"video\",\"start\":7.65625,\"end\":18.73,\"showStart" + + "\":7.65625,\"showEnd\":18.73,\"xpos\":0,\"ypos\":0,\"width\":1,\"height\":1,\"duration\":11.07375,\"" + + "extInfo\":{\"style\":\"\",\"lightness\":0,\"contrast\":0,\"hue\":0,\"speed\":1,\"transitionStart\":\"black\"" + + ",\"transitionEnd\":\"\",\"transitionDuration\":1,\"mirror\":\"\",\"rotate\":0,\"blankArea\":\"\",\"volume\":" + + "1},\"mediaInfo\":{\"mediaId\":\"1f10ce0db10b8eb5b2f2755daf544900\",\"key\":\"dog.mp4\",\"bucket\":\"" + + "bvw-console\",\"fileType\":\"video\",\"width\":1920,\"height\":1080}}]}],\"audio\":[{\"list\":[{\"name\":" + + "\"古风飘扬\",\"start\":0,\"end\":155.99,\"duration\":155.99,\"showStart\":0.078125,\"showEnd\":156.068125,\"" + + "uid\":\"cc8c1ecc-fcd3-493d-be5b-8cce8c15ed15\",\"extInfo\":{\"volume\":\"1.0\",\"transitions\":[]},\"" + + "mediaInfo\":{\"fileType\":\"audio\",\"key\":\"systemPreset/music/古风飘扬.aac\",\"bucket\":\"videoworks-" + + "system-preprocess\",\"name\":\"古风飘扬\"}}]}],\"subtitle\":[{\"list\":[{\"templateId\":\"6764ce3331ea7e406e" + + "4ab4475d1dff18\",\"showStart\":0,\"showEnd\":3,\"duration\":3,\"uid\":\"05e59686-b23e-4b33-96d7-040eab63" + + "85b6\",\"tag\":\"time-place\",\"xpos\":0,\"ypos\":0.431,\"config\":[{\"text\":\"时间\",\"fontSize\":50,\"" + + "fontType\":\"方正时代宋 简 Extrabold\",\"fontColor\":\"#ffffff\",\"alpha\":0,\"fontStyle\":\"normal\",\"" + + "backgroundColor\":\"#2468F2\",\"backgroundAlpha\":0,\"fontx\":0.039,\"fonty\":0.028,\"rectx\":0,\"recty\"" + + ":0.431,\"rectWidth\":0.156,\"rectHeight\":0.139},{\"text\":\"地点\",\"fontSize\":50,\"fontType\":\"" + + "方正时代宋 简 Extrabold\",\"fontColor\":\"#000000\",\"alpha\":0,\"fontStyle\":\"normal\",\"backgroundColor" + + "\":\"#ffffff\",\"backgroundAlpha\":0,\"fontx\":0.039,\"fonty\":0.028,\"rectx\":0.156,\"recty\":0.431,\"" + + "rectWidth\":0.156,\"rectHeight\":0.139}],\"tagExtInfo\":{\"glExtInfo\":{}}}]}],\"sticker\":[{\"list\":" + + "[{\"type\":\"image\",\"showStart\":0,\"showEnd\":3,\"duration\":3,\"xpos\":0,\"ypos\":0,\"width\":0.168" + + ",\"height\":0.168,\"extInfo\":{},\"mediaInfo\":{\"key\":\"systemPreset/picture/面包、肠.png\",\"bucket\":" + + "\"videoworks-system-preprocess\",\"width\":215,\"height\":120.9375,\"fileType\":\"image\"}}]}]}},\"extInfo" + + "\":{\"aspect\":\"hori16x9\",\"resolutionRatio\":\"v720p\"}}" + jsonErr := json.Unmarshal([]byte(jsonStr), &request) + ExpectEqual(t.Errorf, jsonErr, nil) + t.Logf("%+v", &request) + response, err := BVW_CLIENT.CreateVideoEdit(&request) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} diff --git a/bce-sdk-go/services/cce/cce.go b/bce-sdk-go/services/cce/cce.go new file mode 100644 index 0000000..5830b5a --- /dev/null +++ b/bce-sdk-go/services/cce/cce.go @@ -0,0 +1,335 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// cce.go - the cce APIs definition supported by the CCE service + +package cce + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateCluster - create an CCE Cluster with the specific parameters +// +// PARAMS: +// - args: the arguments to create a cce cluster +// +// RETURNS: +// - *CreateClusterResult: the result of create cluster, contains new Cluster's uuid and order id +func (c *Client) CreateCluster(args *CreateClusterArgs) (*CreateClusterResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create cluster argments") + } + + result := &CreateClusterResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterUri()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListClusters - list CCE Clusters with the specific parameters +// +// PARAMS: +// - args: the arguments to list cce cluster +// +// RETURNS: +// - *ListClusterResult: the result of list cluster +func (c *Client) ListClusters(args *ListClusterArgs) (*ListClusterResult, error) { + if args == nil { + args = &ListClusterArgs{} + } + + maxKeysStr := "" + if args.MaxKeys != 0 { + maxKeysStr = strconv.Itoa(args.MaxKeys) + } + + result := &ListClusterResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterUri()). + WithQueryParamFilter("maker", args.Marker). + WithQueryParamFilter("maxKeys", maxKeysStr). + WithQueryParamFilter("status", string(args.Status)). + WithResult(result). + Do() + + return result, err +} + +// GetCluster - get a CCE Cluster with the specific cluster uuid +// +// PARAMS: +// - args: the specific cluster uuid +// +// RETURNS: +// - *GetClusterResult: the detail information about the CCE Cluster +func (c *Client) GetCluster(clusterUuid string) (*GetClusterResult, error) { + result := &GetClusterResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterUriWithId(clusterUuid)). + WithResult(result). + Do() + + return result, err +} + +// DeleteCluster - delete a CCE Cluster +// +// PARAMS: +// - args: the arguments to delete a cce cluster +func (c *Client) DeleteCluster(args *DeleteClusterArgs) error { + if args == nil || args.ClusterUuid == "" { + return fmt.Errorf("please set delete cluster uuid") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getClusterUriWithId(args.ClusterUuid)). + WithQueryParamFilter("deleteEipCds", strconv.FormatBool(args.DeleteEipCds)). + WithQueryParamFilter("deleteSnap", strconv.FormatBool(args.DeleteSnap)). + Do() +} + +// ScalingUp - scaling up a CCE Cluster +// +// PARAMS: +// - args: the arguments to create a cce cluster +// +// RETURNS: +// - *ScalingUpResult: the result of scaling up cluster, contains new Cluster's uuid and order id +func (c *Client) ScalingUp(args *ScalingUpArgs) (*ScalingUpResult, error) { + if args == nil { + return nil, fmt.Errorf("please set scaling up cluster argments") + } + + if args.ClusterUuid == "" { + return nil, fmt.Errorf("please set scaling up clusterUuid") + } + + result := &ScalingUpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterUri()). + WithQueryParam("scalingUp", ""). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ScalingDown - scaling down a CCE Cluster +// +// PARAMS: +// - args: the arguments to scaling down a cce cluster +func (c *Client) ScalingDown(args *ScalingDownArgs) error { + if args == nil { + return fmt.Errorf("please set scaling down cluster argments") + } + + if args.ClusterUuid == "" { + return fmt.Errorf("please set scaling down clusterUuid") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterUri()). + WithQueryParam("scalingDown", ""). + WithQueryParamFilter("deleteEipCds", strconv.FormatBool(args.DeleteEipCds)). + WithQueryParamFilter("deleteSnap", strconv.FormatBool(args.DeleteSnap)). + WithBody(args). + Do() + + return err +} + +// ListNodes - list all nodes in CCE Cluster +// +// PARAMS: +// - args: the arguments to list all nodes +// +// RETURNS: +// - *ListNodeResult: the result of list nodes, contains a Cluster's nodes +func (c *Client) ListNodes(args *ListNodeArgs) (*ListNodeResult, error) { + if args == nil { + return nil, fmt.Errorf("please set list node argments") + } + + if args.ClusterUuid == "" { + return nil, fmt.Errorf("please set cluster uuid for list nodes") + } + + maxKeysStr := "" + if args.MaxKeys != 0 { + maxKeysStr = strconv.Itoa(args.MaxKeys) + } + + result := &ListNodeResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getNodeUri()). + WithQueryParamFilter("maker", args.Marker). + WithQueryParamFilter("maxKeys", maxKeysStr). + WithQueryParamFilter("clusterUuid", args.ClusterUuid). + WithResult(result). + Do() + + return result, err +} + +// ShiftInNode - shift nodes into cluster +// +// PARAMS: +// - args: the arguments about shift nodes into cce cluster +func (c *Client) ShiftInNode(args *ShiftInNodeArgs) error { + if args == nil { + return fmt.Errorf("please set shift in argments") + } + + if args.ClusterUuid == "" { + return fmt.Errorf("please set shift in cluster uuid") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterExisteNodeUri()). + WithQueryParamFilter("action", "shift_in"). + WithBody(args). + Do() +} + +// ShiftOutNode - shift nodes out from CCE Cluster +// +// PARAMS: +// - args: the arguments about shift nodes out from cce cluster +func (c *Client) ShiftOutNode(args *ShiftOutNodeArgs) error { + if args == nil { + return fmt.Errorf("please set shift out argments") + } + + if args.ClusterUuid == "" { + return fmt.Errorf("please set shift out cluster uuid") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterExisteNodeUri()). + WithQueryParamFilter("action", "shift_out"). + WithBody(args). + Do() +} + +// ListExistedBccNode - list all bcc nodes which can shifted into CCE cluster +// +// PARAMS: +// - args: the arguments to list bcc nodes +// +// RETURNS: +// - *ListExistedNodeResult: the result of list nodes +func (c *Client) ListExistedBccNode(args *ListExistedNodeArgs) (*ListExistedNodeResult, error) { + if args == nil { + return nil, fmt.Errorf("please set list existed bcc node argments") + } + + if args.ClusterUuid == "" { + return nil, fmt.Errorf("please set list existed bcc node cluster uuid") + } + + result := &ListExistedNodeResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterExisteNodeListUri()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetContainerNet - get container net in vpc +// +// PARAMS: +// - args: the arguments to get args +// +// RETURNS: +// - *GetContainerNetResult: the result of container net +func (c *Client) GetContainerNet(args *GetContainerNetArgs) (*GetContainerNetResult, error) { + if args == nil { + return nil, fmt.Errorf("please set container net argments") + } + + result := &GetContainerNetResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterContainerNetUri()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetKubeConfig - get config file of CCE Cluster +// +// PARAMS: +// - args: the arguments to get config file of a cce cluster +// +// RETURNS: +// - *GetKubeConfigResult: the kubeconfig file data +func (c *Client) GetKubeConfig(args *GetKubeConfigArgs) (*GetKubeConfigResult, error) { + if args == nil { + return nil, fmt.Errorf("please set kube config argments") + } + + if args.ClusterUuid == "" { + return nil, fmt.Errorf("please set cluster uuid") + } + + result := &GetKubeConfigResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterKubeConfigUri()). + WithQueryParam("clusterUuid", args.ClusterUuid). + WithQueryParamFilter("type", string(args.Type)). + WithResult(result). + Do() + + return result, err +} + +// ListVersions - list all support kubernetes version +// +// RETURNS: +// - *ListVersionsResult: all support kubernetes version list +func (c *Client) ListVersions() (*ListVersionsResult, error) { + result := &ListVersionsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterVersionsUri()). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/cce/client.go b/bce-sdk-go/services/cce/client.go new file mode 100644 index 0000000..2d4171c --- /dev/null +++ b/bce-sdk-go/services/cce/client.go @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for CCE service + +// Package cce defines the CCE services of BCE. The supported APIs are all defined in sub-package + +package cce + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "cce." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_CLUSTER_URL = "/cluster" + REQUEST_NODE_URL = "/node" +) + +// Client of EIP service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getClusterUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL +} + +func getClusterUriWithId(clusterUuid string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterUuid +} + +func getNodeUri() string { + return URI_PREFIX + REQUEST_NODE_URL +} + +func getClusterExisteNodeUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/existed_node" +} + +func getClusterExisteNodeListUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/existed_bcc_node/list" +} + +func getClusterContainerNetUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/get_container_net" +} + +func getClusterKubeConfigUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/kubeconfig" +} + +func getClusterVersionsUri() string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/versions" +} diff --git a/bce-sdk-go/services/cce/client_test.go b/bce-sdk-go/services/cce/client_test.go new file mode 100644 index 0000000..b2558ac --- /dev/null +++ b/bce-sdk-go/services/cce/client_test.go @@ -0,0 +1,251 @@ +package cce + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CCE_CLIENT *Client + CCE_CLUSTER_ID string + CCE_SHIFT_IN_NODE_ID string + CCE_SHIFT_OUT_NODE_ID string + CCE_CONTAINER_NET string + CCE_KUBERNETES_VERSION string + + CCE_NODE_ADMIN_PASSWD = "123qwe!" + VPC_TEST_ID = "" + SUBNET_TEST_ID = "" + SECURITY_GROUP_ID = "" + ZONE_TEST_ID = "zoneA" + IMAGE_TEST_ID = "m-Nlv9C0tF" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fmt.Println(conf) + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CCE_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) + //log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_ListVersion(t *testing.T) { + result, err := CCE_CLIENT.ListVersions() + ExpectEqual(t.Errorf, nil, err) + + CCE_KUBERNETES_VERSION = result.Data[0] +} + +func TestClient_GetContainerNet(t *testing.T) { + args := &GetContainerNetArgs{ + VpcShortId: VPC_TEST_ID, + VpcCidr: "192.168.0.0/24", + Size: 1000, + } + result, err := CCE_CLIENT.GetContainerNet(args) + ExpectEqual(t.Errorf, nil, err) + + CCE_CONTAINER_NET = result.ContainerNet +} + +func TestClient_CreateCluster(t *testing.T) { + args := &CreateClusterArgs{ + ClusterName: "sdk-test", + Version: CCE_KUBERNETES_VERSION, + MainAvailableZone: "zoneA", + ContainerNet: CCE_CONTAINER_NET, + DeployMode: DeployModeBcc, + OrderContent: &BaseCreateOrderRequestVo{Items: []Item{ + { + Config: BccConfig{ + ProductType: ProductTypePostpay, + InstanceType: InstanceTypeG3, + Cpu: 1, + Memory: 2, + ImageType: ImageTypeCommon, + SubnetUuid: SUBNET_TEST_ID, + SecurityGroupId: SECURITY_GROUP_ID, + AdminPass: CCE_NODE_ADMIN_PASSWD, + PurchaseNum: 1, + ImageId: IMAGE_TEST_ID, + ServiceType: ServiceTypeBCC, + }, + }, + }}, + } + result, err := CCE_CLIENT.CreateCluster(args) + ExpectEqual(t.Errorf, nil, err) + + CCE_CLUSTER_ID = result.ClusterUuid +} + +func TestClient_GetCluster(t *testing.T) { + _, err := CCE_CLIENT.GetCluster(CCE_CLUSTER_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListClusters(t *testing.T) { + args := &ListClusterArgs{} + _, err := CCE_CLIENT.ListClusters(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListNodes(t *testing.T) { + args := &ListNodeArgs{ + ClusterUuid: CCE_CLUSTER_ID, + } + result, err := CCE_CLIENT.ListNodes(args) + ExpectEqual(t.Errorf, nil, err) + + CCE_SHIFT_OUT_NODE_ID = result.Nodes[0].InstanceShortId +} + +func TestClient_GetKubeConfig(t *testing.T) { + args := &GetKubeConfigArgs{ + ClusterUuid: CCE_CLUSTER_ID, + } + _, err := CCE_CLIENT.GetKubeConfig(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ScalingUp(t *testing.T) { + args := &ScalingUpArgs{ + ClusterUuid: CCE_CLUSTER_ID, + OrderContent: &BaseCreateOrderRequestVo{Items: []Item{ + { + Config: BccConfig{ + ProductType: ProductTypePostpay, + InstanceType: InstanceTypeG3, + Cpu: 1, + Memory: 2, + ImageType: ImageTypeCommon, + SubnetUuid: SUBNET_TEST_ID, + SecurityGroupId: SECURITY_GROUP_ID, + AdminPass: CCE_NODE_ADMIN_PASSWD, + PurchaseNum: 1, + ImageId: IMAGE_TEST_ID, + ServiceType: ServiceTypeBCC, + }, + }, + }}, + } + _, err := CCE_CLIENT.ScalingUp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ShiftOutNode(t *testing.T) { + args := &ShiftOutNodeArgs{ + ClusterUuid: CCE_CLUSTER_ID, + NodeInfoList: []CceNodeInfo{ + {InstanceId: CCE_SHIFT_OUT_NODE_ID}, + }, + } + err := CCE_CLIENT.ShiftOutNode(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListExistedBccNode(t *testing.T) { + args := &ListExistedNodeArgs{ + ClusterUuid: CCE_CLUSTER_ID, + } + result, err := CCE_CLIENT.ListExistedBccNode(args) + ExpectEqual(t.Errorf, nil, err) + + CCE_SHIFT_IN_NODE_ID = result.NodeList[0].InstanceId +} + +func TestClient_ShiftInNode(t *testing.T) { + args := &ShiftInNodeArgs{ + ClusterUuid: CCE_CLUSTER_ID, + NeedRebuild: false, + AdminPass: CCE_NODE_ADMIN_PASSWD, + InstanceType: ShiftInstanceTypeBcc, + NodeInfoList: []CceNodeInfo{ + { + InstanceId: CCE_SHIFT_IN_NODE_ID, + AdminPass: CCE_NODE_ADMIN_PASSWD, + }, + }, + } + err := CCE_CLIENT.ShiftInNode(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ScalingDown(t *testing.T) { + args := &ScalingDownArgs{ + ClusterUuid: CCE_CLUSTER_ID, + DeleteEipCds: true, + DeleteSnap: true, + NodeInfo: []NodeInfo{ + { + InstanceId: CCE_SHIFT_IN_NODE_ID, + }, + }, + } + err := CCE_CLIENT.ScalingDown(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCluster(t *testing.T) { + deleteClusterArgs := &DeleteClusterArgs{ + ClusterUuid: CCE_CLUSTER_ID, + DeleteEipCds: true, + DeleteSnap: true, + } + err := CCE_CLIENT.DeleteCluster(deleteClusterArgs) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/cce/model.go b/bce-sdk-go/services/cce/model.go new file mode 100644 index 0000000..cf5d414 --- /dev/null +++ b/bce-sdk-go/services/cce/model.go @@ -0,0 +1,469 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package cce + +import ( + "time" +) + +type ProductType string + +const ( + ProductTypePostpay ProductType = "postpay" + ProductTypePrepay ProductType = "prepay" +) + +type ClusterStatus string + +const ( + ClusterStatusRunning ClusterStatus = "RUNNING" + ClusterStatusCreating ClusterStatus = "CREATING" + ClusterStatusCreateFailed ClusterStatus = "CREATE_FAILED" + ClusterStatusDeleting ClusterStatus = "DELETING" + ClusterStatusDeletingFailed ClusterStatus = "DELETE_FAILED" + ClusterStatusMasterUpgrading ClusterStatus = "MASTER_UPGRADING" + ClusterStatusMasterUpgradeFailed ClusterStatus = "MASTER_UPGRADE_FAILED" + ClusterStatusError ClusterStatus = "ERROR" + ClusterStatusDeleted ClusterStatus = "DELETED" +) + +type SimpleNode struct { + InstanceShortId string `json:"instanceShortId"` + InstanceUuid string `json:"instanceUuid"` + InstanceName string `json:"instanceName"` + ClusterUuid string `json:"clusterUuid"` + Status ClusterStatus `json:"status"` +} + +type Cluster struct { + ClusterUuid string `json:"clusterUuid"` + ClusterName string `json:"clusterName"` + SlaveVmCount int `json:"slaveVmCount"` + MasterVmCount int `json:"masterVmCount"` + ContainerNet string `json:"containerNet"` + Status ClusterStatus `json:"status"` + Region string `json:"region"` + CreateTime time.Time `json:"createTime"` + DeleteTime time.Time `json:"deleteTime"` + AllInstanceNormal bool `json:"allInstanceNormal"` + InstanceList []SimpleNode `json:"instanceList"` + DccUuid string `json:"dccUuid"` + HasPrepay bool `json:"hasPrepay"` + VpcId string `json:"vpcId"` + InstanceMode string `json:"instanceMode"` + MasterExposed bool `json:"masterExposed"` +} + +type ImageType string + +const ( + ImageTypeCommon ImageType = "common" + ImageTypeCustom ImageType = "custom" + ImageTypeGpu ImageType = "gpuBccImage" + ImageTypeGpuCustom ImageType = "gpuBccCustom" + ImageTypeSharing ImageType = "sharing" +) + +type ServiceType string + +const ( + ServiceTypeBCC ServiceType = "BCC" + ServiceTypeCDS ServiceType = "CDS" + ServiceTypeEIP ServiceType = "EIP" +) + +type InstanceType string + +const ( + InstanceTypeG1 InstanceType = "0" + InstanceTypeDCC InstanceType = "1" + InstanceTypeBCC InstanceType = "2" + InstanceTypeC1 InstanceType = "4" + InstanceTypeG2 InstanceType = "7" + InstanceTypeGPU InstanceType = "9" + InstanceTypeG3 InstanceType = "10" + InstanceTypeC2 InstanceType = "11" + InstanceTypeG4 InstanceType = "13" + InstanceTypeVGPU InstanceType = "15" + InstanceTypeKunlun InstanceType = "25" +) + +type BccConfig struct { + Name string `json:"name,omitempty"` + KeypairId string `json:"keypairId,omitempty"` + ProductType ProductType `json:"productType"` + LogicalZone string `json:"logicalZone,omitempty"` + InstanceType InstanceType `json:"instanceType"` + GpuCard string `json:"gpuCard,omitempty"` + GpuCount int `json:"gpuCount"` + Cpu int `json:"cpu"` + Memory int `json:"memory"` + ImageType ImageType `json:"imageType"` + SubnetUuid string `json:"subnetUuid"` + SecurityGroupId string `json:"securityGroupId"` + AdminPass string `json:"adminPass,omitempty"` + PurchaseLength int `json:"purchaseLength,omitempty"` + PurchaseNum int `json:"purchaseNum"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType VolumeType `json:"rootDiskStorageType,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + AutoRenew bool `json:"autoRenew,omitempty"` + ImageId string `json:"imageId"` + ServiceType ServiceType `json:"serviceType"` +} + +type VolumeType string + +const ( + VolumeTypeSata VolumeType = "sata" + VolumeTypeSsd VolumeType = "ssd" + VolumeTypePremiumSsd VolumeType = "premium_ssd" +) + +type DiskSizeConfig struct { + Size string `json:"size"` + VolumeType VolumeType `json:"volumeType"` + SnapshotId string `json:"snapshotId,omitempty"` +} + +type CdsConfig struct { + ProductType ProductType `json:"productType"` + LogicalZone string `json:"logicalZone"` + PurchaseNum int `json:"purchaseNum"` + PurchaseLength int `json:"purchaseLength,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + CdsDiskSize []DiskSizeConfig `json:"cdsDiskSize"` + ServiceType ServiceType `json:"serviceType"` +} + +type EipType string + +const ( + EipTypeBandwidth EipType = "bandwidth" + EipTypeNetraffic EipType = "netraffic" +) + +type EipConfig struct { + ProductType ProductType `json:"productType"` + BandwidthInMbps int `json:"bandwidthInMbps"` + SubProductType EipType `json:"subProductType"` + PurchaseNum int `json:"purchaseNum"` + PurchaseLength int `json:"purchaseLength,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + Name string `json:"name,omitempty"` + ServiceType ServiceType `json:"serviceType"` +} + +type Item struct { + Config interface{} `json:"config"` +} + +type BaseCreateOrderRequestVo struct { + Items []Item `json:"items"` +} + +type CdsPreMountInfo struct { + MountPath string `json:"mountPath"` + CdsConfig []DiskSizeConfig `json:"cdsConfig"` +} + +type CniMode string + +const ( + CniModeKubenet CniMode = "kubenet" + CniModeCni CniMode = "cni" +) + +type CniType string + +const ( + CniTypeEmpty CniType = "" + CniTypeRouteVeth CniType = "VPC_ROUTE_VETH" + CniTypeRouteIpvlan CniType = "VPC_ROUTE_IPVLAN" + CniTypeRouteAutoDetect CniType = "VPC_ROUTE_AUTODETECT" + CniTypeSecondaryIpVeth CniType = "VPC_SECONDARY_IP_VETH" + CniTypeSecondaryIpIpvlan CniType = "VPC_SECONDARY_IP_IPVLAN" + CniTypeSecondaryIpAutoDetect CniType = "VPC_SECONDARY_IP_AUTODETECT" +) + +type DNSMode string + +const ( + DNSModeKubeDNS DNSMode = "kubeDNS" + DNSModeCoreDNS DNSMode = "coreDNS" +) + +type KubeProxyMode string + +const ( + KubeProxyModeIptables KubeProxyMode = "iptables" + KubeProxyModeIpvs KubeProxyMode = "ipvs" +) + +type AdvancedOptions struct { + KubeProxyMode KubeProxyMode `json:"kubeProxyMode,omitempty"` + SecureContainerEnable bool `json:"secureContainerEnable,omitempty"` + SetOSSecurity bool `json:"setOSSecurity,omitempty"` + CniMode CniMode `json:"cniMode,omitempty"` + CniType CniType `json:"cniType,omitempty"` + DnsMode DNSMode `json:"dnsMode,omitempty"` + MaxPodNum int `json:"maxPodNum,omitempty"` +} + +type NodeInfo struct { + InstanceId string `json:"instanceId"` +} + +type Node struct { + InstanceShortId string `json:"instanceShortId"` + InstanceUuid string `json:"instanceUuid"` + InstanceName string `json:"instanceName"` + ClusterUuid string `json:"clusterUuid"` + AvailableZone string `json:"availableZone"` + VpcId string `json:"vpcId"` + VpcCidr string `json:"vpcCidr"` + SubnetId string `json:"subnetId"` + SubnetType string `json:"subnetType"` + Eip string `json:"eip"` + EipBandwidth int `json:"eipBandwidth"` + Cpu int `json:"cpu"` + Memory int `json:"memory"` + DiskSize int `json:"diskSize"` + SysDisk int `json:"sysDisk"` + InstanceType string `json:"instanceType"` + Blb string `json:"blb"` + FloatingIp string `json:"floatingIp"` + FixIp string `json:"fixIp"` + CreateTime time.Time `json:"createTime"` + DeleteTime time.Time `json:"deleteTime"` + Status string `json:"status"` + ExpireTime time.Time `json:"expireTime"` + PaymentMethod string `json:"paymentMethod"` + RuntimeVersion string `json:"runtimeVersion"` +} + +type CceNodeInfo struct { + InstanceId string `json:"instanceId"` + AdminPass string `json:"adminPass,omitempty"` +} + +type ServerForDisplay struct { + InstanceId string `json:"instanceId"` + Name string `json:"name"` + Status string `json:"status"` + Payment string `json:"payment"` + InternalIp string `json:"internalIp"` +} + +type DeployMode string + +const ( + DeployModeBcc DeployMode = "BCC" + DeployModeDcc DeployMode = "DCC" +) + +type CreateClusterArgs struct { + ClusterName string `json:"clusterName"` + Version string `json:"version"` + MainAvailableZone string `json:"mainAvailableZone"` + ContainerNet string `json:"containerNet"` + AdvancedOptions *AdvancedOptions `json:"advancedOptions,omitempty"` + CdsPreMountInfo *CdsPreMountInfo `json:"cdsPreMountInfo,omitempty"` + Comment string `json:"comment,omitempty"` + DeployMode DeployMode `json:"deployMode"` + DccUuid string `json:"dccUuid,omitempty"` + MasterExposed bool `json:"masterExposed,omitempty"` + OrderContent *BaseCreateOrderRequestVo `json:"orderContent"` + MasterOrderContent *BaseCreateOrderRequestVo `json:"masterOrderContent,omitempty"` +} + +type CreateClusterResult struct { + ClusterUuid string `json:"clusterUuid"` + OrderId []string `json:"orderId"` +} + +type ListClusterArgs struct { + Status ClusterStatus + Marker string + MaxKeys int +} + +type ListClusterResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Clusters []Cluster `json:"clusters"` +} + +type GetClusterResult struct { + ClusterUuid string `json:"clusterUuid"` + ClusterName string `json:"clusterName"` + Version string `json:"version"` + Region string `json:"region"` + SlaveVmCount int `json:"slaveVmCount"` + MasterVmCount int `json:"masterVmCount"` + VpcId string `json:"vpcId"` + VpcUuid string `json:"vpcUuid"` + VpcCidr string `json:"vpcCidr"` + ZoneSubnetMap map[string]string `json:"zoneSubnetMap"` + ContainerNet string `json:"containerNet"` + AdvancedOptions *AdvancedOptions `json:"advancedOptions"` + Status ClusterStatus `json:"status"` + CreateStartTime time.Time `json:"createStartTime"` + DeleteTime time.Time `json:"deleteTime"` + Comment string `json:"comment"` + InstanceMode string `json:"instanceMode"` + HasPrepay bool `json:"hasPrepay"` + VpcName string `json:"vpcName"` + SecureContainerEnable bool `json:"secureContainerEnable"` + MasterZoneSubnetMap map[string]string `json:"masterZoneSubnetMap"` + MasterExposed bool `json:"masterExposed"` +} + +type DeleteClusterArgs struct { + ClusterUuid string + DeleteEipCds bool + DeleteSnap bool +} + +type ScalingUpArgs struct { + ClusterUuid string `json:"clusterUuid"` + DccUuid string `json:"dccUuid,omitempty"` + CdsPreMountInfo *CdsPreMountInfo `json:"cdsPreMountInfo,omitempty"` + OrderContent *BaseCreateOrderRequestVo `json:"orderContent"` +} + +type ScalingUpResult struct { + ClusterUuid string `json:"clusterUuid"` + OrderId []string `json:"orderId"` +} + +type ScalingDownArgs struct { + ClusterUuid string `json:"clusterUuid"` + DeleteEipCds bool `json:"-"` + DeleteSnap bool `json:"-"` + NodeInfo []NodeInfo `json:"nodeInfo"` +} + +type ListNodeArgs struct { + ClusterUuid string + Marker string + MaxKeys int +} + +type ListNodeResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Nodes []Node `json:"nodes"` +} + +type ShiftInstanceType string + +const ( + ShiftInstanceTypeBcc ShiftInstanceType = "BCC" + ShiftInstanceTypeBBC ShiftInstanceType = "BBC" +) + +type ShiftInNodeArgs struct { + ClusterUuid string `json:"clusterUuid"` + NeedRebuild bool `json:"needRebuild"` + ImageId string `json:"imageId,omitempty"` + AdminPass string `json:"adminPass"` + InstanceType ShiftInstanceType `json:"instanceType"` + NodeInfoList []CceNodeInfo `json:"nodeInfoList"` +} + +type ShiftOutNodeArgs struct { + ClusterUuid string `json:"clusterUuid"` + NodeInfoList []CceNodeInfo `json:"nodeInfoList"` +} + +type Order string + +const ( + OrderAsc Order = "asc" + OrderDesc Order = "desc" +) + +type KeywordType string + +const ( + KeywordTypeName KeywordType = "name" + KeywordTypeInstanceId KeywordType = "instanceId" +) + +type ListExistedNodeArgs struct { + ClusterUuid string `json:"clusterUuid"` + VpcId string `json:"vpcId,omitempty"` + VpcCidr string `json:"vpcCidr,omitempty"` + InstanceType ShiftInstanceType `json:"instanceType,omitempty"` + BBCFlavorId string `json:"bbcFlavorId,omitempty"` + KeywordType KeywordType `json:"keywordType,omitempty"` + Keyword string `json:"keyword,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + Order Order `json:"order,omitempty"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` +} + +type ListExistedNodeResult struct { + ClusterUuid string `json:"clusterUuid"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + NodeList []ServerForDisplay `json:"nodeList"` +} + +type GetContainerNetArgs struct { + VpcShortId string `json:"vpcShortId"` + VpcCidr string `json:"vpcCidr"` + Size int `json:"size"` +} + +type GetContainerNetResult struct { + ContainerNet string `json:"containerNet"` + Capacity int `json:"capacity"` +} + +type KubeConfigType string + +const ( + KubeConfigTypeDefault KubeConfigType = "default" + KubeConfigTypeInternal KubeConfigType = "internal" + KubeConfigTypeIntraVpc KubeConfigType = "intraVpc" +) + +type GetKubeConfigArgs struct { + ClusterUuid string + Type KubeConfigType +} + +type GetKubeConfigResult struct { + Data string `json:"data"` +} + +type ListVersionsResult struct { + Data []string `json:"data"` +} diff --git a/bce-sdk-go/services/cce/v2/ccev2.go b/bce-sdk-go/services/cce/v2/ccev2.go new file mode 100644 index 0000000..0ed6036 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/ccev2.go @@ -0,0 +1,676 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/services/cce/v2/types" +) + +// 创建集群 +func (c *Client) CreateCluster(args *CreateClusterArgs) (*CreateClusterResponse, error) { + if args == nil || args.CreateClusterRequest == nil { + return nil, fmt.Errorf("args is nil") + } + + //给其中可能存在的user script用base64编码 + if err := encodeUserScriptInInstanceSet(args.CreateClusterRequest.MasterSpecs); err != nil { + return nil, err + } + + if err := encodeUserScriptInInstanceSet(args.CreateClusterRequest.NodeSpecs); err != nil { + return nil, err + } + + result := &CreateClusterResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterURI()). + WithBody(args.CreateClusterRequest). + WithResult(result). + Do() + + return result, err +} + +// 删除集群 +func (c *Client) DeleteCluster(args *DeleteClusterArgs) (*DeleteClusterResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &DeleteClusterResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getClusterUriWithIDURI(args.ClusterID)). + WithQueryParamFilter("deleteResource", strconv.FormatBool(args.DeleteResource)). + WithQueryParamFilter("deleteCDSSnapshot", strconv.FormatBool(args.DeleteCDSSnapshot)). + WithResult(result). + Do() + + return result, err +} + +// 获得集群详情 +func (c *Client) GetCluster(clusterID string) (*GetClusterResponse, error) { + if clusterID == "" { + return nil, fmt.Errorf("clusterID is empty") + } + + result := &GetClusterResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterUriWithIDURI(clusterID)). + WithResult(result). + Do() + + return result, err +} + +// 集群列表 +func (c *Client) ListClusters(args *ListClustersArgs) (*ListClustersResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + if args.PageNum <= 0 || args.PageSize <= 0 { + return nil, fmt.Errorf("invlaid pageNo or pageSize") + } + + result := &ListClustersResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterListURI()). + WithQueryParamFilter("keywordType", string(args.KeywordType)). + WithQueryParamFilter("keyword", args.Keyword). + WithQueryParamFilter("orderBy", string(args.OrderBy)). + WithQueryParamFilter("order", string(args.Order)). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNum)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithResult(result). + Do() + + return result, err +} + +// 创建节点(扩容) +func (c *Client) CreateInstances(args *CreateInstancesArgs) (*CreateInstancesResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + //给其中可能存在的user script用base64编码 + if err := encodeUserScriptInInstanceSet(args.Instances); err != nil { + return nil, err + } + + s, _ := json.MarshalIndent(args, "", "\t") + fmt.Println("Args:" + string(s)) + + result := &CreateInstancesResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getClusterInstanceListURI(args.ClusterID)). + WithBody(args.Instances). + WithResult(result). + Do() + + return result, err +} + +// 查询节点 +func (c *Client) GetInstance(args *GetInstanceArgs) (*GetInstanceResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &GetInstanceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterInstanceURI(args.ClusterID, args.InstanceID)). + WithResult(result). + Do() + + return result, err +} + +// 更新节点配置 +func (c *Client) UpdateInstance(args *UpdateInstanceArgs) (*UpdateInstancesResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &UpdateInstancesResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getClusterInstanceURI(args.ClusterID, args.InstanceID)). + WithBody(args.InstanceSpec). + WithResult(result). + Do() + + return result, err +} + +// 删除节点(缩容) +func (c *Client) DeleteInstances(args *DeleteInstancesArgs) (*DeleteInstancesResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &DeleteInstancesResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getClusterInstanceListURI(args.ClusterID)). + WithBody(args.DeleteInstancesRequest). + WithResult(result). + Do() + + return result, err +} + +// 集群内节点列表 +func (c *Client) ListInstancesByPage(args *ListInstancesByPageArgs) (*ListInstancesResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &ListInstancesResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getClusterInstanceListURI(args.ClusterID)). + WithQueryParamFilter("keywordType", string(args.Params.KeywordType)). + WithQueryParamFilter("keyword", args.Params.Keyword). + WithQueryParamFilter("orderBy", string(args.Params.OrderBy)). + WithQueryParamFilter("order", string(args.Params.Order)). + WithQueryParamFilter("pageNo", strconv.Itoa(args.Params.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.Params.PageSize)). + WithResult(result). + Do() + + return result, err +} + +// 检查容器网络网段 +func (c *Client) CheckContainerNetworkCIDR(args *CheckContainerNetworkCIDRArgs) (*CheckContainerNetworkCIDRResponse, error) { + if args == nil { + return nil, fmt.Errorf("CheckContainerNetworkCIDRRequest is nil") + } + + result := &CheckContainerNetworkCIDRResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getNetCheckContainerNetworkCIDRURI()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// 检查集群网络网段 +func (c *Client) CheckClusterIPCIDR(args *CheckClusterIPCIDRArgs) (*CheckClusterIPCIDRResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &CheckClusterIPCIDRResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getNetCheckClusterIPCIDRURL()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// 推荐容器CIDR +func (c *Client) RecommendContainerCIDR(args *RecommendContainerCIDRArgs) (*RecommendContainerCIDRResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &RecommendContainerCIDRResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getNetRecommendContainerCidrURI()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// 推荐集群CIDR +func (c *Client) RecommendClusterIPCIDR(args *RecommendClusterIPCIDRArgs) (*RecommendClusterIPCIDRResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &RecommendClusterIPCIDRResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getNetRecommendClusterIpCidrURI()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// 用户集群 Quota +func (c *Client) GetClusterQuota() (*GetQuotaResponse, error) { + result := &GetQuotaResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getQuotaURI()). + WithResult(result). + Do() + + return result, err +} + +// 用户集群 Node Quota +func (c *Client) GetClusterNodeQuota(clusterID string) (*GetQuotaResponse, error) { + if clusterID == "" { + return nil, fmt.Errorf("clusterID is empty") + } + + result := &GetQuotaResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getQuotaNodeURI(clusterID)). + WithResult(result). + Do() + + return result, err +} + +// 创建节点组 +func (c *Client) CreateInstanceGroup(args *CreateInstanceGroupArgs) (*CreateInstanceGroupResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + encodeUserScript(&args.Request.InstanceTemplate.InstanceSpec) + + result := &CreateInstanceGroupResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getInstanceGroupURI(args.ClusterID)). + WithBody(args.Request). + WithResult(result). + Do() + + return result, err +} + +// 获取节点组列表 +func (c *Client) ListInstanceGroups(args *ListInstanceGroupsArgs) (*ListInstanceGroupResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &ListInstanceGroupResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParamFilter("pageNo", strconv.Itoa(args.ListOption.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.ListOption.PageSize)). + WithURL(getInstanceGroupListURI(args.ClusterID)). + WithResult(result). + Do() + + return result, err +} + +// 获取节点组的节点列表 +func (c *Client) ListInstancesByInstanceGroupID(args *ListInstanceByInstanceGroupIDArgs) (*ListInstancesByInstanceGroupIDResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &ListInstancesByInstanceGroupIDResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithURL(getClusterInstanceListWithInstanceGroupIDURI(args.ClusterID, args.InstanceGroupID)). + WithResult(result). + Do() + + return result, err +} + +// 获取节点组详情 +func (c *Client) GetInstanceGroup(args *GetInstanceGroupArgs) (*GetInstanceGroupResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &GetInstanceGroupResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceGroupWithIDURI(args.ClusterID, args.InstanceGroupID)). + WithResult(result). + Do() + + return result, err +} + +// 更新节点组副本数 +func (c *Client) UpdateInstanceGroupReplicas(args *UpdateInstanceGroupReplicasArgs) (*UpdateInstanceGroupReplicasResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &UpdateInstanceGroupReplicasResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceGroupReplicasURI(args.ClusterID, args.InstanceGroupID)). + WithBody(args.Request). + WithResult(result). + Do() + + return result, err +} + +// 修改节点组节点Autoscaler配置 +func (c *Client) UpdateInstanceGroupClusterAutoscalerSpec(args *UpdateInstanceGroupClusterAutoscalerSpecArgs) (*UpdateInstanceGroupClusterAutoscalerSpecResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + if args.Request == nil { + return nil, fmt.Errorf("nil UpdateInstanceGroupReplicasRequest") + } + if args.Request.Enabled { + if args.Request.MinReplicas < 0 || args.Request.MaxReplicas < args.Request.MinReplicas { + return nil, fmt.Errorf("invalid minReplicas or maxReplicas") + } + if args.Request.ScalingGroupPriority < 0 { + return nil, fmt.Errorf("invalid scalingGroupPriority") + } + } + + result := &UpdateInstanceGroupClusterAutoscalerSpecResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceGroupAutoScalerURI(args.ClusterID, args.InstanceGroupID)). + WithBody(args.Request). + WithResult(result). + Do() + + return result, err +} + +// 删除节点组 +func (c *Client) DeleteInstanceGroup(args *DeleteInstanceGroupArgs) (*DeleteInstanceGroupResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &DeleteInstanceGroupResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getInstanceGroupWithIDURI(args.ClusterID, args.InstanceGroupID)). + WithQueryParamFilter("deleteInstances", strconv.FormatBool(args.DeleteInstances)). + WithQueryParamFilter("releaseAllResources", strconv.FormatBool(args.ReleaseAllResources)). + WithResult(result). + Do() + + return result, err +} + +// 创建autoscaler配置 +func (c *Client) CreateAutoscaler(args *CreateAutoscalerArgs) (*CreateAutoscalerResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &CreateAutoscalerResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAutoscalerURI(args.ClusterID)). + WithResult(result). + Do() + + return result, err +} + +// 查询autoscaler配置 +func (c *Client) GetAutoscaler(args *GetAutoscalerArgs) (*GetAutoscalerResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &GetAutoscalerResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAutoscalerURI(args.ClusterID)). + WithResult(result). + Do() + + return result, err +} + +// 更新autoscaler配置 +func (c *Client) UpdateAutoscaler(args *UpdateAutoscalerArgs) (*UpdateAutoscalerResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &UpdateAutoscalerResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAutoscalerURI(args.ClusterID)). + WithBody(args.AutoscalerConfig). + WithResult(result). + Do() + + return result, err +} + +// 获取kubeconfig +func (c *Client) GetKubeConfig(args *GetKubeConfigArgs) (*GetKubeConfigResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + if err := CheckKubeConfigType(string(args.KubeConfigType)); err != nil { + return nil, err + } + + result := &GetKubeConfigResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getKubeconfigURI(args.ClusterID, args.KubeConfigType)). + WithResult(result). + Do() + + return result, err +} + +// 创建节点组扩容任务 +func (c *Client) CreateScaleUpInstanceGroupTask(args *CreateScaleUpInstanceGroupTaskArgs) (*CreateTaskResp, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.ClusterID == "" { + return nil, fmt.Errorf("clusterID is empty") + } + if args.InstanceGroupID == "" { + return nil, fmt.Errorf("instanceGroupID is empty") + } + if args.TargetReplicas <= 0 { + return nil, fmt.Errorf("target replicas should be positive") + } + + result := &CreateTaskResp{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getScaleUpInstanceGroupURI(args.ClusterID, args.InstanceGroupID)). + WithQueryParamFilter("upToReplicas", strconv.Itoa(args.TargetReplicas)). + WithResult(result). + Do() + return result, err +} + +// 创建节点组缩容任务 +func (c *Client) CreateScaleDownInstanceGroupTask(args *CreateScaleDownInstanceGroupTaskArgs) (*CreateTaskResp, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.ClusterID == "" { + return nil, fmt.Errorf("clusterID is empty") + } + if args.InstanceGroupID == "" { + return nil, fmt.Errorf("instanceGroupID is empty") + } + if len(args.InstancesToBeRemoved) == 0 { + return nil, fmt.Errorf("instances to be removed are not provided") + } + + body := map[string]interface{}{ + "instancesToBeRemoved": args.InstancesToBeRemoved, + } + + result := &CreateTaskResp{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getScaleDownInstanceGroupURI(args.ClusterID, args.InstanceGroupID)). + WithBody(body). + WithResult(result). + Do() + return result, err +} + +// 获取任务信息 +func (c *Client) GetTask(args *GetTaskArgs) (*GetTaskResp, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.TaskType == "" { + return nil, fmt.Errorf("taskType is not set") + } + if args.TaskID == "" { + return nil, fmt.Errorf("taskID is empty") + } + + switch args.TaskType { + case types.TaskTypeInstanceGroupReplicas: + default: + return nil, fmt.Errorf("unsupported taskType") + } + + result := &GetTaskResp{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTaskWithIDURI(args.TaskType, args.TaskID)). + WithResult(result). + Do() + return result, err +} + +// 获取任务列表 +func (c *Client) ListTasks(args *ListTasksArgs) (*ListTaskResp, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.TaskType == "" { + return nil, fmt.Errorf("taskType is not set") + } + + switch args.TaskType { + case types.TaskTypeInstanceGroupReplicas: + if args.TargetID == "" { + return nil, fmt.Errorf("targetID is empty") + } + default: + return nil, fmt.Errorf("unsupported taskType") + } + + result := &ListTaskResp{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTaskListURI(args.TaskType)). + WithQueryParamFilter("targetID", args.TargetID). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithResult(result). + Do() + return result, err +} + +func (c *Client) GetInstanceCRD(args *GetInstanceCRDArgs) (*GetInstanceCRDResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &GetInstanceCRDResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(genGetInstanceCRDURI(args.ClusterID, args.CCEInstanceID)). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) UpdateInstanceCRD(args *UpdateInstanceCRDRequest) (*CommonResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.Instance == nil { + return nil, fmt.Errorf("instance is nil") + } + + result := &CommonResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(genUpdateInstanceCRDURI(args.Instance.Spec.ClusterID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) GetClusterCRD(args *GetClusterCRDArgs) (*GetClusterCRDResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + result := &GetClusterCRDResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(genGetClusterCRDURI(args.ClusterID)). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) UpdateClusterCRD(args *UpdateClusterCRDArgs) (*CommonResponse, error) { + if args == nil { + return nil, fmt.Errorf("args is nil") + } + + if args.Cluster == nil { + return nil, fmt.Errorf("cluster is nil") + } + + result := &CommonResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(genUpdateClusterCRDURI(args.Cluster.Spec.ClusterID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/cce/v2/client.go b/bce-sdk-go/services/cce/v2/client.go new file mode 100644 index 0000000..bc47f59 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/client.go @@ -0,0 +1,216 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package v2 + +import ( + "encoding/base64" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/cce/v2/types" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "api/cce/service/v2" + + DEFAULT_ENDPOINT = "cce." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_CLUSTER_URL = "/cluster" + + REQUEST_CLUSTER_LIST_URL = "/clusters" + + REQUEST_INSTANCE_URL = "/instance" + + REQUEST_INSTANCE_LIST_URL = "/instances" + + REQUEST_INSTANCEGROUP_URL = "/instancegroup" + + REQUEST_INSTANCEGROUP_LIST_URL = "/instancegroups" + + REQUEST_INSTANCEGROUP_AUTOSCALER_URL = "/autoscaler" + + REQUEST_INSTANCEGROUP_REPLICAS_URL = "/replicas" + + REQUEST_INSTANCEGROUP_SCALE_UP_URL = "/scaleup" + + REQUEST_INSTANCEGROUP_SCALE_DOWN_URL = "/scaledown" + + REQUEST_QUOTA_URL = "/quota" + + REQUEST_NODE_URL = "/node" + + REQUEST_NET_URL = "/net" + + REQUEST_NET_CHECK_CONTAINER_NETWORK_CIDR_URL = "/check_container_network_cidr" + + REQUEST_NET_CHECK_CLUSTERIP_CIDR_URL = "/check_clusterip_cidr" + + REQUEST_NET_RECOMMEND_CLSUTERIP_CIDR_URL = "/recommend_clusterip_cidr" + + REQUEST_NET_RECOMMEND_CONTAINER_CIDR_URL = "/recommend_container_cidr" + + REQUEST_AUTOSCALER = "/autoscaler" + + REQUEST_KUBECONFIG = "/kubeconfig/%s/%s" + + REQUEST_TASK_URL = "/task" + + REQUEST_TASK_LIST_URL = "/tasks" +) + +var _ Interface = &Client{} + +// Client 实现 ccev2.Interface +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getClusterURI() string { + return URI_PREFIX + REQUEST_CLUSTER_URL +} + +func getClusterUriWithIDURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID +} + +func getClusterListURI() string { + return URI_PREFIX + REQUEST_CLUSTER_LIST_URL +} + +func getClusterInstanceListURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCE_LIST_URL +} + +func getClusterInstanceURI(clusterID, instanceID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCE_URL + "/" + instanceID +} + +func getClusterInstanceListWithInstanceGroupIDURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID + REQUEST_INSTANCE_LIST_URL +} + +func getInstanceGroupURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL +} + +func getInstanceGroupListURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_LIST_URL +} + +func getInstanceGroupWithIDURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID +} + +func getInstanceGroupAutoScalerURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID + REQUEST_INSTANCEGROUP_AUTOSCALER_URL +} + +func getScaleUpInstanceGroupURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID + REQUEST_INSTANCEGROUP_SCALE_UP_URL +} + +func getScaleDownInstanceGroupURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID + REQUEST_INSTANCEGROUP_SCALE_DOWN_URL +} + +func getInstanceGroupReplicasURI(clusterID, instanceGroupID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCEGROUP_URL + "/" + instanceGroupID + REQUEST_INSTANCEGROUP_REPLICAS_URL +} + +func getNetCheckContainerNetworkCIDRURI() string { + return URI_PREFIX + REQUEST_NET_URL + REQUEST_NET_CHECK_CONTAINER_NETWORK_CIDR_URL +} + +func getNetCheckClusterIPCIDRURL() string { + return URI_PREFIX + REQUEST_NET_URL + REQUEST_NET_CHECK_CLUSTERIP_CIDR_URL +} + +func getNetRecommendClusterIpCidrURI() string { + return URI_PREFIX + REQUEST_NET_URL + REQUEST_NET_RECOMMEND_CLSUTERIP_CIDR_URL +} + +func getNetRecommendContainerCidrURI() string { + return URI_PREFIX + REQUEST_NET_URL + REQUEST_NET_RECOMMEND_CONTAINER_CIDR_URL +} + +func getQuotaURI() string { + return URI_PREFIX + REQUEST_QUOTA_URL + REQUEST_CLUSTER_URL +} + +func getQuotaNodeURI(clusterID string) string { + return URI_PREFIX + REQUEST_QUOTA_URL + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_NODE_URL +} + +func getAutoscalerURI(clusterID string) string { + return URI_PREFIX + REQUEST_AUTOSCALER + "/" + clusterID +} + +func getKubeconfigURI(clusterID string, kubeConfigType KubeConfigType) string { + return URI_PREFIX + fmt.Sprintf(REQUEST_KUBECONFIG, clusterID, kubeConfigType) +} + +func getTaskWithIDURI(taskType types.TaskType, taskID string) string { + return URI_PREFIX + REQUEST_TASK_URL + "/" + string(taskType) + "/" + taskID +} + +func getTaskListURI(taskType types.TaskType) string { + return URI_PREFIX + REQUEST_TASK_LIST_URL + "/" + string(taskType) +} + +func genGetInstanceCRDURI(clusterID, cceInstanceID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCE_URL + "/" + cceInstanceID + "/crd" +} + +func genUpdateInstanceCRDURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + REQUEST_INSTANCE_URL + "/crd" +} + +func genGetClusterCRDURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + "/crd" +} + +func genUpdateClusterCRDURI(clusterID string) string { + return URI_PREFIX + REQUEST_CLUSTER_URL + "/" + clusterID + "/crd" +} + +func encodeUserScriptInInstanceSet(instancesSets []*InstanceSet) error { + if instancesSets == nil { + return nil + } + for _, instanceSet := range instancesSets { + encodeUserScript(&instanceSet.InstanceSpec) + } + return nil +} + +func encodeUserScript(instanceSpec *types.InstanceSpec) { + if instanceSpec == nil { + return + } + if instanceSpec.DeployCustomConfig.PreUserScript != "" { + base64Str := base64.StdEncoding.EncodeToString([]byte(instanceSpec.DeployCustomConfig.PreUserScript)) + instanceSpec.DeployCustomConfig.PreUserScript = base64Str + } + if instanceSpec.DeployCustomConfig.PostUserScript != "" { + base64Str := base64.StdEncoding.EncodeToString([]byte(instanceSpec.DeployCustomConfig.PostUserScript)) + instanceSpec.DeployCustomConfig.PostUserScript = base64Str + } +} diff --git a/bce-sdk-go/services/cce/v2/client_test.go b/bce-sdk-go/services/cce/v2/client_test.go new file mode 100644 index 0000000..f795be5 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/client_test.go @@ -0,0 +1,688 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + "github.com/baidubce/bce-sdk-go/services/cce/v2/types" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CCE_CLIENT *Client + CCE_CLUSTER_ID string + CCE_INSTANCE_GROUP_ID string + CCE_INSTANCE_ID string + VPC_TEST_ID = "" + IMAGE_TEST_ID = "" + SECURITY_GROUP_TEST_ID = "" + VPC_SUBNET_TEST_ID = "" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func TestMain(m *testing.M) { + setup() + code := m.Run() + os.Exit(code) +} + +func setup() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fmt.Println(conf) + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + log.SetLogLevel(log.WARN) + + CCE_CLIENT, err = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + if err != nil { + log.Fatal(err) + } + + log.Info("Setup Complete") +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CheckClusterIPCIDR(t *testing.T) { + args := &CheckClusterIPCIDRArgs{ + VPCID: VPC_TEST_ID, + VPCCIDR: "192.168.0.0/16", + ClusterIPCIDR: "172.31.0.0/16", + IPVersion: "ipv4", + } + resp, err := CCE_CLIENT.CheckClusterIPCIDR(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_CheckContainerNetworkCIDR(t *testing.T) { + args := &CheckContainerNetworkCIDRArgs{ + VPCID: VPC_TEST_ID, + VPCCIDR: "192.168.0.0/16", + ContainerCIDR: "172.28.0.0/16", + ClusterIPCIDR: "172.31.0.0/16", + MaxPodsPerNode: 256, + } + resp, err := CCE_CLIENT.CheckContainerNetworkCIDR(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_RecommendClusterIPCIDR(t *testing.T) { + args := &RecommendClusterIPCIDRArgs{ + ClusterMaxServiceNum: 8, + ContainerCIDR: "172.28.0.0/16", + IPVersion: "ipv4", + PrivateNetCIDRs: []PrivateNetString{PrivateIPv4Net172}, + VPCCIDR: "192.168.0.0/16", + } + resp, err := CCE_CLIENT.RecommendClusterIPCIDR(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_RecommendContainerCIDR(t *testing.T) { + args := &RecommendContainerCIDRArgs{ + ClusterMaxNodeNum: 2, + IPVersion: "ipv4", + K8SVersion: types.K8S_1_16_8, + MaxPodsPerNode: 32, + PrivateNetCIDRs: []PrivateNetString{PrivateIPv4Net172}, + VPCCIDR: "192.168.0.0/16", + VPCID: VPC_TEST_ID, + } + + resp, err := CCE_CLIENT.RecommendContainerCIDR(args) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("Request ID:" + resp.RequestID) + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_CreateCluster(t *testing.T) { + args := &CreateClusterArgs{ + CreateClusterRequest: &CreateClusterRequest{ + ClusterSpec: &types.ClusterSpec{ + ClusterName: "sdk-ccev2-test", + K8SVersion: types.K8S_1_16_8, + RuntimeType: types.RuntimeTypeDocker, + VPCID: VPC_TEST_ID, + MasterConfig: types.MasterConfig{ + MasterType: types.MasterTypeManaged, + ClusterHA: 1, + ExposedPublic: false, + ClusterBLBVPCSubnetID: VPC_SUBNET_TEST_ID, + ManagedClusterMasterOption: types.ManagedClusterMasterOption{ + MasterVPCSubnetZone: types.AvailableZoneA, + }, + }, + ContainerNetworkConfig: types.ContainerNetworkConfig{ + Mode: types.ContainerNetworkModeKubenet, + LBServiceVPCSubnetID: VPC_SUBNET_TEST_ID, + ClusterPodCIDR: "172.28.0.0/16", + ClusterIPServiceCIDR: "172.31.0.0/16", + }, + K8SCustomConfig: types.K8SCustomConfig{ + KubeAPIQPS: 1000, + KubeAPIBurst: 2000, + }, + }, + }, + } + resp, err := CCE_CLIENT.CreateCluster(args) + + ExpectEqual(t.Errorf, nil, err) + if resp.ClusterID == "" { + t.Fatalf("Request Fail. Cluster ID is empty.") + } + + CCE_CLUSTER_ID = resp.ClusterID + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + //等集群创建完成 + time.Sleep(time.Duration(180) * time.Second) +} + +func TestClient_GetCluster(t *testing.T) { + resp, err := CCE_CLIENT.GetCluster(CCE_CLUSTER_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_ListClusters(t *testing.T) { + args := &ListClustersArgs{ + KeywordType: "clusterName", + Keyword: "", + OrderBy: "clusterID", + Order: OrderASC, + PageSize: 10, + PageNum: 1, + } + resp, err := CCE_CLIENT.ListClusters(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_CreateInstanceGroup(t *testing.T) { + args := &CreateInstanceGroupArgs{ + ClusterID: CCE_CLUSTER_ID, + Request: &CreateInstanceGroupRequest{ + types.InstanceGroupSpec{ + InstanceGroupName: "jichao-sdk-testcase", + CleanPolicy: types.DeleteCleanPolicy, + Replicas: 3, + InstanceTemplate: types.InstanceTemplate{ + InstanceSpec: types.InstanceSpec{ + ClusterRole: types.ClusterRoleNode, + Existed: false, + MachineType: types.MachineTypeBCC, + InstanceType: bccapi.InstanceTypeN3, + VPCConfig: types.VPCConfig{ + VPCID: VPC_TEST_ID, + VPCSubnetID: VPC_SUBNET_TEST_ID, + SecurityGroupID: SECURITY_GROUP_TEST_ID, + AvailableZone: types.AvailableZoneA, + }, + DeployCustomConfig: types.DeployCustomConfig{ + PreUserScript: "ls", + PostUserScript: "ls", + }, + InstanceResource: types.InstanceResource{ + CPU: 1, + MEM: 4, + RootDiskSize: 40, + LocalDiskSize: 0, + }, + ImageID: IMAGE_TEST_ID, + InstanceOS: types.InstanceOS{ + ImageType: bccapi.ImageTypeSystem, + }, + NeedEIP: false, + InstanceChargingType: bccapi.PaymentTimingPostPaid, + RuntimeType: types.RuntimeTypeDocker, + }, + }, + }, + }, + } + + resp, err := CCE_CLIENT.CreateInstanceGroup(args) + + ExpectEqual(t.Errorf, nil, err) + if resp.InstanceGroupID == "" { + t.Fatalf("Request Fail. Instance Group ID is empty.") + } + CCE_INSTANCE_GROUP_ID = resp.InstanceGroupID + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + time.Sleep(time.Duration(180) * time.Second) +} + +func TestClient_ListInstanceGroups(t *testing.T) { + args := &ListInstanceGroupsArgs{ + ClusterID: CCE_CLUSTER_ID, + ListOption: &InstanceGroupListOption{ + PageNo: 0, + PageSize: 0, + }, + } + resp, err := CCE_CLIENT.ListInstanceGroups(args) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListInstancesByInstanceGroupID(t *testing.T) { + args := &ListInstanceByInstanceGroupIDArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceGroupID: CCE_INSTANCE_GROUP_ID, + PageSize: 0, + PageNo: 0, + } + + resp, err := CCE_CLIENT.ListInstancesByInstanceGroupID(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetInstanceGroup(t *testing.T) { + args := &GetInstanceGroupArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceGroupID: CCE_INSTANCE_GROUP_ID, + } + + resp, err := CCE_CLIENT.GetInstanceGroup(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_UpdateInstanceGroupReplicas(t *testing.T) { + args := &UpdateInstanceGroupReplicasArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceGroupID: CCE_INSTANCE_GROUP_ID, + Request: &UpdateInstanceGroupReplicasRequest{ + Replicas: 1, + DeleteInstance: true, + DeleteOption: &types.DeleteOption{ + MoveOut: false, + DeleteCDSSnapshot: true, + DeleteResource: true, + }, + }, + } + + resp, err := CCE_CLIENT.UpdateInstanceGroupReplicas(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + time.Sleep(time.Duration(120) * time.Second) +} + +func TestClient_CreateAutoscaler(t *testing.T) { + args := &CreateAutoscalerArgs{ + ClusterID: CCE_CLUSTER_ID, + } + + resp, err := CCE_CLIENT.CreateAutoscaler(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetAutoscaler(t *testing.T) { + args := &GetAutoscalerArgs{ + ClusterID: CCE_CLUSTER_ID, + } + + resp, err := CCE_CLIENT.GetAutoscaler(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_UpdateAutoscaler(t *testing.T) { + args := &UpdateAutoscalerArgs{ + ClusterID: CCE_CLUSTER_ID, + AutoscalerConfig: ClusterAutoscalerConfig{ + ReplicaCount: 5, + ScaleDownEnabled: true, + Expander: "random", + }, + } + + resp, err := CCE_CLIENT.UpdateAutoscaler(args) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateInstanceGroupClusterAutoscalerSpec(t *testing.T) { + args := &UpdateInstanceGroupClusterAutoscalerSpecArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceGroupID: CCE_INSTANCE_GROUP_ID, + Request: &ClusterAutoscalerSpec{ + Enabled: true, + MinReplicas: 2, + MaxReplicas: 5, + ScalingGroupPriority: 1, + }, + } + + resp, err := CCE_CLIENT.UpdateInstanceGroupClusterAutoscalerSpec(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetKubeConfig(t *testing.T) { + args := &GetKubeConfigArgs{ + ClusterID: CCE_CLUSTER_ID, + KubeConfigType: KubeConfigTypeVPC, + } + + resp, err := CCE_CLIENT.GetKubeConfig(args) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteInstanceGroup(t *testing.T) { + args := &DeleteInstanceGroupArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceGroupID: CCE_INSTANCE_GROUP_ID, + DeleteInstances: true, + ReleaseAllResources: true, + } + + resp, err := CCE_CLIENT.DeleteInstanceGroup(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + time.Sleep(time.Duration(180) * time.Second) +} + +func TestClient_CreateInstances(t *testing.T) { + args := &CreateInstancesArgs{ + ClusterID: CCE_CLUSTER_ID, + Instances: []*InstanceSet{ + { + Count: 1, + InstanceSpec: types.InstanceSpec{ + ClusterRole: types.ClusterRoleNode, + Existed: false, + MachineType: types.MachineTypeBCC, + InstanceType: bccapi.InstanceTypeN3, + VPCConfig: types.VPCConfig{ + VPCID: VPC_TEST_ID, + VPCSubnetID: VPC_SUBNET_TEST_ID, + SecurityGroupID: SECURITY_GROUP_TEST_ID, + AvailableZone: types.AvailableZoneA, + }, + DeployCustomConfig: types.DeployCustomConfig{ + PreUserScript: "ls", + PostUserScript: "time", + }, + InstanceResource: types.InstanceResource{ + CPU: 1, + MEM: 4, + RootDiskSize: 40, + LocalDiskSize: 0, + }, + ImageID: IMAGE_TEST_ID, + InstanceOS: types.InstanceOS{ + ImageType: bccapi.ImageTypeSystem, + OSType: types.OSTypeLinux, + OSName: types.OSNameCentOS, + OSVersion: "7.5", + OSArch: "x86_64 (64bit)", + }, + NeedEIP: false, + InstanceChargingType: bccapi.PaymentTimingPostPaid, + RuntimeType: types.RuntimeTypeDocker, + }, + }, + }, + } + resp, err := CCE_CLIENT.CreateInstances(args) + + ExpectEqual(t.Errorf, nil, err) + if resp.CCEInstanceIDs == nil || len(resp.CCEInstanceIDs) == 0 { + t.Fatalf("Request Fail. Instance ID is empty.") + } + CCE_INSTANCE_ID = resp.CCEInstanceIDs[0] + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) + + time.Sleep(time.Duration(180) * time.Second) +} + +func TestClient_ListInstancesByPage(t *testing.T) { + args := &ListInstancesByPageArgs{ + ClusterID: CCE_CLUSTER_ID, + Params: &ListInstancesByPageParams{ + KeywordType: InstanceKeywordTypeInstanceName, + Keyword: "", + OrderBy: "createdAt", + Order: OrderASC, + PageNo: 1, + PageSize: 10, + EnableInternalFields: true, + }, + } + resp, err := CCE_CLIENT.ListInstancesByPage(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetInstance(t *testing.T) { + args := &GetInstanceArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceID: CCE_INSTANCE_ID, + } + resp, err := CCE_CLIENT.GetInstance(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_UpdateInstance(t *testing.T) { + args := &GetInstanceArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceID: CCE_INSTANCE_ID, + } + respGet, err := CCE_CLIENT.GetInstance(args) + + oldInstanceSpec := respGet.Instance.Spec + + oldInstanceSpec.CCEInstancePriority = 1 + + argsUpdate := &UpdateInstanceArgs{ + ClusterID: CCE_CLUSTER_ID, + InstanceID: CCE_INSTANCE_ID, + InstanceSpec: oldInstanceSpec, + } + + respUpdate, err := CCE_CLIENT.UpdateInstance(argsUpdate) + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(respUpdate, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetClusterQuota(t *testing.T) { + resp, err := CCE_CLIENT.GetClusterQuota() + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetClusterNodeQuota(t *testing.T) { + resp, err := CCE_CLIENT.GetClusterNodeQuota(CCE_CLUSTER_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_DeleteInstances(t *testing.T) { + args := &DeleteInstancesArgs{ + ClusterID: CCE_CLUSTER_ID, + DeleteInstancesRequest: &DeleteInstancesRequest{ + InstanceIDs: []string{CCE_INSTANCE_ID}, + DeleteOption: &types.DeleteOption{ + MoveOut: false, + DeleteCDSSnapshot: true, + DeleteResource: true, + }, + }, + } + resp, err := CCE_CLIENT.DeleteInstances(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_DeleteCluster(t *testing.T) { + args := &DeleteClusterArgs{ + ClusterID: CCE_CLUSTER_ID, + DeleteResource: true, + DeleteCDSSnapshot: true, + } + resp, err := CCE_CLIENT.DeleteCluster(args) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("Request ID:" + resp.RequestID) + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_InstanceCRD(t *testing.T) { + getInstanceCRDArgs := &GetInstanceCRDArgs{ + ClusterID: "cce-xyr5njs7", + CCEInstanceID: "cce-xyr5njs7-zjxl4lju", + } + + resp, err := CCE_CLIENT.GetInstanceCRD(getInstanceCRDArgs) + if err != nil { + t.Logf("get instance crd error: %s", err.Error()) + return + } + t.Logf("Request ID: %s", resp.RequestID) + s, _ := json.MarshalIndent(resp, "", "\t") + t.Logf("Response: %s", string(s)) + + // update instance crd + instance := resp.Instance + instance.Spec.AdminPassword = "Test123!" + + updateInstanceCRD := UpdateInstanceCRDRequest{ + Instance: instance, + } + commonResp, err := CCE_CLIENT.UpdateInstanceCRD(&updateInstanceCRD) + if err != nil { + t.Logf("update instance crd error: %s", err.Error()) + return + } + + t.Logf("Request ID: %s", commonResp.RequestID) +} + +func TestClient_UpdateClusterCRD(t *testing.T) { + getClusterCRDArgs := &GetClusterCRDArgs{ + + ClusterID: "cce-bvyohjkg", + } + + resp, err := CCE_CLIENT.GetClusterCRD(getClusterCRDArgs) + if err != nil { + fmt.Printf("get cluster crd error: %s", err.Error()) + return + } + + fmt.Printf("Request ID: %s", resp.RequestID) + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Printf("Response: %s", string(s)) + + cluster := resp.Cluster + cluster.Spec.ClusterName = "gogogogo" + + request := UpdateClusterCRDArgs{ + Cluster: cluster, + } + + commonResp, err := CCE_CLIENT.UpdateClusterCRD(&request) + if err != nil { + fmt.Printf("update cluster crd error: %s", err.Error()) + return + } + + fmt.Printf("Resuest ID: %s", commonResp.RequestID) +} diff --git a/bce-sdk-go/services/cce/v2/model.go b/bce-sdk-go/services/cce/v2/model.go new file mode 100644 index 0000000..5d25c89 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/model.go @@ -0,0 +1,879 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ +/* +CCE V2 版本 GO SDK, Interface 定义 +*/ + +package v2 + +import ( + "fmt" + "time" + + "github.com/baidubce/bce-sdk-go/services/cce/v2/types" + "github.com/baidubce/bce-sdk-go/services/vpc" +) + +// Interface 定义 CCE V2 SDK +type Interface interface { + CreateCluster(args *CreateClusterArgs) (*CreateClusterResponse, error) + GetCluster(clusterID string) (*GetClusterResponse, error) + DeleteCluster(args *DeleteClusterArgs) (*DeleteClusterResponse, error) + ListClusters(args *ListClustersArgs) (*ListClustersResponse, error) + + CreateInstances(args *CreateInstancesArgs) (*CreateInstancesResponse, error) + GetInstance(args *GetInstanceArgs) (*GetInstanceResponse, error) + DeleteInstances(args *DeleteInstancesArgs) (*DeleteInstancesResponse, error) + ListInstancesByPage(args *ListInstancesByPageArgs) (*ListInstancesResponse, error) + CreateScaleUpInstanceGroupTask(args *CreateScaleUpInstanceGroupTaskArgs) (*CreateTaskResp, error) + CreateScaleDownInstanceGroupTask(args *CreateScaleDownInstanceGroupTaskArgs) (*CreateTaskResp, error) + + GetClusterQuota() (*GetQuotaResponse, error) + GetClusterNodeQuota(clusterID string) (*GetQuotaResponse, error) + + CheckContainerNetworkCIDR(args *CheckContainerNetworkCIDRArgs) (*CheckContainerNetworkCIDRResponse, error) + CheckClusterIPCIDR(args *CheckClusterIPCIDRArgs) (*CheckClusterIPCIDRResponse, error) + RecommendContainerCIDR(args *RecommendContainerCIDRArgs) (*RecommendContainerCIDRResponse, error) + RecommendClusterIPCIDR(args *RecommendClusterIPCIDRArgs) (*RecommendClusterIPCIDRResponse, error) + + GetTask(args *GetTaskArgs) (*GetTaskResp, error) + ListTasks(args *ListTasksArgs) (*ListTaskResp, error) + + GetInstanceCRD(args *GetInstanceCRDArgs) (*GetInstanceCRDResponse, error) + UpdateInstanceCRD(args *UpdateInstanceCRDRequest) (*CommonResponse, error) +} + +// CreateCluterArgs为后续支持clientToken预留空间 +type CreateClusterArgs struct { + CreateClusterRequest *CreateClusterRequest +} + +type DeleteClusterArgs struct { + ClusterID string + DeleteResource bool + DeleteCDSSnapshot bool +} + +type ListClustersArgs struct { + KeywordType ClusterKeywordType + Keyword string + OrderBy ClusterOrderBy + Order Order + PageNum int + PageSize int +} + +type CreateInstancesArgs struct { + ClusterID string + Instances []*InstanceSet +} + +type GetInstanceArgs struct { + ClusterID string + InstanceID string +} + +type GetInstanceCRDArgs struct { + ClusterID string + // cceInstanceID , cce 节点的唯一标志,不是底层机器的instanceID + CCEInstanceID string +} + +type DeleteInstancesArgs struct { + ClusterID string + DeleteInstancesRequest *DeleteInstancesRequest +} + +type ListInstancesByPageArgs struct { + ClusterID string + Params *ListInstancesByPageParams +} + +// CreateClusterRequest - 创建 Cluster 参数 +type CreateClusterRequest struct { + ClusterSpec *types.ClusterSpec `json:"cluster"` + MasterSpecs []*InstanceSet `json:"masters,omitempty"` + NodeSpecs []*InstanceSet `json:"nodes,omitempty"` +} + +type InstanceSet struct { + InstanceSpec types.InstanceSpec `json:"instanceSpec"` + Count int `json:"count"` +} + +// ListInstancesByPageParams - 分页查询集群实例列表参数 +type ListInstancesByPageParams struct { + KeywordType InstanceKeywordType `json:"keywordType"` + Keyword string `json:"keyword"` + OrderBy InstanceOrderBy `json:"orderBy"` + Order Order `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + EnableInternalFields bool `json:"enableInternalFields"` +} + +// CreateClusterResponse - 创建 Cluster 返回 +type CreateClusterResponse struct { + ClusterID string `json:"clusterID"` + RequestID string `json:"requestID"` +} + +// UpdateClusterResponse - 更新 Cluster 返回 +type UpdateClusterResponse struct { + Cluster *Cluster `json:"cluster"` + RequestID string `json:"requestID"` +} + +// GetClusterResponse - 查询 Cluster 返回 +type GetClusterResponse struct { + Cluster *Cluster `json:"cluster"` + RequestID string `json:"requestID"` +} + +// DeleteClusterResponse - 删除 Cluster 返回 +type DeleteClusterResponse struct { + RequestID string `json:"requestID"` +} + +// ListClustersResponse - List 用户 Cluster 返回 +type ListClustersResponse struct { + ClusterPage *ClusterPage `json:"clusterPage"` + RequestID string `json:"requestID"` +} + +// CreateInstancesResponse - 创建 Instances 返回 +type CreateInstancesResponse struct { + CCEInstanceIDs []string `json:"cceInstanceIDs"` + RequestID string `json:"requestID"` +} + +type UpdateInstanceArgs struct { + ClusterID string + InstanceID string + InstanceSpec *types.InstanceSpec +} + +// UpdateInstancesResponse - 更新 Instances 返回 +type UpdateInstancesResponse struct { + Instance *Instance `json:"instance"` + RequestID string `json:"requestID"` +} + +// ClusterPage - 集群分页查询返回 +type ClusterPage struct { + KeywordType ClusterKeywordType `json:"keywordType"` + Keyword string `json:"keyword"` + OrderBy ClusterOrderBy `json:"orderBy"` + Order Order `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + ClusterList []*Cluster `json:"clusterList"` +} + +// ClusterKeywordType 集群模糊查询字段 +type ClusterKeywordType string + +const ( + // ClusterKeywordTypeClusterName 集群模糊查询字段: ClusterName + ClusterKeywordTypeClusterName ClusterKeywordType = "clusterName" + // ClusterKeywordTypeClusterID 集群模糊查询字段: ClusterID + ClusterKeywordTypeClusterID ClusterKeywordType = "clusterID" +) + +// ClusterOrderBy 集群查询排序字段 +type ClusterOrderBy string + +const ( + // ClusterOrderByClusterName 集群查询排序字段: ClusterName + ClusterOrderByClusterName ClusterOrderBy = "clusterName" + // ClusterOrderByClusterID 集群查询排序字段: ClusterID + ClusterOrderByClusterID ClusterOrderBy = "clusterID" + // ClusterOrderByCreatedAt 集群查询排序字段: CreatedAt + ClusterOrderByCreatedAt ClusterOrderBy = "createdAt" +) + +// Order 集群查询排序 +type Order string + +const ( + // OrderASC 集群查询排序: 升序 + OrderASC Order = "ASC" + // OrderDESC 集群查询排序: 降序 + OrderDESC Order = "DESC" +) + +const ( + // PageNoDefault 分页查询默认页码 + PageNoDefault int = 1 + // PageSizeDefault 分页查询默认页面元素数目 + PageSizeDefault int = 10 +) + +// GetInstanceResponse - 查询 Instances 返回 +type GetInstanceResponse struct { + Instance *Instance `json:"instance"` + RequestID string `json:"requestID"` +} + +// DeleteInstancesResponse - 删除 Instances 返回 +type DeleteInstancesResponse struct { + RequestID string `json:"requestID"` +} + +// ListInstancesResponse - List Instances 返回 +type ListInstancesResponse struct { + InstancePage *InstancePage `json:"instancePage"` + RequestID string `json:"requestID"` +} + +// GetQuotaResponse - 查询 Quota 返回 +type GetQuotaResponse struct { + types.Quota + RequestID string `json:"requestID"` +} + +// Cluster - Cluster 返回 +type Cluster struct { + Spec *ClusterSpec `json:"spec"` + Status *ClusterStatus `json:"status"` + + CreatedAt time.Time `json:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` +} + +// 作为返回值的ClusterSpec +type ClusterSpec struct { + ClusterID string `json:"clusterID"` + ClusterName string `json:"clusterName"` + ClusterType types.ClusterType `json:"clusterType"` + + Description string `json:"description"` + + K8SVersion types.K8SVersion `json:"k8sVersion"` + + VPCID string `json:"vpcID"` + VPCCIDR string `json:"vpcCIDR"` + + Plugins []string `json:"plugins"` + + MasterConfig types.MasterConfig `json:"masterConfig"` + ContainerNetworkConfig types.ContainerNetworkConfig `json:"containerNetworkConfig"` +} + +// ClusterStatus - Cluster Status +type ClusterStatus struct { + ClusterBLB BLB `json:"clusterBLB"` + + ClusterPhase types.ClusterPhase `json:"clusterPhase"` + + NodeNum int `json:"nodeNum"` +} + +// BLB 定义 BLB 类型 +type BLB struct { + ID string `json:"id"` + VPCIP string `json:"vpcIP"` + EIP string `json:"eip"` +} + +// InstancePage - 节点分页查询返回 +type InstancePage struct { + ClusterID string `json:"clusterID"` + KeywordType InstanceKeywordType `json:"keywordType"` + Keyword string `json:"keyword"` + OrderBy InstanceOrderBy `json:"orderBy"` + Order Order `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + InstanceList []*Instance `json:"instanceList"` +} + +// InstanceKeywordType 节点模糊查询字段 +type InstanceKeywordType string + +const ( + // InstanceKeywordTypeInstanceName 节点模糊查询字段: InstanceName + InstanceKeywordTypeInstanceName InstanceKeywordType = "instanceName" + // InstanceKeywordTypeInstanceID 节点模糊查询字段: InstanceID + InstanceKeywordTypeInstanceID InstanceKeywordType = "instanceID" +) + +// InstanceOrderBy 节点查询排序字段 +type InstanceOrderBy string + +const ( + // InstanceOrderByInstanceName 节点查询排序字段: InstanceName + InstanceOrderByInstanceName InstanceOrderBy = "instanceName" + // InstanceOrderByInstanceID 节点查询排序字段: InstanceID + InstanceOrderByInstanceID InstanceOrderBy = "instanceID" + // InstanceOrderByCreatedAt 节点查询排序字段: CreatedAt + InstanceOrderByCreatedAt InstanceOrderBy = "createdAt" +) + +// Instance - 节点详情 +// 作为sdk返回结果的Instance +type Instance struct { + Spec *types.InstanceSpec `json:"spec"` + Status *InstanceStatus `json:"status"` + + CreatedAt time.Time `json:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` +} + +type InstanceCRD struct { + ObjectMeta `json:"metadata,omitempty"` + + Spec types.InstanceSpec `json:"spec,omitempty"` + Status InstanceStatus `json:"status,omitempty"` +} + +type ClusterCRD struct { + ObjectMeta `json:"metadata,omitempty"` + + Spec types.ClusterSpec `json:"spec,omitempty"` + Status ClusterStatus `json:"status,omitempty"` +} + +type ObjectMeta struct { + Name string `json:"name,omitempty"` + GenerateName string `json:"generateName,omitempty"` + ClusterName string `json:"clusterName,omitempty"` +} + +// InstanceStatus - Instance Status +type InstanceStatus struct { + Machine Machine `json:"machine"` + + InstancePhase types.InstancePhase `json:"instancePhase"` + MachineStatus types.ServerStatus `json:"machineStatus"` +} + +// Machine - 定义机器相关信息 +type Machine struct { + InstanceID string `json:"instanceID"` + + OrderID string `json:"orderID,omitempty"` + + MountList []types.MountConfig `json:"mountList,omitempty"` + + VPCIP string `json:"vpcIP,omitempty"` + VPCIPIPv6 string `json:"vpcIPIPv6,omitempty"` + + EIP string `json:"eip,omitempty"` +} + +// DeleteInstancesRequest - 删除节点请求 +type DeleteInstancesRequest struct { + InstanceIDs []string `json:"instanceIDs,omitempty"` + DeleteOption *types.DeleteOption `json:"deleteOption,omitempty"` +} + +// InstanceKeyType - ListInstanceByPage 参数 +type InstanceKeyType string + +// NetworkConflictType 冲突类型 +type NetworkConflictType string + +const ( + // ContainerCIDRAndNodeCIDRConflict 容器网段和本集群的节点网段冲突 + ContainerCIDRAndNodeCIDRConflict NetworkConflictType = "ContainerCIDRAndNodeCIDR" + // ContainerCIDRAndExistedClusterContainerCIDRConflict 容器网段和 VPC 内已有集群的容器网段冲突 + ContainerCIDRAndExistedClusterContainerCIDRConflict NetworkConflictType = "ContainerCIDRAndExistedClusterContainerCIDR" + // ContainerCIDRAndVPCRouteConflict 容器网段与 VPC 路由冲突 + ContainerCIDRAndVPCRouteConflict NetworkConflictType = "ContainerCIDRAndVPCRoute" + // ClusterIPCIDRAndNodeCIDRConflict ClusterIP 网段与本集群节点网段冲突 + ClusterIPCIDRAndNodeCIDRConflict NetworkConflictType = "ClusterIPCIDRAndNodeCIDR" + // ClusterIPCIDRAndContainerCIDRConflict ClusterIP 网段与本集群容器网段冲突 + ClusterIPCIDRAndContainerCIDRConflict NetworkConflictType = "ClusterIPCIDRAndContainerCIDR" +) + +// PrivateNetString IPv4/IPv6 私有网络地址类型 +type PrivateNetString string + +const ( + // PrivateIPv4Net10 - IPv4 10 段 + PrivateIPv4Net10 PrivateNetString = "10.0.0.0/8" + + // PrivateIPv4Net172 - IPv4 172 段 + PrivateIPv4Net172 PrivateNetString = "172.16.0.0/12" + + // PrivateIPv4Net192 - IPv4 192 段 + PrivateIPv4Net192 PrivateNetString = "192.168.0.0/16" + + // PrivateIPv6Net - IPv6 段 + PrivateIPv6Net PrivateNetString = "fc00::/7" +) + +const ( + // MaxClusterIPServiceNum 集群最大的 ClusterIP Service 数量 + MaxClusterIPServiceNum = 65536 +) + +// CheckContainerNetworkCIDRRequest 包含检查容器网络网段冲突的请求参数 +type CheckContainerNetworkCIDRArgs struct { + VPCID string `json:"vpcID"` + VPCCIDR string `json:"vpcCIDR"` + VPCCIDRIPv6 string `json:"vpcCIDRIPv6"` + ContainerCIDR string `json:"containerCIDR"` + ContainerCIDRIPv6 string `json:"containerCIDRIPv6"` + ClusterIPCIDR string `json:"clusterIPCIDR"` + ClusterIPCIDRIPv6 string `json:"clusterIPCIDRIPv6"` + MaxPodsPerNode int `json:"maxPodsPerNode"` + IPVersion types.ContainerNetworkIPType `json:"ipVersion"` // if not set, set ipv4 +} + +// CheckClusterIPCIDRequest - 检查 ClusterIP CIDR 请求 +type CheckClusterIPCIDRArgs struct { + VPCID string `json:"vpcID"` + VPCCIDR string `json:"vpcCIDR"` + VPCCIDRIPv6 string `json:"vpcCIDRIPv6"` + ClusterIPCIDR string `json:"clusterIPCIDR"` + ClusterIPCIDRIPv6 string `json:"clusterIPCIDRIPv6"` + IPVersion types.ContainerNetworkIPType `json:"ipVersion"` // if not set, set ipv4 +} + +// CheckContainerNetworkCIDRResponse 检查容器网络网段冲突的响应 +type CheckContainerNetworkCIDRResponse struct { + MaxNodeNum int `json:"maxNodeNum"` + NetworkConflictInfo + RequestID string `json:"requestID"` +} + +// CheckClusterIPCIDRResponse - 检查 ClusterIP CIDR 返回 +type CheckClusterIPCIDRResponse struct { + IsConflict bool `json:"isConflict"` + ErrMsg string `json:"errMsg"` + RequestID string `json:"requestID"` +} + +// RecommendContainerCIDRRequest 推荐容器网段的请求参数 +type RecommendContainerCIDRArgs struct { + VPCID string `json:"vpcID"` + VPCCIDR string `json:"vpcCIDR"` + VPCCIDRIPv6 string `json:"vpcCIDRIPv6"` + // ClusterMaxNodeNum 集群节点的最大规模 + ClusterMaxNodeNum int `json:"clusterMaxNodeNum"` + MaxPodsPerNode int `json:"maxPodsPerNode"` + // PrivateNetCIDRs 候选的容器网段列表,只能从 [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16] 里选择 + PrivateNetCIDRs []PrivateNetString `json:"privateNetCIDRs"` + PrivateNetCIDRIPv6s []PrivateNetString `json:"privateNetCIDRIPv6s"` + K8SVersion types.K8SVersion `json:"k8sVersion"` + IPVersion types.ContainerNetworkIPType `json:"ipVersion"` // if not set, set ipv4 +} + +// RecommendContainerCIDRResponse 推荐容器网段的响应 +type RecommendContainerCIDRResponse struct { + RecommendedContainerCIDRs []string `json:"recommendedContainerCIDRs"` + RecommendedContainerCIDRIPv6s []string `json:"recommendedContainerCIDRIPv6s"` + IsSuccess bool `json:"isSuccess"` + ErrMsg string `json:"errMsg"` + RequestID string `json:"requestID"` +} + +// RecommendClusterIPCIDRRequest 推荐 ClusterIP 网段的请求参数 +type RecommendClusterIPCIDRArgs struct { + VPCCIDR string `json:"vpcCIDR"` + VPCCIDRIPv6 string `json:"vpcCIDRIPv6"` + ContainerCIDR string `json:"containerCIDR"` + ContainerCIDRIPv6 string `json:"containerCIDRIPv6"` + // ClusterMaxServiceNum 集群 Service 最大规模 + ClusterMaxServiceNum int `json:"clusterMaxServiceNum"` + // PrivateNetCIDRs 候选的 ClusterIP 网段列表,只能从 [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16] 里选择 + PrivateNetCIDRs []PrivateNetString `json:"privateNetCIDRs"` + PrivateNetCIDRIPv6s []PrivateNetString `json:"privateNetCIDRIPv6s"` + IPVersion types.ContainerNetworkIPType `json:"ipVersion"` // if not set, set ipv4 +} + +// RecommendClusterIPCIDRResponse 推荐 ClusterIP 网段的响应 +type RecommendClusterIPCIDRResponse struct { + RecommendedClusterIPCIDRs []string `json:"recommendedClusterIPCIDRs"` + RecommendedClusterIPCIDRIPv6s []string `json:"recommendedClusterIPCIDRIPv6s"` + IsSuccess bool `json:"isSuccess"` + ErrMsg string `json:"errMsg"` + RequestID string `json:"requestID"` +} + +// NetworkConflictInfo 容器网络整体配置冲突信息 +type NetworkConflictInfo struct { + IsConflict bool `json:"isConflict"` + ErrMsg string `json:"errMsg"` + ContainerCIDRConflict *ContainerCIDRConflict `json:"containerCIDRConflict"` // 容器网段冲突信息 + ClusterIPCIDRConflict *ClusterIPCIDRConflict `json:"clusterIPCIDRConflict"` // ClusterIP 网段冲突信息 +} + +// ContainerCIDRConflict 容器网段冲突信息 +type ContainerCIDRConflict struct { + // NetworkConflictType 冲突类型,可取的值: ContainerCIDRAndNodeCIDRConflict、ContainerCIDRAndExistedClusterContainerCIDRConflict、ContainerCIDRAndVPCRouteConflict + ConflictType NetworkConflictType `json:"conflictType"` + // ConflictNodeCIDR 与容器网段冲突的节点网段,当且仅当 NetworkConflictType 为 ContainerCIDRAndNodeCIDRConflict 不为 nil + ConflictNodeCIDR *ConflictNodeCIDR `json:"conflictNodeCIDR"` + // ConflictCluster 与容器网段冲突的VPC内集群,当且仅当 NetworkConflictType 为 ContainerCIDRAndExistedClusterContainerCIDRConflict 不为 nil + ConflictCluster *ConflictCluster `json:"conflictCluster"` + // ConflictVPCRoute 与容器网段冲突的VPC路由,当且仅当 NetworkConflictType 为 ContainerCIDRAndVPCRouteConflict 不为 nil + ConflictVPCRoute *ConflictVPCRoute `json:"conflictVPCRoute"` +} + +// ClusterIPCIDRConflict ClusterIP 网段冲突信息 +type ClusterIPCIDRConflict struct { + // NetworkConflictType 冲突类型,可取的值: ClusterIPCIDRAndNodeCIDRConflict、ClusterIPCIDRAndContainerCIDRConflict + ConflictType NetworkConflictType `json:"conflictType"` + // ConflictNodeCIDR 与 ClusterIP 网段冲突的节点网段,当且仅当 NetworkConflictType 为 ClusterIPCIDRAndNodeCIDRConflict 不为 nil + ConflictNodeCIDR *ConflictNodeCIDR `json:"conflictNodeCIDR"` + // ConflictContainerCIDR 与 ClusterIP 网段冲突的节点网段,当且仅当 NetworkConflictType 为 ClusterIPCIDRAndContainerCIDRConflict 不为 nil + ConflictContainerCIDR *ConflictContainerCIDR `json:"conflictContainerCIDR"` +} + +// ConflictNodeCIDR 节点网段冲突信息 +type ConflictNodeCIDR struct { + NodeCIDR string `json:"nodeCIDR"` +} + +// ConflictContainerCIDR 容器网段冲突信息 +type ConflictContainerCIDR struct { + ContainerCIDR string `json:"containerCIDR"` +} + +// ConflictCluster 同一 VPC 内容器网段冲突的集群信息 +type ConflictCluster struct { + ClusterID string `json:"clusterID"` + ContainerCIDR string `json:"containerCIDR"` +} + +// ConflictVPCRoute 冲突的 VPC 路由 +type ConflictVPCRoute struct { + RouteRule vpc.RouteRule `json:"routeRule"` +} + +type InstanceTemplate struct { + types.InstanceSpec `json:",inline"` +} + +type CommonResponse struct { + RequestID string `json:"requestID"` +} + +type InstanceGroup struct { + Spec *InstanceGroupSpec `json:"spec"` + Status *InstanceGroupStatus `json:"status"` + CreatedAt time.Time `json:"createdAt"` +} + +type InstanceGroupSpec struct { + CCEInstanceGroupID string `json:"cceInstanceGroupID,omitempty"` + InstanceGroupName string `json:"instanceGroupName"` + + ClusterID string `json:"clusterID,omitempty"` + ClusterRole types.ClusterRole `json:"clusterRole,omitempty"` + ShrinkPolicy ShrinkPolicy `json:"shrinkPolicy,omitempty"` + UpdatePolicy UpdatePolicy `json:"updatePolicy,omitempty"` + CleanPolicy CleanPolicy `json:"cleanPolicy,omitempty"` + + InstanceTemplate InstanceTemplate `json:"instanceTemplate"` + Replicas int `json:"replicas"` + + ClusterAutoscalerSpec *ClusterAutoscalerSpec `json:"clusterAutoscalerSpec,omitempty"` +} + +type ShrinkPolicy string +type UpdatePolicy string +type CleanPolicy string + +type ClusterAutoscalerSpec struct { + Enabled bool `json:"enabled"` + MinReplicas int `json:"minReplicas"` + MaxReplicas int `json:"maxReplicas"` + ScalingGroupPriority int `json:"scalingGroupPriority"` +} + +// InstanceGroupStatus - +type InstanceGroupStatus struct { + ReadyReplicas int `json:"readyReplicas"` + Pause *PauseDetail `json:"pause,omitempty"` +} + +type PauseDetail struct { + Paused bool `json:"paused"` + Reason string `json:"reason"` +} + +// CreateInstanceGroupRequest - 创建InstanceGroup request +type CreateInstanceGroupRequest struct { + types.InstanceGroupSpec +} + +// CreateInstanceGroupResponse - 创建InstanceGroup response +type CreateInstanceGroupResponse struct { + CommonResponse + InstanceGroupID string `json:"instanceGroupID"` +} + +type ListInstanceGroupResponse struct { + CommonResponse + Page ListInstanceGroupPage `json:"page"` +} + +type ListInstanceGroupPage struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + List []*InstanceGroup `json:"list"` +} + +type GetInstanceGroupResponse struct { + CommonResponse + InstanceGroup *InstanceGroup `json:"instanceGroup"` +} + +type UpdateInstanceGroupReplicasRequest struct { + Replicas int `json:"replicas"` + InstanceIDs []string `json:"instanceIDs"` + DeleteInstance bool `json:"deleteInstance"` + DeleteOption *types.DeleteOption `json:"deleteOption,omitempty"` +} + +type UpdateInstanceGroupReplicasResponse struct { + CommonResponse +} + +type UpdateInstanceGroupClusterAutoscalerSpecResponse struct { + CommonResponse +} + +type DeleteInstanceGroupResponse struct { + CommonResponse +} + +type ListInstancesByInstanceGroupIDPage struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + List []*Instance `json:"list"` +} + +type ListInstancesByInstanceGroupIDResponse struct { + CommonResponse + Page ListInstancesByInstanceGroupIDPage `json:"page"` +} + +type GetAutoscalerArgs struct { + ClusterID string +} + +type GetAutoscalerResponse struct { + Autoscaler *Autoscaler `json:"autoscaler"` + RequestID string `json:"requestID"` +} + +type UpdateAutoscalerArgs struct { + ClusterID string + AutoscalerConfig ClusterAutoscalerConfig +} + +type UpdateAutoscalerResponse struct { + CommonResponse +} + +type CreateAutoscalerArgs struct { + ClusterID string +} + +type CreateAutoscalerResponse struct { + CommonResponse +} + +type Autoscaler struct { + ClusterID string `json:"clusterID"` + ClusterName string `json:"clusterName"` + CAConfig ClusterAutoscalerConfig `json:"caConfig,omitempty"` +} + +type ClusterAutoscalerConfig struct { + KubeVersion string `json:"kubeVersion,omitempty"` + ReplicaCount int `json:"replicaCount"` + InstanceGroups []ClusterAutoscalerInstanceGroup `json:"instanceGroups,omitempty"` + // default: false + ScaleDownEnabled bool `json:"scaleDownEnabled"` + // 可选,缩容阈值百分比,范围(0, 100) + ScaleDownUtilizationThreshold *int `json:"scaleDownUtilizationThreshold,omitempty"` + // 可选,GPU缩容阈值百分比,范围(0, 100) + ScaleDownGPUUtilizationThreshold *int `json:"scaleDownGPUUtilizationThreshold,omitempty"` + // 可选,缩容触发时延,单位:m + ScaleDownUnneededTime *int `json:"scaleDownUnneededTime,omitempty"` + // 可选,扩容后缩容启动时延,单位:m + ScaleDownDelayAfterAdd *int `json:"scaleDownDelayAfterAdd,omitempty"` + // 可选,最大并发缩容数 + MaxEmptyBulkDelete *int `json:"maxEmptyBulkDelete,omitempty"` + // 可选, + SkipNodesWithLocalStorage *bool `json:"skipNodesWithLocalStorage,omitempty"` + // 可选, + SkipNodesWithSystemPods *bool `json:"skipNodesWithSystemPods,omitempty"` + // supported: random, most-pods, least-waste, priority; default: random + Expander string `json:"expander"` +} + +type ClusterAutoscalerInstanceGroup struct { + InstanceGroupID string + MinReplicas int + MaxReplicas int + Priority int +} + +type InstanceGroupListOption struct { + PageNo int + PageSize int +} + +type CreateInstanceGroupArgs struct { + ClusterID string + Request *CreateInstanceGroupRequest +} + +type ListInstanceGroupsArgs struct { + ClusterID string + ListOption *InstanceGroupListOption +} + +type ListInstanceByInstanceGroupIDArgs struct { + ClusterID string + InstanceGroupID string + PageNo int + PageSize int +} + +type GetInstanceGroupArgs struct { + ClusterID string + InstanceGroupID string +} + +type UpdateInstanceGroupClusterAutoscalerSpecArgs struct { + ClusterID string + InstanceGroupID string + Request *ClusterAutoscalerSpec +} + +type UpdateInstanceGroupReplicasArgs struct { + ClusterID string + InstanceGroupID string + Request *UpdateInstanceGroupReplicasRequest +} + +type DeleteInstanceGroupArgs struct { + ClusterID string + InstanceGroupID string + DeleteInstances bool + ReleaseAllResources bool +} + +// KubeConfigType - kube config 类型 +type KubeConfigType string + +const ( + // KubeConfigTypeInternal 使用 BLB FloatingIP + KubeConfigTypeInternal KubeConfigType = "internal" + + // KubeConfigTypeVPC 使用 BLB VPCIP + KubeConfigTypeVPC KubeConfigType = "vpc" + + // KubeConfigTypePublic 使用 BLB EIP + KubeConfigTypePublic KubeConfigType = "public" +) + +type GetKubeConfigArgs struct { + ClusterID string + KubeConfigType KubeConfigType +} + +// GetKubeConfigResponse - 查询 KubeConfig 返回 +type GetKubeConfigResponse struct { + KubeConfigType KubeConfigType `json:"kubeConfigType"` + KubeConfig string `json:"kubeConfig"` + RequestID string `json:"requestID"` +} + +func CheckKubeConfigType(kubeConfigType string) error { + if kubeConfigType != string(KubeConfigTypePublic) && + kubeConfigType != string(KubeConfigTypeInternal) && + kubeConfigType != string(KubeConfigTypeVPC) { + return fmt.Errorf("KubeConfigType %s not valid", kubeConfigType) + } + return nil +} + +type CreateScaleUpInstanceGroupTaskArgs struct { + ClusterID string + InstanceGroupID string + TargetReplicas int +} + +type CreateScaleDownInstanceGroupTaskArgs struct { + ClusterID string + InstanceGroupID string + InstancesToBeRemoved []string +} + +type CreateTaskResp struct { + CommonResponse + TaskID string `json:"taskID"` +} + +type GetTaskArgs struct { + TaskType types.TaskType + TaskID string +} + +type ListTasksArgs struct { + TaskType types.TaskType + TargetID string + PageNo int + PageSize int +} + +type GetTaskResp struct { + CommonResponse + Task *types.Task `json:"task"` +} + +type ListTaskResp struct { + CommonResponse + Page ListTaskPage +} + +type ListTaskPage struct { + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` + TotalCount int `json:"totalCount"` + Items []*types.Task `json:"items"` +} + +type UpdateInstanceCRDRequest struct { + Instance *InstanceCRD `json:"instance"` +} + +type GetInstanceCRDResponse struct { + Instance *InstanceCRD `json:"instance"` + RequestID string `json:"requestID"` +} + +type GetClusterCRDArgs struct { + ClusterID string `json:"clusterID"` +} + +type GetClusterCRDResponse struct { + Cluster *ClusterCRD `json:"cluster"` + RequestID string `json:"requestID"` +} + +type UpdateClusterCRDArgs struct { + Cluster *ClusterCRD `json:"cluster"` +} + +type UpdateClusterCRDResponses struct { +} diff --git a/bce-sdk-go/services/cce/v2/types/bcc.go b/bce-sdk-go/services/cce/v2/types/bcc.go new file mode 100644 index 0000000..441e893 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/bcc.go @@ -0,0 +1,76 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +import bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + +const ( + // InstanceTypeDCC DCC 类型 + InstanceTypeDCC bccapi.InstanceType = "DCC" + // InstanceTypeBBC BBC 类型 + InstanceTypeBBC bccapi.InstanceType = "BBC" + // InstanceTypeBBCGPU BBC GPU 类型 + InstanceTypeBBCGPU bccapi.InstanceType = "BBC_GPU" +) + +// SecurityGroupRule 安全组规则 +type SecurityGroupRule struct { + SecurityGroupID string `json:"securityGroupId"` + EtherType EtherType `json:"ethertype"` + Direction Direction `json:"direction"` + Protocol Protocol `json:"protocol"` + SourceGroupID string `json:"sourceGroupId"` + SourceIP string `json:"sourceIp"` + DestGroupID string `json:"destGroupId"` + DestIP string `json:"destIp"` + PortRange string `json:"portRange"` + Remark string `json:"remark"` +} + +type Direction string + +const ( + DirectionIngress Direction = "ingress" + DirectionEgress Direction = "egress" +) + +type EtherType string + +const ( + EtherTypeIPv4 EtherType = "IPv4" + EtherTypeIPv6 EtherType = "IPv6" +) + +type Protocol string + +const ( + ProtocolAll Protocol = "all" + ProtocolTCP Protocol = "tcp" + ProtocolUDP Protocol = "udp" + ProtocolICMP Protocol = "icmp" +) + +// GPUType GPU 类型 +type GPUType string + +const ( + // GPUTypeV100_32 NVIDIA Tesla V100-32G + GPUTypeV100_32 GPUType = "V100-32" + // GPUTypeV100_16 NVIDIA Tesla V100-16G + GPUTypeV100_16 GPUType = "V100-16" + // GPUTypeP40 P40 NVIDIA Tesla P40 + GPUTypeP40 GPUType = "P40" + // GPUTypeP4 P4 NVIDIA Tesla P4 + GPUTypeP4 GPUType = "P4" + // GPUTypeK40 K40 NVIDIA Tesla K40 + GPUTypeK40 GPUType = "K40" + // GPUTypeDLCard NVIDIA 深度学习开发卡 + GPUTypeDLCard GPUType = "DLCard" +) diff --git a/bce-sdk-go/services/cce/v2/types/bccimage.go b/bce-sdk-go/services/cce/v2/types/bccimage.go new file mode 100644 index 0000000..9c9f87f --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/bccimage.go @@ -0,0 +1,42 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +import bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + +const ( + ImageTypeService bccapi.ImageType = "service" +) + +// OSType 操作系统类型 +type OSType string + +const ( + // OSTypeLinux linux + OSTypeLinux OSType = "linux" + // OSTypeWindows windows + OSTypeWindows OSType = "windows" +) + +// OSName 操作系统名字 +type OSName string + +const ( + // OSNameCentOS centos + OSNameCentOS OSName = "CentOS" + // OSNameUbuntu ubuntu + OSNameUbuntu OSName = "Ubuntu" + // OSNameWindows windows + OSNameWindows OSName = "Windows Server" + // OSNameDebian debian + OSNameDebian OSName = "Debian" + // OSNameOpensuse opensuse + OSNameOpensuse OSName = "opensuse" +) diff --git a/bce-sdk-go/services/cce/v2/types/cce_supported.go b/bce-sdk-go/services/cce/v2/types/cce_supported.go new file mode 100644 index 0000000..332dd2f --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/cce_supported.go @@ -0,0 +1,111 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +import ( + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" +) + +// SupportedInstanceType - CCE 支持的 Instance 类型 +var SupportedInstanceType = map[bccapi.InstanceType]string{ + bccapi.InstanceTypeN1: "", + bccapi.InstanceTypeN2: "", + bccapi.InstanceTypeN3: "", + bccapi.InstanceTypeN4: "", + bccapi.InstanceTypeN5: "", + bccapi.InstanceTypeC1: "", + bccapi.InstanceTypeC2: "", + bccapi.InstanceTypeS1: "", + bccapi.InstanceTypeG1: "", + bccapi.InstanceTypeF1: "", + // 以下为 CCE 自行定义 + InstanceTypeDCC: "", + InstanceTypeBBC: "", + InstanceTypeBBCGPU: "", +} + +// SupportedStorageType - CCE 支持的 Storage 类型 +var SupportedStorageType = map[bccapi.StorageType]string{ + bccapi.StorageTypeStd1: "", + bccapi.StorageTypeHP1: "", + bccapi.StorageTypeCloudHP1: "", +} + +// SupportedRootDiskStorageType - CCE 支持的 RootDiskStorage 类型 +var SupportedRootDiskStorageType = map[bccapi.StorageType]string{ + bccapi.StorageTypeHP1: "", + bccapi.StorageTypeCloudHP1: "", +} + +// SupportedGPUType - CCE 支持的 GPU 类型 +var SupportedGPUType = map[GPUType]string{ + GPUTypeV100_32: "", + GPUTypeV100_16: "", + GPUTypeP40: "", + GPUTypeP4: "", + GPUTypeK40: "", + GPUTypeDLCard: "", +} + +// SupportedK8SVersions - CCE 支持的 K8s 版本 +var SupportedK8SVersions = map[K8SVersion]string{ + K8S_1_13_10: "", + K8S_1_16_8: "", +} + +// SupportedClusterHA - CCE 支持的 ClusterHA 类型 +var SupportedClusterHA = map[ClusterHA]string{ + ClusterHALow: "", + ClusterHAMedium: "", + ClusterHAHigh: "", +} + +// SupportedMasterType - CCE 支持 Master 类型 +var SupportedMasterType = map[MasterType]string{ + MasterTypeManaged: "", + MasterTypeCustom: "", + MasterTypeServerless: "", +} + +// SupportedImageType - CCE 支持镜像类型 +var SupportedImageType = map[bccapi.ImageType]string{ + bccapi.ImageTypeSystem: "", + bccapi.ImageTypeCustom: "", + bccapi.ImageTypeGPUSystem: "", + bccapi.ImageTypeGPUCustom: "", + bccapi.ImageTypeSharing: "", + bccapi.ImageTypeIntegration: "", + ImageTypeService: "", + // ImageTypeBBCSystem BBC 公有 + bccapi.ImageTypeBBCSystem: "", + // ImageTypeBBCCustom BBC 自定义 + bccapi.ImageTypeBBCCustom: "", +} + +// SupportedContainerNetworkMode - CCE 支持的容器网络类型 +var SupportedContainerNetworkMode = map[ContainerNetworkMode]string{ + ContainerNetworkModeKubenet: "", + ContainerNetworkModeVPCCNI: "", + ContainerNetworkModeVPCRouteAutoDetect: "", + ContainerNetworkModeVPCRouteVeth: "", + ContainerNetworkModeVPCRouteIPVlan: "", + ContainerNetworkModeVPCSecondaryIPAutoDetect: "", + ContainerNetworkModeVPCSecondaryIPVeth: "", + ContainerNetworkModeVPCSecondaryIPIPVlan: "", +} + +var SupportedRuntimeType = map[RuntimeType]string{ + RuntimeTypeDocker: "", +} + +var SupportedKubeProxyMode = map[KubeProxyMode]string{ + KubeProxyModeIptables: "", + KubeProxyModeIPVS: "", +} diff --git a/bce-sdk-go/services/cce/v2/types/cluster.go b/bce-sdk-go/services/cce/v2/types/cluster.go new file mode 100644 index 0000000..9e6ea05 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/cluster.go @@ -0,0 +1,291 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +const ( + // LatestSupportedK8SVersion latest K8S Version that we supported + LatestSupportedK8SVersion = "1.16.8" + + // DefaultRuntime default runtime + DefaultRuntime = "docker" + + // LatestSupportedDockerVersion default docker version + LatestSupportedDockerVersion = "18.09.2" + + CCEPrefix = "cce-" + + // ClusterIDLabelKey 关联 ClusterCRD 和 InstanceCRD 或 InstanceGroupCRD + ClusterIDLabelKey = "cluster-id" + + ClusterRoleLabelKey = "cluster-role" + + DoNotHandle = "not-handler-by-cce" +) + +// 创建集群时使用的ClusterSpec +type ClusterSpec struct { + + // 创建集群时无需传入ClusterID + ClusterID string `json:"clusterID,omitempty" ` + + // ClusterName 由用户指定 + ClusterName string `json:"clusterName" valid:"Required"` + + ClusterType ClusterType `json:"clusterType,omitempty" valid:"Required"` + + Description string `json:"description,omitempty"` + + K8SVersion K8SVersion `json:"k8sVersion,omitempty"` + + RuntimeType RuntimeType `json:"runtimeType,omitempty"` + RuntimeVersion string `json:"runtimeVersion,omitempty"` + + // VPCCIDR 无需用户设置 + VPCID string `json:"vpcID,omitempty" valid:"Required"` + VPCCIDR string `json:"vpcCIDR,omitempty"` + VPCCIDRIPv6 string `json:"vpcCIDRIPv6,omitempty"` + + // PluginListType CCE 插件类型 + Plugins []string `json:"plugins,omitempty"` + + MasterConfig MasterConfig `json:"masterConfig,omitempty" valid:"Required"` + ContainerNetworkConfig ContainerNetworkConfig `json:"containerNetworkConfig,omitempty" valid:"Required"` + + AuthenticateMode AuthenticateMode `json:"authenticateMode,omitempty"` // APIServer 认证方式 + + // K8S 自定义配置 + K8SCustomConfig K8SCustomConfig `json:"k8sCustomConfig,omitempty"` +} + +// K8SCustomConfig - K8S 自定义配置 +type K8SCustomConfig struct { + MasterFeatureGates map[string]bool `json:"masterFeatureGates,omitempty"` // 自定义 FeatureGates + NodeFeatureGates map[string]bool `json:"nodeFeatureGates,omitempty"` // 自定义 FeatureGates + AdmissionPlugins []string `json:"admissionPlugins,omitempty"` // 自定义 AdmissionPlugins + PauseImage string `json:"pauseImage,omitempty"` // 自定义 PauseImage + KubeAPIQPS int `json:"kubeAPIQPS,omitempty"` // 自定义 KubeAPIQPS + KubeAPIBurst int `json:"kubeAPIBurst,omitempty"` // 自定义 KubeAPIBurst + SchedulerPredicates []string `json:"schedulerPredicates,omitempty"` // 自定义 SchedulerPredicates + SchedulerPriorities map[string]int `json:"schedulerPriorities,omitempty"` // 自定义 SchedulerPriorities + ETCDDataPath string `json:"etcdDataPath,omitempty"` // 自定义 etcd数据目录 +} + +// ClusterType usually used to init Provider +// and it represents the difference between IaaS +type ClusterType string + +const ( + // ClusterTypeNormal = 普通类型集群 + ClusterTypeNormal ClusterType = "normal" +) + +// K8SVersion defines the k8stypes version of cluster +type K8SVersion string + +const ( + //1.6和1.8不再支持,扩缩容需要联系CCE人员手动操作 + //K8S_1_6_2 K8SVersion = "1.6.2" + //K8S_1_8_6 K8SVersion = "1.8.6" + //K8S_1_8_12 K8SVersion = "1.8.12" + //1.11.1 1.11.5 1.13.4仅支持已有集群扩容节点,不支持新创建集群 + //K8S_1_11_1 K8SVersion = "1.11.1" + //K8S_1_11_5 K8SVersion = "1.11.5" + //K8S_1_13_4 K8SVersion = "1.13.4" + //支持在console创建集群 + K8S_1_13_10 K8SVersion = "1.13.10" + //K8S_1_16_3 K8SVersion = "1.16.3" + K8S_1_16_8 K8SVersion = "1.16.8" +) + +// MasterConfig Master 配置 +type MasterConfig struct { + // MasterTypes: 托管, 自定义, 已有 BCC, 已有 BBC + MasterType MasterType `json:"masterType,omitempty"` + + // ClusterHA 对 3 种集群都有效: 对于 Custom 和 Existed 作为校验和展示作用 + ClusterHA ClusterHA `json:"clusterHA,omitempty"` + + ExposedPublic bool `json:"exposedPublic,omitempty"` + + ClusterBLBVPCSubnetID string `json:"clusterBLBVPCSubnetID,omitempty"` + + ManagedClusterMasterOption `json:"managedClusterMasterOption,omitempty"` +} + +// ManagedClusterMasterOption 托管集群 Master 配置 +type ManagedClusterMasterOption struct { + MasterVPCSubnetZone AvailableZone `json:"masterVPCSubnetZone,omitempty"` +} + +// RuntimeType defines the runtime on each node +type RuntimeType string + +const ( + RuntimeTypeDocker RuntimeType = "docker" +) + +// ContainerNetworkConfig defines the network config +// Some attrs have default value +type ContainerNetworkConfig struct { + // CCE 支持网络类型: kubenet 及 vpc-cni + Mode ContainerNetworkMode `json:"mode,omitempty"` // If not set, set mode = kubenet + + // ENI 网络模式子网 + ENIVPCSubnetIDs map[AvailableZone][]string `json:"eniVPCSubnetIDs,omitempty"` + ENISecurityGroupID string `json:"eniSecurityGroupID,omitempty"` + + // CCE 支持集群 IP version: dual stack, ipv4 only, ipv6 only + IPVersion ContainerNetworkIPType `json:"ipVersion,omitempty"` // if not set, set ipv4 + + // LB Service 关联 BLB 所在子网, 目前只能为普通子网 + LBServiceVPCSubnetID string `json:"lbServiceVPCSubnetID,omitempty" valid:"Required"` + + // 指定 NodePort Service 的端口范围 + NodePortRangeMin int `json:"nodePortRangeMin,omitempty"` + NodePortRangeMax int `json:"nodePortRangeMax,omitempty"` + + // 集群 PodIP CIDR, 在 kubenet 网络模式下有效 + ClusterPodCIDR string `json:"clusterPodCIDR,omitempty"` + ClusterPodCIDRIPv6 string `json:"clusterPodCIDRIPv6,omitempty"` + + // Service ClusterIP 的 CIDR + ClusterIPServiceCIDR string `json:"clusterIPServiceCIDR,omitempty"` + ClusterIPServiceCIDRIPv6 string `json:"clusterIPServiceCIDRIPv6,omitempty"` + + // 每个 Node 上最大的 Pod 数, 限制 NodeCIDR 的分配 + MaxPodsPerNode int `json:"maxPodsPerNode,omitempty"` // If not set, MaxPodsPerNode = 128 + + // KubeProxy 的模式: iptables 和 ipvs + KubeProxyMode KubeProxyMode `json:"kubeProxyMode,omitempty"` // If not set, kubeProxyMode=ipvs +} + +// ContainerNetworkIPType - 容器 IP 类型 +type ContainerNetworkIPType string + +const ( + // ContainerNetworkIPTypeIPv4 - 容器网段 IPv4 + ContainerNetworkIPTypeIPv4 ContainerNetworkIPType = "ipv4" + // ContainerNetworkIPTypeIPv6 - 容器网段 IPv6 + ContainerNetworkIPTypeIPv6 ContainerNetworkIPType = "ipv6" + // ContainerNetworkIPTypeDualStack - 容器网段双栈 + ContainerNetworkIPTypeDualStack ContainerNetworkIPType = "dualStack" +) + +// ContainerNetworkMode defines container config +type ContainerNetworkMode string + +const ( + // ContainerNetworkModeKubenet using kubenet + ContainerNetworkModeKubenet ContainerNetworkMode = "kubenet" + + // ContainerNetworkModeVPCCNI using vpc-cni + ContainerNetworkModeVPCCNI ContainerNetworkMode = "vpc-cni" + + // ContainerNetworkModeVPCRouteVeth using vpc route plus veth + ContainerNetworkModeVPCRouteVeth ContainerNetworkMode = "vpc-route-veth" + + // ContainerNetworkModeVPCRouteIPVlan using vpc route plus ipvlan + ContainerNetworkModeVPCRouteIPVlan ContainerNetworkMode = "vpc-route-ipvlan" + + // ContainerNetworkModeVPCRouteAutoDetect using vpc route and auto detects veth or ipvlan due to kernel version + ContainerNetworkModeVPCRouteAutoDetect ContainerNetworkMode = "vpc-route-auto-detect" + + // ContainerNetworkModeVPCSecondaryIPVeth using vpc secondary ip plus veth + ContainerNetworkModeVPCSecondaryIPVeth ContainerNetworkMode = "vpc-secondary-ip-veth" + + // ContainerNetworkModeVPCSecondaryIPIPVlan using vpc secondary ip plus ipvlan + ContainerNetworkModeVPCSecondaryIPIPVlan ContainerNetworkMode = "vpc-secondary-ip-ipvlan" + + // ContainerNetworkModeVPCSecondaryIPAutoDetect using vpc secondary ip and auto detects veth or ipvlan due to kernel version + ContainerNetworkModeVPCSecondaryIPAutoDetect ContainerNetworkMode = "vpc-secondary-ip-auto-detect" +) + +// KubeProxyMode defines kube-proxy --proxy-mode +// If not set, using KubeProxyModeIPVS as default +type KubeProxyMode string + +const ( + // KubeProxyModeIPVS --proxy-mode=ipvs + KubeProxyModeIPVS KubeProxyMode = "ipvs" + + // KubeProxyModeIptables --proxy-mode=iptables + KubeProxyModeIptables KubeProxyMode = "iptables" +) + +// MasterType 定义 Master 机器来源 +type MasterType string + +const ( + // MasterTypeManaged 托管 Master + MasterTypeManaged MasterType = "managed" + + // MasterTypeCustom 自定义集群, 包含: + // 1. 新建 BCC; + // 2. 已有 BCC; + // 3. 已有 BBC. + MasterTypeCustom MasterType = "custom" + + // MasterTypeServerless Serverless集群Master + MasterTypeServerless MasterType = "serverless" +) + +// ClusterHA Cluster Master 对应副本数 +type ClusterHA int + +const ( + // ClusterHALow 单 Master + ClusterHALow ClusterHA = 1 + // ClusterHAMedium 三 Master + ClusterHAMedium ClusterHA = 3 + // ClusterHAHigh 五 Master + ClusterHAHigh ClusterHA = 5 + // ClusterHAServerless Cluster Master 副本数 + ClusterHAServerless ClusterHA = 2 +) + +// ClusterPhase for CCE K8S Cluster Phase +type ClusterPhase string + +const ( + // ClusterPhasePending 创建 Cluster 时默认状态 + ClusterPhasePending ClusterPhase = "pending" + + // ClusterPhaseProvisioning IaaS 相关资源正在创建中 + ClusterPhaseProvisioning ClusterPhase = "provisioning" + + // ClusterPhaseProvisioned IaaS 相关资源已经 Ready + ClusterPhaseProvisioned ClusterPhase = "provisioned" + + // ClusterPhaseRunning 集群运行正常 + ClusterPhaseRunning ClusterPhase = "running" + + // ClusterPhaseCreateFailed 集群创建失败 + ClusterPhaseCreateFailed ClusterPhase = "create_failed" + + // ClusterPhaseDeleting 集群正在删除 + ClusterPhaseDeleting ClusterPhase = "deleting" + + // ClusterPhaseDeleted 集群删除完成 + ClusterPhaseDeleted ClusterPhase = "deleted" + + // ClusterPhaseDeleteFailed 集群删除失败 + ClusterPhaseDeleteFailed ClusterPhase = "delete_failed" +) + +// AuthenticateMode - 认证类型 +type AuthenticateMode string + +const ( + // AuthenticateModeX509 - X509 + AuthenticateModeX509 AuthenticateMode = "x509" + + // AuthenticateModeOIDC - OIDC + AuthenticateModeOIDC AuthenticateMode = "oidc" +) diff --git a/bce-sdk-go/services/cce/v2/types/eip.go b/bce-sdk-go/services/cce/v2/types/eip.go new file mode 100644 index 0000000..05f4afd --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/eip.go @@ -0,0 +1,30 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +// BillingMethod 计费方式 +type BillingMethod string + +const ( + // BillingMethodByTraffic 按照流量计费 + BillingMethodByTraffic BillingMethod = "ByTraffic" + // BillingMethodByBandwidth 按带宽计费 + BillingMethodByBandwidth BillingMethod = "ByBandwidth" +) + +// PaymentTiming 付费时间选择 +type PaymentTiming string + +const ( + // PaymentTimingPrepaid 预付费 + PaymentTimingPrepaid PaymentTiming = "Prepaid" + // PaymentTimingPostpaid 后付费 + PaymentTimingPostpaid PaymentTiming = "Postpaid" +) diff --git a/bce-sdk-go/services/cce/v2/types/instance.go b/bce-sdk-go/services/cce/v2/types/instance.go new file mode 100644 index 0000000..9b8064e --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/instance.go @@ -0,0 +1,368 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +import ( + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + "github.com/baidubce/bce-sdk-go/services/vpc" +) + +// 已有节点需要用户提供:ClusterRole 、短ID,密码,镜像ID,镜像类型, docker storage(可选); BBC要额外加preservedData、raidId、sysRootSize +type InstanceSpec struct { + + // 用于 CCE 唯一标识 Instance + CCEInstanceID string `json:"cceInstanceID,omitempty"` + InstanceName string `json:"instanceName"` + + RuntimeType RuntimeType `json:"runtimeType,omitempty"` + RuntimeVersion string `json:"runtimeVersion,omitempty"` + + ClusterID string `json:"clusterID,omitempty"` + ClusterRole ClusterRole `json:"clusterRole,omitempty"` + + InstanceGroupID string `json:"instanceGroupID,omitempty"` + InstanceGroupName string `json:"instanceGroupName,omitempty"` + + // 初始化 DelProvider 使用 + MasterType MasterType `json:"masterType,omitempty"` + + // 是否为已有实例 + Existed bool `json:"existed,omitempty"` + ExistedOption ExistedOption `json:"existedOption,omitempty"` + + // BCC, BBC, 裸金属 + MachineType MachineType `json:"machineType,omitempty"` + // 机器规格: 普通一, 普通二 ... + InstanceType bccapi.InstanceType `json:"instanceType"` + // BBC 选项 + BBCOption *BBCOption `json:"bbcOption,omitempty"` + + // 是否为竞价实例 + Bid bool `json:"bid,omitempty"` + BidOption BidOption `json:"bidOption,omitempty"` + + // VPC 相关配置 + VPCConfig VPCConfig `json:"vpcConfig,omitempty"` + + // 集群规格相关配置 + InstanceResource InstanceResource `json:"instanceResource,omitempty"` + + // 优先使用 ImageID, 如果用户传入 InstanceOS 信息, 由 service 计算 ImageID + ImageID string `json:"imageID,omitempty"` + InstanceOS InstanceOS `json:"instanceOS,omitempty"` + + // EIP + NeedEIP bool `json:"needEIP,omitempty"` + EIPOption *EIPOption `json:"eipOption,omitempty"` + + // AdminPassword + AdminPassword string `json:"adminPassword,omitempty"` + SSHKeyID string `json:"sshKeyID,omitempty"` + + // Charging Type, 通常只支持后付费 + InstanceChargingType bccapi.PaymentTimingType `json:"instanceChargingType,omitempty"` // 后付费或预付费 + InstancePreChargingOption InstancePreChargingOption `json:"instancePreChargingOption,omitempty"` + + // 删除节点选项 + DeleteOption *DeleteOption `json:"deleteOption,omitempty"` + + DeployCustomConfig DeployCustomConfig `json:"deployCustomConfig,omitempty"` // 部署相关高级配置 + + Tags TagList `json:"tags,omitempty"` + + Labels InstanceLabels `json:"labels,omitempty"` + Taints InstanceTaints `json:"taints,omitempty"` + Annotations InstanceAnnotations `json:"annotations,omitempty"` + + CCEInstancePriority int `json:"cceInstancePriority,omitempty"` + + AutoSnapshotID string `json:"autoSnapshotID,omitempty"` // 自动快照策略 ID +} + +// VPCConfig 定义 Instance VPC +type VPCConfig struct { + VPCID string `json:"vpcID,omitempty"` + VPCSubnetID string `json:"vpcSubnetID,omitempty"` + SecurityGroupID string `json:"securityGroupID,omitempty"` + + VPCSubnetType vpc.SubnetType `json:"vpcSubnetType,omitempty"` + VPCSubnetCIDR string `json:"vpcSubnetCIDR,omitempty"` + VPCSubnetCIDRIPv6 string `json:"vpcSubnetCIDRIPv6,omitempty"` + + AvailableZone AvailableZone `json:"availableZone,omitempty"` + + SecurityGroup SecurityGroup `json:"securityGroup,omitempty"` +} + +// SecurityGroup 定义 Instance 安全组配置 +type SecurityGroup struct { + // 是否附加 CCE 必须安全组 + EnableCCERequiredSecurityGroup bool `json:"enableCCERequiredSecurityGroup"` + // 是否附加 CCE 可选安全组 + EnableCCEOptionalSecurityGroup bool `json:"enableCCEOptionalSecurityGroup"` + // 用户自定义安全组 ID 列表 + CustomSecurityGroupIDs []string `json:"customSecurityGroups,omitempty"` +} + +// InstanceResource 定义 Instance CPU/MEM/Disk 配置 +type InstanceResource struct { + MachineSpec string `json:"machineSpec,omitempty"` // 机器规格,例:bcc.g5.c2m8 + + CPU int `json:"cpu,omitempty"` // unit: Core + MEM int `json:"mem,omitempty"` // unit: GB + + NodeCPUQuota int `json:"nodeCPUQuota,omitempty"` // unit: Core + NodeMEMQuota int `json:"nodeMEMQuota,omitempty"` // unit: GB + + // RootDisk + RootDiskType bccapi.StorageType `json:"rootDiskType,omitempty"` + RootDiskSize int `json:"rootDiskSize,omitempty"` // unit: GB + + // GPU 机器必须指定, 其他机器不用 + LocalDiskSize int `json:"localDiskSize,omitempty"` // unit: GB + + // CDS 列表, 默认第一块盘作为 docker 和 kubelet 数据盘 + CDSList CDSConfigList `json:"cdsList,omitempty"` + + // Only necessary when InstanceType = GPU + GPUType GPUType `json:"gpuType,omitempty"` + GPUCount int `json:"gpuCount,omitempty"` +} + +// EIPOption 定义 Instance EIP 相关配置 +type EIPOption struct { + EIPName string `json:"eipName,omitempty"` + EIPChargingType BillingMethod `json:"eipChargeType,omitempty"` + EIPBandwidth int `json:"eipBandwidth,omitempty"` +} + +// InstancePreChargingOption 定义付费相关配置 +type InstancePreChargingOption struct { + PurchaseTime int `json:"purchaseTime,omitempty"` // 预付费才生效:单位月,12 = 12 月 + AutoRenew bool `json:"autoRenew,omitempty"` // 是否自动续费 + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` // 续费单位:月 + AutoRenewTime int `json:"autoRenewTime,omitempty"` // 12 = 12 个月 +} + +// DeleteOption 删除节点选项 +type DeleteOption struct { + MoveOut bool `json:"moveOut,omitempty"` + DeleteResource bool `json:"deleteResource,omitempty"` + DeleteCDSSnapshot bool `json:"deleteCDSSnapshot,omitempty"` +} + +// BBCOption BBC 相关配置 +type BBCOption struct { + Flavor string `json:"flavor,omitempty"` + DiskInfo string `json:"diskInfo,omitempty"` + // 是否保留数据 + ReserveData bool `json:"reserveData,omitempty"` + // 磁盘阵列类型 ID + RaidID string `json:"raidID,omitempty"` + // 系统盘分配大小 + SysDiskSize int `json:"sysDiskSize,omitempty"` +} + +// DeployCustomConfig - 部署自定义配置 +type DeployCustomConfig struct { + // Docker相关配置 + DockerConfig DockerConfig `json:"dockerConfig,omitempty"` + // containerd相关配置 + ContainerdConfig ContainerdConfig `json:"containerdConfig,omitempty"` + + // kubelet数据目录 + KubeletRootDir string `json:"kubeletRootDir,omitempty"` + // 是否开启资源预留 + EnableResourceReserved bool `json:"EnableResourceReserved,omitempty"` + // k8s进程资源预留配额 + // key:value: cpu: 50m, memory: 100Mi + KubeReserved map[string]string `json:"kubeReserved,omitempty"` + // 系统进程资源预留配额 + // key:value: cpu: 50m, memory: 100Mi + SystemReserved map[string]string `json:"systemReserved,omitempty"` + + // RegistryPullQPS, default: 5 + RegistryPullQPS int `json:"registryPullQPS,omitempty"` + // RegistryBurst, default: 10 + RegistryBurst int `json:"registryBurst,omitempty"` + // PodPidsLimit, default: -1 + PodPidsLimit int `json:"podPidsLimit,omitempty"` + + // 是否封锁节点 + EnableCordon bool `json:"enableCordon,omitempty"` + + // 部署前执行脚本, 前端 base64编码后传参 + PreUserScript string `json:"preUserScript,omitempty"` + // 部署后执行脚本, 前端 base64编码后传参 + PostUserScript string `json:"postUserScript,omitempty"` + + // KubeletBindAddressType, kubelet bind address + KubeletBindAddressType KubeletBindAddressType `json:"kubeletBindAddressType,omitempty"` +} + +// DockerConfig docker相关配置 +type DockerConfig struct { + DockerDataRoot string `json:"dockerDataRoot,omitempty"` // 自定义 docker 数据目录 + RegistryMirrors []string `json:"registryMirrors,omitempty"` // 自定义 RegistryMirrors + InsecureRegistries []string `json:"insecureRegistries,omitempty"` // 自定义 InsecureRegistries + DockerLogMaxSize string `json:"dockerLogMaxSize,omitempty"` // docker日志大小,default: 20m + DockerLogMaxFile string `json:"dockerLogMaxFile,omitempty"` // docker日志保留数,default: 10 + BIP string `json:"dockerBIP,omitempty"` // docker0网桥网段, default: 169.254.30.1/28 +} + +// ContainerdConfig containerd相关配置 +type ContainerdConfig struct { + DataRoot string `json:"dataRoot,omitempty"` // 自定义 containerd 数据目录 + RegistryMirrors []string `json:"registryMirrors,omitempty"` // 自定义 RegistryMirrors + InsecureRegistries []string `json:"insecureRegistries,omitempty"` // 自定义 InsecureRegistries +} + +// ExistedOption 已有实例相关配置 +type ExistedOption struct { + ExistedInstanceID string `json:"existedInstanceID,omitempty"` + + // nil 为默认: 重装系统 + Rebuild *bool `json:"rebuild,omitempty"` +} + +// MachineType 机器类型: BCC, BBC +type MachineType string + +const ( + // MachineTypeBCC 机器类型 BCC + MachineTypeBCC MachineType = "BCC" + + // MachineTypeBBC 机器类型 BBC + MachineTypeBBC MachineType = "BBC" + + // MachineTypeMetal 机器类型 裸金属 + MachineTypeMetal MachineType = "Metal" +) + +// CDSConfig clone from BCC +type CDSConfig struct { + Path string `json:"diskPath,omitempty"` + StorageType bccapi.StorageType `json:"storageType,omitempty"` + CDSSize int `json:"cdsSize,omitempty"` + SnapshotID string `json:"snapshotID,omitempty"` +} + +// MountConfig - 磁盘挂载信息 +type MountConfig struct { + Path string `json:"diskPath,omitempty"` // "/data" + CDSID string `json:"cdsID,omitempty"` + Device string `json:"device,omitempty"` // "/dev/vdb" + CDSSize int `json:"cdsSize,omitempty"` + StorageType bccapi.StorageType `json:"storageType,omitempty"` +} + +// ClusterRole master & slave +type ClusterRole string + +const ( + // ClusterRoleMaster K8S master + ClusterRoleMaster ClusterRole = "master" + + // ClusterRoleNode K8S node + ClusterRoleNode ClusterRole = "node" +) + +// InstanceOS defines the OS of BCC +type InstanceOS struct { + ImageType bccapi.ImageType `json:"imageType,omitempty"` // 镜像类型 + ImageName string `json:"imageName,omitempty"` // 镜像名字: ubuntu-14.04.1-server-amd64-201506171832 + OSType OSType `json:"osType,omitempty"` // e.g. linux + OSName OSName `json:"osName,omitempty"` // e.g. Ubuntu + OSVersion string `json:"osVersion,omitempty"` // e.g. 14.04.1 LTS + OSArch string `json:"osArch,omitempty"` // e.g. x86_64 (64bit) + OSBuild string `json:"osBuild,omitempty"` // e.g. 2015061700 +} + +// InstancePhase CCE InstancePhase +type InstancePhase string + +const ( + // InstancePhasePending 创建节点时默认状态 + InstancePhasePending InstancePhase = "pending" + + // InstancePhaseProvisioning IaaS 相关资源正在创建中 + InstancePhaseProvisioning InstancePhase = "provisioning" + + // InstancePhaseProvisioned IaaS 相关资源已经 Ready + InstancePhaseProvisioned InstancePhase = "provisioned" + + // InstancePhaseRunning 节点运行正常 + InstancePhaseRunning InstancePhase = "running" + + // InstancePhaseCreateFailed 节点异常 + InstancePhaseCreateFailed InstancePhase = "create_failed" + + // InstancePhaseDeleting 节点正在删除 + InstancePhaseDeleting InstancePhase = "deleting" + + // InstancePhaseDeleted 节点删除完成 + InstancePhaseDeleted InstancePhase = "deleted" + + // InstancePhaseDeleteFailed 节点删除失败 + InstancePhaseDeleteFailed InstancePhase = "delete_failed" +) + +// KubeletBindAddressType - kubelet bind address 类型 +type KubeletBindAddressType string + +const ( + // KubeletBindAddressTypeAll - 0.0.0.0 + KubeletBindAddressTypeAll KubeletBindAddressType = "all" + + // KubeletBindAddressTypeLocal - 127.0.0.1 + KubeletBindAddressTypeLocal KubeletBindAddressType = "local" + + // KubeletBindAddressTypeHostIP - 主网卡 IP + KubeletBindAddressTypeHostIP KubeletBindAddressType = "hostip" +) + +type BidOption struct { + // BidMode 竞价模式 + BidMode BidMode `json:"bidMode,omitempty"` + + // BidPrice 用户的出价, 仅在 BidMode=BidModeCustomPrice 模式下生效 + BidPrice string `json:"bidPrice,omitempty"` + + // BidTime 竞价超时时间, 单位: minute, 超时会取消该竞价实例订单 + BidTimeout int `json:"bidTimeout,omitempty"` + + // BidReleaseEIP 竞价实例被动释放时, 是否联动释放实例 EIP + BidReleaseEIP bool `json:"bidReleaseEIP,omitempty"` + + // BidReleaseEIP 竞价实例被动释放时, 是否联动释放实例 CDS + BidReleaseCDS bool `json:"bidReleaseCDS,omitempty"` +} + +type CDSConfigList []CDSConfig + +type TagList []Tag + +type InstanceLabels map[string]string + +type InstanceTaints []Taint + +type InstanceAnnotations map[string]string + +type BBCFlavorID string + +type BidMode string + +const ( + // BidModeMarketPrice 跟随市场价出价 + BidModeMarketPrice BidMode = "MARKET_PRICE_BID" + + // BidModeCustomPrice 用户自定义出价 + BidModeCustomPrice BidMode = "CUSTOM_BID" +) diff --git a/bce-sdk-go/services/cce/v2/types/instance_group.go b/bce-sdk-go/services/cce/v2/types/instance_group.go new file mode 100644 index 0000000..708ece1 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/instance_group.go @@ -0,0 +1,91 @@ +package types + +const ( + DefaultShrinkPolicy = PriorityShrinkPolicy + PriorityShrinkPolicy ShrinkPolicy = "Priority" + RandomShrinkPolicy ShrinkPolicy = "Random" + + DefaultUpdatePolicy = ConcurrencyUpdatePolicy + RollingUpdatePolicy UpdatePolicy = "Rolling" + ConcurrencyUpdatePolicy UpdatePolicy = "Concurrency" + + DefaultCleanPolicy = RemainCleanPolicy + RemainCleanPolicy CleanPolicy = "Remain" + DeleteCleanPolicy CleanPolicy = "Delete" +) + +type InstanceGroupSpec struct { + CCEInstanceGroupID string `json:"cceInstanceGroupID,omitempty" ` + InstanceGroupName string `json:"instanceGroupName" ` + + ClusterID string `json:"clusterID,omitempty" ` + ClusterRole ClusterRole `json:"clusterRole,omitempty" ` + + Selector *InstanceSelector `json:"selector" ` + + ShrinkPolicy ShrinkPolicy `json:"shrinkPolicy,omitempty" ` + + UpdatePolicy UpdatePolicy `json:"updatePolicy,omitempty" ` + + CleanPolicy CleanPolicy `json:"cleanPolicy,omitempty" ` + + InstanceTemplate InstanceTemplate `json:"instanceTemplate" ` + Replicas int `json:"replicas" ` + + ClusterAutoscalerSpec *ClusterAutoscalerSpec `json:"clusterAutoscalerSpec,omitempty" ` +} + +type InstanceTemplate struct { + InstanceSpec `json:",inline"` +} + +type InstanceSelector struct { + LabelSelector `json:",inline"` +} + +type ShrinkPolicy string +type UpdatePolicy string +type CleanPolicy string + +type ClusterAutoscalerSpec struct { + Enabled bool `json:"enabled" ` + MinReplicas int `json:"minReplicas" ` + MaxReplicas int `json:"maxReplicas" ` + ScalingGroupPriority int `json:"scalingGroupPriority" ` +} + +type InstanceGroupStatus struct { + ReadyReplicas int `json:"readyReplicas" ` + UndeliveredMachines UndeliveredMachines `json:"undeliveredMachines,omitempty" ` + Pause *PauseDetail `json:"pause,omitempty" ` +} + +type UndeliveredMachines struct { + FailedMachines []string `json:"failedMachines,omitempty"` + PendingMachines []string `json:"pendingMachines,omitempty"` +} + +type PauseDetail struct { + Paused bool `json:"paused"` + Reason string `json:"reason"` +} + +type LabelSelector struct { + MatchLabels map[string]string `json:"matchLabels,omitempty" protobuf:"bytes,1,rep,name=matchLabels"` + MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"` +} + +type LabelSelectorRequirement struct { + Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,1,opt,name=key"` + Operator LabelSelectorOperator `json:"operator" protobuf:"bytes,2,opt,name=operator,casttype=LabelSelectorOperator"` + Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"` +} + +type LabelSelectorOperator string + +const ( + LabelSelectorOpIn LabelSelectorOperator = "In" + LabelSelectorOpNotIn LabelSelectorOperator = "NotIn" + LabelSelectorOpExists LabelSelectorOperator = "Exists" + LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" +) diff --git a/bce-sdk-go/services/cce/v2/types/internalblb.go b/bce-sdk-go/services/cce/v2/types/internalblb.go new file mode 100644 index 0000000..deb189b --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/internalblb.go @@ -0,0 +1,21 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +// BLBType for load balancer type +type BLBType string + +const ( + // BLBTypeNormal 普通 BLB 类型 + BLBTypeNormal BLBType = "normal" + + // BLBTypeApplication 应用型 BLB 类型 + BLBTypeApplication BLBType = "application" +) diff --git a/bce-sdk-go/services/cce/v2/types/internalvpc.go b/bce-sdk-go/services/cce/v2/types/internalvpc.go new file mode 100644 index 0000000..66274e9 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/internalvpc.go @@ -0,0 +1,33 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +// AvailableZone 可用区 +type AvailableZone string + +const ( + // ZoneA 可用区 A + AvailableZoneA AvailableZone = "zoneA" + + // ZoneB 可用区 B + AvailableZoneB AvailableZone = "zoneB" + + // ZoneC 可用区 C + AvailableZoneC AvailableZone = "zoneC" + + // ZoneD 可用区 D + AvailableZoneD AvailableZone = "zoneD" + + // ZoneE 可用区 E + AvailableZoneE AvailableZone = "zoneE" + + // ZoneF 可用区 F + AvailableZoneF AvailableZone = "zoneF" +) diff --git a/bce-sdk-go/services/cce/v2/types/k8stypes.go b/bce-sdk-go/services/cce/v2/types/k8stypes.go new file mode 100644 index 0000000..38524fa --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/k8stypes.go @@ -0,0 +1,193 @@ +package types + +import ( + "encoding/json" + "time" +) + +// The node this Taint is attached to has the "effect" on +// any pod that does not tolerate the Taint. +type Taint struct { + // Required. The taint key to be applied to a node. + Key string `json:"key" protobuf:"bytes,1,opt,name=key"` + // Required. The taint value corresponding to the taint key. + // +optional + Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"` + // Required. The effect of the taint on pods + // that do not tolerate the taint. + // Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + Effect TaintEffect `json:"effect" protobuf:"bytes,3,opt,name=effect,casttype=TaintEffect"` + // TimeAdded represents the time at which the taint was added. + // It is only written for NoExecute taints. + // +optional + TimeAdded *Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"` +} + +type TaintEffect string + +const ( + // Do not allow new pods to schedule onto the node unless they tolerate the taint, + // but allow all pods submitted to Kubelet without going through the scheduler + // to start, and allow all already-running pods to continue running. + // Enforced by the scheduler. + TaintEffectNoSchedule TaintEffect = "NoSchedule" + + // Like TaintEffectNoSchedule, but the scheduler tries not to schedule + // new pods onto the node, rather than prohibiting new pods from scheduling + // onto the node entirely. Enforced by the scheduler. + TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" + + // Evict any already-running pods that do not tolerate the taint. + // Currently enforced by NodeController. + TaintEffectNoExecute TaintEffect = "NoExecute" +) + +// Time is a wrapper around time.Time which supports correct +// marshaling to YAML and JSON. Wrappers are provided for many +// of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Time struct { + time.Time `protobuf:"-"` +} + +// DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *Time) DeepCopyInto(out *Time) { + *out = *t +} + +// NewTime returns a wrapped instance of the provided time +func NewTime(time time.Time) Time { + return Time{time} +} + +// Date returns the Time corresponding to the supplied parameters +// by wrapping time.Date. +func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { + return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// Now returns the current local time. +func Now() Time { + return Time{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *Time) Before(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *Time) Equal(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// Unix returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func Unix(sec int64, nsec int64) Time { + return Time{time.Unix(sec, nsec)} +} + +// Rfc3339Copy returns a copy of the Time at second-level precision. +func (t Time) Rfc3339Copy() Time { + copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) + return Time{copied} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *Time) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *Time) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t Time) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + buf := make([]byte, 0, len(time.RFC3339)+2) + buf = append(buf, '"') + // time cannot contain non escapable JSON characters + buf = t.UTC().AppendFormat(buf, time.RFC3339) + buf = append(buf, '"') + return buf, nil +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Time) OpenAPISchemaFormat() string { return "date-time" } + +// MarshalQueryParameter converts to a URL query parameter value +func (t Time) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(time.RFC3339), nil +} diff --git a/bce-sdk-go/services/cce/v2/types/logicbcc.go b/bce-sdk-go/services/cce/v2/types/logicbcc.go new file mode 100644 index 0000000..6af344b --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/logicbcc.go @@ -0,0 +1,57 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +// ServerStatus BCC 虚机状态 +type ServerStatus string + +const ( + // ServerStatusActive 虚机运行中 + ServerStatusActive ServerStatus = "ACTIVE" + + // ServerStatusBuild 虚机创建中 + ServerStatusBuild ServerStatus = "BUILD" + + // ServerStatusRebuild 虚机重装系统中 + ServerStatusRebuild ServerStatus = "REBUILD" + + // ServerStatusDeleted 虚机已删除 + ServerStatusDeleted ServerStatus = "DELETED" + + // ServerStatusSnapshot 创建快照 + ServerStatusSnapshot ServerStatus = "SNAPSHOT" + + // ServerStatusDeleteSnapshot 删除快照 + ServerStatusDeleteSnapshot ServerStatus = "DELETE_SNAPSHOT" + + // ServerStatusVolumeResize VOLUME_RESIZE + ServerStatusVolumeResize ServerStatus = "VOLUME_RESIZE" + + // ServerStatusError 虚机异常 + ServerStatusError ServerStatus = "ERROR" + + // ServerStatusExpired 虚机欠费释放 + ServerStatusExpired ServerStatus = "EXPIRED" + + // ServerStatusReboot 虚机重启 + ServerStatusReboot ServerStatus = "REBOOT" + + // ServerStatusRecharge 虚机续费 + ServerStatusRecharge ServerStatus = "RECHARGE" + + // ServerStatusShutoff 虚机关机 + ServerStatusShutoff ServerStatus = "SHUTOFF" + + // ServerStatusStopped 虚机关机 + ServerStatusStopped ServerStatus = "STOPPED" + + // ServerStatusUnknown 虚机状态未知 + ServerStatusUnknown ServerStatus = "UNKNOWN" +) diff --git a/bce-sdk-go/services/cce/v2/types/tag.go b/bce-sdk-go/services/cce/v2/types/tag.go new file mode 100644 index 0000000..23d53ce --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/tag.go @@ -0,0 +1,22 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2020/07/28 16:26:00, by jichao04@baidu.com, create +*/ + +package types + +// Tag represents TagModel in BCE +type Tag struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} + +// Quota - CCE Cluster/Node Quota +type Quota struct { + Quota int `json:"quota"` + Used int `json:"used"` +} diff --git a/bce-sdk-go/services/cce/v2/types/task.go b/bce-sdk-go/services/cce/v2/types/task.go new file mode 100644 index 0000000..4345013 --- /dev/null +++ b/bce-sdk-go/services/cce/v2/types/task.go @@ -0,0 +1,56 @@ +// Copyright 2019 Baidu Inc. All rights reserved +// Use of this source code is governed by a CCE +// license that can be found in the LICENSE file. +/* +modification history +-------------------- +2021/06/24 16:00:00, by pansiyuan02@baidu.com, create +*/ +package types + +import "time" + +const ( + TaskTypeInstanceGroupReplicas TaskType = "InstanceGroupReplicas" +) + +const ( + TaskPhasePending TaskPhase = "Pending" + TaskPhaseProcessing TaskPhase = "Processing" + TaskPhaseDone TaskPhase = "Done" + TaskPhaseAborted TaskPhase = "Aborted" +) + +const ( + TaskProcessPhasePending TaskProcessPhase = "Pending" + TaskProcessPhaseProcessing TaskProcessPhase = "Processing" + TaskProcessPhaseDone TaskProcessPhase = "Done" + TaskProcessPhaseAborted TaskProcessPhase = "Aborted" +) + +type TaskType string +type TaskPhase string +type TaskProcessPhase string + +type Task struct { + ID string `json:"id"` + Type TaskType `json:"type"` + Description string `json:"description"` + StartTime time.Time `json:"startTime"` + FinishTime *time.Time `json:"finishTime,omitempty"` + Phase TaskPhase `json:"phase"` + + TaskProcesses []TaskProcess `json:"processes,omitempty"` + ErrMessage string `json:"errMessage,omitempty"` +} + +type TaskProcess struct { + Name string `json:"name"` + Phase TaskProcessPhase `json:"phase,omitempty"` + StartTime *time.Time `json:"startTime,omitempty"` + FinishTime *time.Time `json:"finishTime,omitempty"` + + Metrics map[string]string `json:"metrics,omitempty"` + SubProcesses []TaskProcess `json:"subProcesses,omitempty"` + ErrMessage string `json:"errMessage,omitempty"` +} diff --git a/bce-sdk-go/services/cdn/api/cache.go b/bce-sdk-go/services/cdn/api/cache.go new file mode 100644 index 0000000..b55288b --- /dev/null +++ b/bce-sdk-go/services/cdn/api/cache.go @@ -0,0 +1,335 @@ +package api + +import ( + "errors" + "time" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/util" +) + +type PurgedId string +type PrefetchId string + +// CStatusQueryData defined a struct for the query conditions about the tasks' progress +type CStatusQueryData struct { + EndTime string + StartTime string + Url string + Marker string + Id string +} + +// CRecordQueryData defined a struct for the query conditions about the operated records +type CRecordQueryData struct { + EndTime string + StartTime string + Url string + Marker string + TaskType string +} + +// PurgeTask defined a struct for purged task +type PurgeTask struct { + Url string `json:"url"` + Type string `json:"type,omitempty"` +} + +// PrefetchTask defined a struct for prefetch task +type PrefetchTask struct { + Url string `json:"url"` + Speed int64 `json:"speed,omitempty"` + StartTime string `json:"startTime,omitempty"` +} + +// CachedDetail defined a struct for task details +type CachedDetail struct { + Status string `json:"status"` + CreatedAt string `json:"createdAt"` + StartedAt string `json:"startedAt"` + FinishedAt string `json:"finishedAt"` + Progress int64 `json:"progress"` +} + +// PurgedDetail defined a struct for purged task information +type PurgedDetail struct { + *CachedDetail + Task PurgeTask `json:"task"` +} + +// PrefetchDetail defined a struct for prefetch task information +type PrefetchDetail struct { + *CachedDetail + Task PrefetchTask `json:"task"` +} + +// PurgedStatus defined a struct for purged status +type PurgedStatus struct { + Details []PurgedDetail `json:"details"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` +} + +// PrefetchStatus defined a struct for prefetch status +type PrefetchStatus struct { + Details []PrefetchDetail `json:"details"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` +} + +// QuotaDetail defined a struct for query quota +type QuotaDetail struct { + DirRemain int64 `json:"dirRemain"` + UrlRemain int64 `json:"urlRemain"` + DirQuota int64 `json:"dirQuota"` + UrlQuota int64 `json:"urlQuota"` +} + +// RecordDetail defined a struct for one operating record +type RecordDetail struct { + Status string `json:"status"` + Url string `json:"url"` + Type string `json:"type"` + CreatedAt string `json:"createdAt"` + StartedAt string `json:"startedAt"` + FinishedAt string `json:"finishedAt"` + Progress int64 `json:"progress"` + Reason string `json:"reason"` + Operator string `json:"operator"` +} + +// RecordDetails defined a struct for multi operating records in some querying conditions +type RecordDetails struct { + Details []RecordDetail `json:"details"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` +} + +// Purge - tells the CDN system to purge the specified files +// For more details, please refer https://cloud.baidu.com/doc/CDN/s/ijwvyeyyj +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - tasks: the tasks about purging the files from the CDN nodes +// +// RETURNS: +// - PurgedId: an ID representing a purged task, using it to search the task progress +// - error: nil if success otherwise the specific error +func Purge(cli bce.Client, tasks []PurgeTask) (PurgedId, error) { + + respObj := &struct { + Id string `json:"id"` + }{} + + err := httpRequest(cli, "POST", "/v2/cache/purge", nil, &struct { + Tasks []PurgeTask `json:"tasks"` + }{ + Tasks: tasks, + }, respObj) + if err != nil { + return "", err + } + + return PurgedId(respObj.Id), nil +} + +// GetPurgedStatus - get the purged progress +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ujwvyezqm +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryData: querying conditions, it contains the time interval, the task ID and the specified url +// +// RETURNS: +// - *PurgedStatus: the details about the purged +// - error: nil if success otherwise the specific error +func GetPurgedStatus(cli bce.Client, queryData *CStatusQueryData) (*PurgedStatus, error) { + if queryData == nil { + queryData = &CStatusQueryData{} + } + + params := map[string]string{} + if queryData.Id != "" { + params["id"] = queryData.Id + } + if err := getTimeParams(params, queryData.StartTime, queryData.EndTime); err != nil { + return nil, err + } + + if queryData.Url != "" { + params["url"] = queryData.Url + } + if queryData.Marker != "" { + params["marker"] = queryData.Marker + } + + respObj := &PurgedStatus{} + err := httpRequest(cli, "GET", "/v2/cache/purge", params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} + +// Prefetch - tells the CDN system to prefetch the specified files +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Rjwvyf0ff +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - tasks: the tasks about prefetch the files from the CDN nodes +// - error: nil if success otherwise the specific error +func Prefetch(cli bce.Client, tasks []PrefetchTask) (PrefetchId, error) { + respObj := &struct { + Id string `json:"id"` + }{} + + err := httpRequest(cli, "POST", "/v2/cache/prefetch", nil, &struct { + Tasks []PrefetchTask `json:"tasks"` + }{ + Tasks: tasks, + }, respObj) + if err != nil { + return "", err + } + + return PrefetchId(respObj.Id), nil +} + +// GetPrefetchStatus - get the prefetch progress +// For details, please refer https://cloud.baidu.com/doc/CDN/s/4jwvyf01w +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryData: querying conditions, it contains the time interval, the task ID and the specified url +// +// RETURNS: +// - *PrefetchStatus: the details about the prefetch +// - error: nil if success otherwise the specific error +func GetPrefetchStatus(cli bce.Client, queryData *CStatusQueryData) (*PrefetchStatus, error) { + if queryData == nil { + queryData = &CStatusQueryData{} + } + + params := map[string]string{} + if queryData.Id != "" { + params["id"] = queryData.Id + } + if err := getTimeParams(params, queryData.StartTime, queryData.EndTime); err != nil { + return nil, err + } + + if queryData.Url != "" { + params["url"] = queryData.Url + } + if queryData.Marker != "" { + params["marker"] = queryData.Marker + } + + respObj := &PrefetchStatus{} + err := httpRequest(cli, "GET", "/v2/cache/prefetch", params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} + +// GetQuota - get the quota about purge and prefetch +// For details, please refer https://cloud.baidu.com/doc/CDN/s/zjwvyeze3 +// +// RETURNS: +// - cli: the client agent which can perform sending request +// - QuotaDetail: the quota details about a specified user +// - error: nil if success otherwise the specific error +func GetQuota(cli bce.Client) (*QuotaDetail, error) { + respObj := &QuotaDetail{} + err := httpRequest(cli, "GET", "/v2/cache/quota", nil, nil, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} + +func getTimeParams(params map[string]string, startTime, endTime string) error { + // get "endTime" + endTs := int64(0) + if endTime == "" { + // default current time + endTs = time.Now().Unix() + params["endTime"] = util.FormatISO8601Date(endTs) + } else { + t, err := util.ParseISO8601Date(endTime) + if err != nil { + return err + } + endTs = t.Unix() + params["endTime"] = endTime + } + + // get "startTime", the default "startTime" is one day later than the "endTime" + startTs := int64(0) + if startTime == "" { + startTs = endTs - 24*60*60 + params["startTime"] = util.FormatISO8601Date(startTs) + } else { + t, err := util.ParseISO8601Date(startTime) + if err != nil { + return err + } + startTs = t.Unix() + params["startTime"] = startTime + } + + // the "startTime should be less than the "endTime" + // if we set "startTime" but not "endTime", we normally assign the current time to the "endTime", + // in this condition, we might get "startTs > endTs" + if startTs > endTs { + return errors.New("error time range, the startTime should be less than the endTime") + } + + return nil +} + +// GetCacheOpRecords get the history operating records +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jypnzjqt +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryData: querying conditions, it contains the time interval, the task type and the specified url +// +// RETURNS: +// - *RecordDetails: the details about the records +// - error: nil if success otherwise the specific error +func GetCacheOpRecords(cli bce.Client, queryData *CRecordQueryData) (*RecordDetails, error) { + params := map[string]string{} + if queryData == nil { + queryData = &CRecordQueryData{} + } + + if queryData.TaskType != "" { + params["type"] = queryData.TaskType + } else { + params["type"] = "all" + } + + if err := getTimeParams(params, queryData.StartTime, queryData.EndTime); err != nil { + return nil, err + } + + if queryData.Url != "" { + params["url"] = queryData.Url + } + if queryData.Marker != "" { + params["marker"] = queryData.Marker + } + + respObj := &RecordDetails{} + err := httpRequest(cli, "GET", "/v2/cache/records", params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} diff --git a/bce-sdk-go/services/cdn/api/cert.go b/bce-sdk-go/services/cdn/api/cert.go new file mode 100644 index 0000000..d626a57 --- /dev/null +++ b/bce-sdk-go/services/cdn/api/cert.go @@ -0,0 +1,108 @@ +package api + +import ( + "errors" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" +) + +// UserCertificate defined a struct for the origin certificate hold by user. +type UserCertificate struct { + CertName string + ServerData string + PrivateData string + LinkData string +} + +// CertificateDetail defined a struct holds the brief information. +type CertificateDetail struct { + CertId string `json:"certId"` + CertName string `json:"certName"` + Status string `json:"status"` + CommonName string `json:"certCommonName"` + DNSNames string `json:"certDNSNames"` + StartTime string `json:"certStartTime"` + StopTime string `json:"certStopTime"` + CreateTime string `json:"certCreateTime"` + UpdateTime string `json:"certUpdateTime"` +} + +// PutCert - put the certificate data for the specified domain to server, you can also enable HTTPS or not. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - userCert: certificate data +// - httpsEnabled: "ON" for enable HTTPS, "OFF" for disable HTTPS, otherwise invalid. +// +// RETURNS: +// - string: certId +// - error: nil if success otherwise the specific error +func PutCert(cli bce.Client, domain string, userCert *UserCertificate, httpsEnabled string) (certId string, err error) { + if userCert == nil { + return "", errors.New("userCert can not be nil") + } + if httpsEnabled != "ON" && httpsEnabled != "OFF" { + return "", fmt.Errorf("httpsEnabled either \"ON\" or \"OFF\", your input is \"%s\"", httpsEnabled) + } + + reqObj := map[string]interface{}{ + "certificate": map[string]string{ + "certName": userCert.CertName, + "certServerData": userCert.ServerData, + "certPrivateData": userCert.PrivateData, + "certLinkData": userCert.LinkData, + }, + "httpsEnable": httpsEnabled, + } + + urlPath := fmt.Sprintf("/v2/%s/certificates", domain) + respObj := struct { + CertId string `json:"certId"` + }{} + if err := httpRequest(cli, "PUT", urlPath, nil, reqObj, &respObj); err != nil { + return "", err + } + + return respObj.CertId, nil +} + +// GetCert - query the certificate data for the specified domain. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/kjzuvz70t +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *CertificateDetail: certificate details +// - error: nil if success otherwise the specific error +func GetCert(cli bce.Client, domain string) (certDetail *CertificateDetail, err error) { + urlPath := fmt.Sprintf("/v2/%s/certificates", domain) + respObj := &CertificateDetail{} + if err := httpRequest(cli, "GET", urlPath, nil, nil, respObj); err != nil { + return nil, err + } + return respObj, err +} + +// DeleteCert - delete the certificate data for the specified domain. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ljzuylmee +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *CertificateDetail: certificate details +// - error: nil if success otherwise the specific error +func DeleteCert(cli bce.Client, domain string) error { + urlPath := fmt.Sprintf("/v2/%s/certificates", domain) + var respObj interface{} + if err := httpRequest(cli, "DELETE", urlPath, nil, nil, respObj); err != nil { + return err + } + return nil +} diff --git a/bce-sdk-go/services/cdn/api/domain.go b/bce-sdk-go/services/cdn/api/domain.go new file mode 100644 index 0000000..b42b2bb --- /dev/null +++ b/bce-sdk-go/services/cdn/api/domain.go @@ -0,0 +1,247 @@ +package api + +import ( + "errors" + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" +) + +// DomainStatus defined a struct for domain info, +// the status only include "RUNNING", "OPERATING" and "STOPPED", +// the other status is undefined +type DomainStatus struct { + Domain string `json:"domain"` + Status string `json:"status"` +} + +// DomainValidInfo defined a struct for `IsValidDomain` return +type DomainValidInfo struct { + Domain string + IsValid bool `json:"isValid"` + Message string `json:"message"` +} + +// OriginPeer defined a struct for an origin server setting +type OriginPeer struct { + Peer string `json:"peer"` + Host string `json:"host,omitempty"` + Backup bool `json:"backup"` + Follow302 bool `json:"follow302"` + Weight int `json:"weight,omitempty"` + ISP string `json:"isp,omitempty"` +} + +// OriginInit defined a struct for creating a new CDN service in `OPENCDN` +type OriginInit struct { + Origin []OriginPeer `json:"origin"` + DefaultHost string `json:"defaultHost,omitempty"` + Form string `json:"form,omitempty"` +} + +// DomainCreatedInfo defined a struct for `CreateDomain` return +type DomainCreatedInfo struct { + Domain string `json:"domain"` + Status string `json:"status"` + Cname string `json:"cname"` +} + +// ListDomains - list all domains that in CDN service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/sjwvyewt1 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - marker: a marker is a start point of searching +// +// RETURNS: +// - []string: domains belongs to the user +// - string: a marker for next searching, empty if is in the end +// - error: nil if success otherwise the specific error +func ListDomains(cli bce.Client, marker string) ([]string, string, error) { + type domainInfo struct { + Name string `json:"name"` + } + + respObj := &struct { + IsTruncated bool `json:"isTruncated"` + Domains []domainInfo `json:"domains"` + NextMarker string `json:"nextMarker"` + }{} + + params := map[string]string{} + if marker != "" { + params["marker"] = marker + } + + err := httpRequest(cli, "GET", "/v2/domain", params, nil, respObj) + if err != nil { + return nil, "", err + } + + domains := make([]string, len(respObj.Domains)) + for i, domain := range respObj.Domains { + domains[i] = domain.Name + } + + return domains, respObj.NextMarker, nil +} + +// GetDomainStatus - get domains' details +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyewf1 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - status: the specified running status, the available values are "RUNNING", "STOPPED", OPERATING or "ALL" +// - rule: the regex matching rule +// +// RETURNS: +// - []DomainStatus: domain details list +// - error: nil if success otherwise the specific error +func GetDomainStatus(cli bce.Client, status string, rule string) ([]DomainStatus, error) { + if status == "" { + return nil, errors.New("domain status parameter could not be empty") + } + + params := map[string]string{ + "status": status, + } + + if rule != "" { + params["rule"] = rule + } + + respObj := &struct { + Domains []DomainStatus `json:"domains"` + }{} + + err := httpRequest(cli, "GET", "/v2/user/domains", params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.Domains, nil +} + +// IsValidDomain - check the specified domain whether it can be added to CDN service or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjwvyexh6 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *DomainValidInfo: available information about the specified domain +// - error: nil if success otherwise the specific error +func IsValidDomain(cli bce.Client, domain string) (*DomainValidInfo, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/valid", domain) + respObj := &DomainValidInfo{} + + err := httpRequest(cli, "GET", urlPath, nil, nil, respObj) + if err != nil { + return nil, err + } + + respObj.Domain = domain + return respObj, nil +} + +// CreateDomain - add a specified domain into CDN service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/gjwvyex4o +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - originInit: initialized data for a CDN domain +// +// RETURNS: +// - *DomainCreatedInfo: the details about created a CDN domain +// - error: nil if success otherwise the specific error +func CreateDomain(cli bce.Client, domain string, originInit *OriginInit) (*DomainCreatedInfo, error) { + urlPath := fmt.Sprintf("/v2/domain/%s", domain) + respObj := &DomainCreatedInfo{} + + err := httpRequest(cli, "PUT", urlPath, nil, originInit, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} + +// EnableDomain - enable a specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjwvyexv8 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func EnableDomain(cli bce.Client, domain string) error { + if domain == "" { + return errors.New("domain parameter could not be empty") + } + + params := map[string]string{ + "enable": "", + } + urlPath := fmt.Sprintf("/v2/domain/%s", domain) + + err := httpRequest(cli, "POST", urlPath, params, nil, nil) + if err != nil { + return err + } + + return nil +} + +// DisableDomain - disable a specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/9jwvyew3e +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DisableDomain(cli bce.Client, domain string) error { + if domain == "" { + return errors.New("domain parameter could not be empty") + } + + params := map[string]string{ + "disable": "", + } + urlPath := fmt.Sprintf("/v2/domain/%s", domain) + + err := httpRequest(cli, "POST", urlPath, params, nil, nil) + if err != nil { + return err + } + + return nil +} + +// DeleteDomain - delete a specified domain from BCE CDN system +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Njwvyey7f +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DeleteDomain(cli bce.Client, domain string) error { + if domain == "" { + return errors.New("domain parameter could not be empty") + } + + urlPath := fmt.Sprintf("/v2/domain/%s", domain) + + err := httpRequest(cli, "DELETE", urlPath, nil, nil, nil) + if err != nil { + return err + } + + return nil +} diff --git a/bce-sdk-go/services/cdn/api/domain_config.go b/bce-sdk-go/services/cdn/api/domain_config.go new file mode 100644 index 0000000..686dc22 --- /dev/null +++ b/bce-sdk-go/services/cdn/api/domain_config.go @@ -0,0 +1,1936 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" +) + +// DomainConfig defined a struct for a specified domain's configuration +type DomainConfig struct { + Domain string `json:"domain"` + Cname string `json:"cname"` + Status string `json:"status"` + CreateTime string `json:"createTime"` + LastModifyTime string `json:"lastModifyTime"` + IsBan string `json:"isBan"` + Origin []OriginPeer `json:"origin"` + OriginProtocol *OriginProtocol `json:"originProtocol,omitempty"` + OriginTimeout *OriginTimeout `json:"originTimeout,omitempty"` + OriginFixedISP bool `json:"originFixedISP,omitempty"` + DefaultHost string `json:"defaultHost,omitempty"` + RequestHostAsOriginHost bool `json:"requestHostAsOriginHost"` + CacheTTL []CacheTTL `json:"cacheTTL"` + LimitRate int `json:"limitRate"` + RequestAuth *RequestAuth `json:"requestAuth,omitempty"` + Https *HTTPSConfig `json:"https,omitempty"` + FollowProtocol bool `json:"followProtocol"` + SeoSwitch *SeoSwitch `json:"seoSwitch"` + Form string `json:"form"` + RangeSwitch string `json:"rangeSwitch"` + OfflineMode bool `json:"offlineMode"` + ClientIp *ClientIp `json:"clientIp"` + OCSP bool `json:"ocsp"` + HttpHeader []HttpHeader `json:"httpHeader"` + MediaDragConf *MediaDragConf `json:"mediaDragConf"` + FileTrim bool `json:"fileTrim"` + QUIC bool `json:"quic"` + RefererACL *RefererACL `json:"refererACL"` + IpACL *IpACL `json:"ipACL"` + UaAcl *UaACL `json:"uaAcl"` + AccessLimit *AccessLimit `json:"accessLimit"` + TrafficLimit *TrafficLimit `json:"trafficLimit"` + ErrorPage []ErrorPage `json:"errorPage"` + CacheShare *CacheShared `json:"cacheShare"` + Compress *Compress `json:"compress,omitempty"` + Cors *CorsCfg `json:"cors,omitempty"` + Ipv6Dispatch *Ipv6Dispatch `json:"ipv6Dispatch,omitempty"` + RetryOrigin *RetryOrigin `json:"retryOrigin,omitempty"` +} + +// CacheTTL defined a struct for cached rules setting +type CacheTTL struct { + Type string `json:"type"` + Value string `json:"value"` + Weight int `json:"weight,omitempty"` + TTL int `json:"ttl"` +} + +// CacheShared defined a struct for sharing cache with the other domain +type CacheShared struct { + Enabled bool `json:"enabled"` + SharedWith string `json:"domain,omitempty"` +} + +// RequestAuth defined a struct for the authorization setting +type RequestAuth struct { + Type string `json:"type"` + Key1 string `json:"key1"` + Key2 string `json:"key2,omitempty"` + Timeout int `json:"timeout,omitempty"` + WhiteList []string `json:"whiteList,omitempty"` + SignArg string `json:"signArg,omitempty"` + TimeArg string `json:"timeArg,omitempty"` + TimestampFormat string `json:"timestampFormat"` + + // Deprecated, please use TimestampFormat as replacement. + TimestampMetric int `json:"-"` +} + +// HTTPSConfig defined a struct for configuration about HTTPS +type HTTPSConfig struct { + Enabled bool `json:"enabled"` + CertId string `json:"certId,omitempty"` + HttpRedirect bool `json:"httpRedirect"` + HttpRedirectCode int `json:"httpRedirectCode,omitempty"` + HttpsRedirect bool `json:"httpsRedirect"` + HttpsRedirectCode int `json:"httpsRedirectCode"` + Http2Enabled bool `json:"http2Enabled"` + SslVersion string `json:"sslVersion,omitempty"` + VerifyClient bool `json:"verifyClient"` + SslProtocols []string `json:"sslProtocols,omitempty"` + + // Deprecated: You can no longer use this field, + // The better choice is use SetOriginProtocol/GetOriginProtocol. + HttpOrigin bool `json:"-"` +} + +// SeoSwitch defined a struct for SEO setting +type SeoSwitch struct { + DirectlyOrigin string `json:"diretlyOrigin"` + PushRecord string `json:"pushRecord"` +} + +// TrafficLimit defined a struct for configure the traffic limitation +type TrafficLimit struct { + Enabled bool `json:"enable"` + LimitRate int `json:"limitRate,omitempty"` + LimitStartHour int `json:"limitStartHour"` + LimitEndHour int `json:"limitEndHour"` + LimitRateAfter int `json:"limitRateAfter,omitempty"` + TrafficLimitArg string `json:"trafficLimitArg,omitempty"` + TrafficLimitUnit string `json:"trafficLimitUnit,omitempty"` +} + +// HttpHeader defined a struct for a operation about HTTP header +type HttpHeader struct { + Type string `json:"type"` + Header string `json:"header"` + Value string `json:"value"` + Action string `json:"action,omitempty"` + Describe string `json:"describe,omitempty"` +} + +// RefererACL defined a struct for referer ACL setting +type RefererACL struct { + BlackList []string `json:"blackList"` + WhiteList []string `json:"whiteList"` + AllowEmpty bool `json:"allowEmpty"` +} + +// IpACL defined a struct for black IP and white IP +type IpACL struct { + BlackList []string `json:"blackList"` + WhiteList []string `json:"whiteList"` +} + +// UaACL defined a struct for black UA and white UA +type UaACL struct { + BlackList []string `json:"blackList"` + WhiteList []string `json:"whiteList"` +} + +// ErrorPage defined a struct for redirecting to the custom page when error occur +type ErrorPage struct { + Code int `json:"code"` + RedirectCode int `json:"redirectCode,omitempty"` + Url string `json:"url"` +} + +// MediaCfg defined a struct for a media dragging config +type MediaCfg struct { + FileSuffix []string `json:"fileSuffix"` + StartArgName string `json:"startArgName,omitempty"` + EndArgName string `json:"endArgName,omitempty"` + DragMode string `json:"dragMode"` +} + +// MediaDragConf defined a struct for dragging configs about 'Mp4' and 'Flv' +type MediaDragConf struct { + Mp4 *MediaCfg `json:"mp4,omitempty"` + Flv *MediaCfg `json:"flv,omitempty"` +} + +// ClientIp defined a struct for how to get client IP +type ClientIp struct { + Enabled bool `json:"enabled"` + Name string `json:"name,omitempty"` +} + +// RetryOrigin defined a struct for how to retry origin servers +type RetryOrigin struct { + Codes []int `json:"codes"` +} + +// AccessLimit defined a struct for access restriction in one client +type AccessLimit struct { + Enabled bool `json:"enabled"` + Limit int `json:"limit,omitempty"` +} + +// CacheUrlArgs defined a struct for cache keys +type CacheUrlArgs struct { + CacheFullUrl bool `json:"cacheFullUrl"` + CacheUrlArgs []string `json:"cacheUrlArgs,omitempty"` +} + +// CorsCfg defined a struct for cors setting +type CorsCfg struct { + IsAllow bool + Origins []string +} + +// OriginProtocol defined a struct for originProtocol setting +type OriginProtocol struct { + Value string `json:"value"` +} + +// OriginTimeout defined a struct for originTimeout setting +type OriginTimeout struct { + ConnectTimeout int `json:"connectTimeout"` + LoadTimeout int `json:"loadTimeout"` +} + +// Compress defined a struct for page compression function +type Compress struct { + Allow string `json:"allow"` + Type string `json:"type,omitempty"` +} + +// Ipv6Dispatch defined a struct for ipv6 setting +type Ipv6Dispatch struct { + Enable bool `json:"enable"` +} + +// GetDomainConfig - get the configuration for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/2jwvyf39o +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *DomainConfig: the configuration about the specified domain +// - error: nil if success otherwise the specific error +func GetDomainConfig(cli bce.Client, domain string) (*DomainConfig, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + + config := &DomainConfig{} + + err := httpRequest(cli, "GET", urlPath, nil, nil, config) + if err != nil { + return nil, err + } + + return config, nil +} + +// SetDomainOrigin - set the origin setting for the new +// For details, please refer https://cloud.baidu.com/doc/CDN/s/xjxzi7729 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - origins: the origin servers +// - defaultHost: the default host +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetDomainOrigin(cli bce.Client, domain string, origins []OriginPeer, defaultHost string) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "origin": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + Origin []OriginPeer `json:"origin"` + DefaultHost string `json:"defaultHost"` + }{ + Origin: origins, + DefaultHost: defaultHost, + }, nil) + + return err +} + +// SetOriginProtocol - set the http protocol back to backend server. +// The valid "originProtocol" must be "http", "https" or "*", +// "http" means send the HTTP request to the backend server, +// "https" means send the HTTPS request to the backend server, +// "*" means send the request follow the client's requesting protocol. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7k9jdhhlm +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - originProtocol: the protocol used for back to the backend server +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetOriginProtocol(cli bce.Client, domain string, originProtocol string) error { + validOriginProtocols := map[string]bool{ + "http": true, + "https": false, + "*": true, + } + if _, ok := validOriginProtocols[originProtocol]; !ok { + return fmt.Errorf("invalid originProtocol \"%s\", "+ + "valid value must be \"http\", \"https\" or \"*\"", originProtocol) + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "originProtocol": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, map[string]interface{}{ + "originProtocol": map[string]string{ + "value": originProtocol, + }, + }, nil) + + return err +} + +// GetOriginProtocol - get the protocol used for back to the backend server. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/dk9jdoob4 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - string: the protocol used for back to the backend server, it's value must be "http", "https" or "*" +// - error: nil if success otherwise the specific error +func GetOriginProtocol(cli bce.Client, domain string) (string, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "originProtocol": "", + } + + respObj := &struct { + OriginProtocol struct { + Value string `json:"value"` + } `json:"originProtocol"` + }{} + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return "", err + } + if respObj.OriginProtocol.Value == "" { + return "http", nil + } + return respObj.OriginProtocol.Value, nil +} + +// SetDomainSeo - set SEO setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjxziuq4y +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - seoSwitch: the setting about SEO +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetDomainSeo(cli bce.Client, domain string, seoSwitch *SeoSwitch) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "seoSwitch": "", + } + + respObj := &struct { + Status string `json:"status"` + }{} + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + SeoSwitch *SeoSwitch `json:"seoSwitch"` + }{ + SeoSwitch: seoSwitch, + }, respObj) + if err != nil { + return err + } + + return nil +} + +// GetDomainSeo - retrieve the setting about SEO +// There are two types of data that the server responds to +// 1. `{"seoSwitch":[]}` indicates no setting about SEO +// 2. `{"seoSwitch":{"diretlyOrigin":"ON","pushRecord":"OFF"}}` indicates it had normal setting about SEO +// So the code need to handle the complex affairs +// +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Djxzjfz8f +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *SeoSwitch: the setting about SEO +// - error: nil if success otherwise the specific error +func GetDomainSeo(cli bce.Client, domain string) (*SeoSwitch, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "seoSwitch": "", + } + + var respMap map[string]interface{} + + err := httpRequest(cli, "GET", urlPath, params, nil, &respMap) + if err != nil { + return nil, err + } + + if _, ok := respMap["seoSwitch"]; ok { + if _, ok := respMap["seoSwitch"].([]interface{}); !ok { + respData, _ := json.Marshal(respMap) + respObj := &struct { + SeoSwitch *SeoSwitch `json:"seoSwitch"` + }{} + err = json.Unmarshal(respData, respObj) + if err != nil { + return nil, err + } + return respObj.SeoSwitch, nil + } + } + + return nil, nil +} + +// GetCacheTTL - get the current cached setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ljxzhl9bu +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - []CacheTTL: the cache setting list +// - error: nil if success otherwise the specific error +func GetCacheTTL(cli bce.Client, domain string) ([]CacheTTL, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheTTL": "", + } + + respObj := &struct { + CacheTTLs []CacheTTL `json:"cacheTTL"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.CacheTTLs, nil +} + +// SetCacheTTL - add some rules for cached setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/wjxzhgxnx +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - cacheTTLs: the cache setting list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetCacheTTL(cli bce.Client, domain string, cacheTTLs []CacheTTL) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheTTL": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + CacheTTLs []CacheTTL `json:"cacheTTL"` + }{ + CacheTTLs: cacheTTLs, + }, nil) + if err != nil { + return err + } + + return nil +} + +// SetRefererACL - set a rule for filter some HTTP request, blackList and whiteList only one can be set +// For details, please refer https://cloud.baidu.com/doc/CDN/s/yjxzhvf21 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - blackList: the forbidden host +// - whiteList: the available host +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetRefererACL(cli bce.Client, domain string, blackList []string, whiteList []string, isAllowEmpty bool) error { + if len(blackList) != 0 && len(whiteList) != 0 { + return errors.New("blackList and whiteList cannot exist at the same time") + } + + refererACLObj := &RefererACL{ + AllowEmpty: isAllowEmpty, + } + + if blackList != nil { + refererACLObj.BlackList = blackList + } else if whiteList != nil { + refererACLObj.WhiteList = whiteList + } else { + return errors.New("blackList and whiteList cannot be nil at the same time") + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "refererACL": "", + } + err := httpRequest(cli, "PUT", urlPath, params, &struct { + RefererACL *RefererACL `json:"refererACL"` + }{ + RefererACL: refererACLObj, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetRefererACL - get referer ACL setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ujzkotvtb +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *api.RefererACL: referer ACL setting +// - error: nil if success otherwise the specific error +func GetRefererACL(cli bce.Client, domain string) (*RefererACL, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "refererACL": "", + } + + refererACLObj := &RefererACL{} + + err := httpRequest(cli, "GET", urlPath, params, nil, &struct { + RefererACL *RefererACL `json:"refererACL"` + }{ + RefererACL: refererACLObj, + }) + if err != nil { + return nil, err + } + + return refererACLObj, nil +} + +// SetRefererACL - set a rule for filter some HTTP request, blackList and whiteList only one can be set +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jxzhwc4d +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - blackList: the forbidden ip +// - whiteList: the available ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetIpACL(cli bce.Client, domain string, blackList []string, whiteList []string) error { + if len(blackList) != 0 && len(whiteList) != 0 { + return errors.New("blackList and whiteList cannot exist at the same time") + } + + ipACLObj := &IpACL{} + + if blackList != nil { + ipACLObj.BlackList = blackList + } else if whiteList != nil { + ipACLObj.WhiteList = whiteList + } else { + return errors.New("blackList and whiteList cannot be nil at the same time") + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ipACL": "", + } + err := httpRequest(cli, "PUT", urlPath, params, &struct { + IpACL *IpACL `json:"ipACL"` + }{ + IpACL: ipACLObj, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetIpACL - get black IP or white IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/jjzkp5ku7 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *api.IpACL: ip setting +// - error: nil if success otherwise the specific error +func GetIpACL(cli bce.Client, domain string) (*IpACL, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ipACL": "", + } + + ipACLObj := &IpACL{} + err := httpRequest(cli, "GET", urlPath, params, nil, &struct { + IpACL *IpACL `json:"ipACL"` + }{ + IpACL: ipACLObj, + }) + + if err != nil { + return nil, err + } + + return ipACLObj, nil +} + +// SetUaACL - set a rule for filter the specified HTTP header named "User-Agent" +// For details, please refer https://cloud.baidu.com/doc/CDN/s/uk88i2a86 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - blackList: the forbidden UA +// - whiteList: the available UA +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetUaACL(cli bce.Client, domain string, blackList []string, whiteList []string) error { + if len(blackList) != 0 && len(whiteList) != 0 { + return errors.New("blackList and whiteList cannot exist at the same time") + } + + uaAclObj := &UaACL{} + + if blackList != nil { + uaAclObj.BlackList = blackList + } else if whiteList != nil { + uaAclObj.WhiteList = whiteList + } else { + return errors.New("blackList and whiteList cannot be nil at the same time") + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "uaAcl": "", + } + err := httpRequest(cli, "PUT", urlPath, params, &struct { + UaACL *UaACL `json:"uaAcl"` + }{ + UaACL: uaAclObj, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetUaACL - get black UA or white UA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ak88ix19h +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - *api.UaACL: filter config for UA +// - error: nil if success otherwise the specific error +func GetUaACL(cli bce.Client, domain string) (*UaACL, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "uaAcl": "", + } + + uaACLObj := &UaACL{} + err := httpRequest(cli, "GET", urlPath, params, nil, &struct { + UaACL *UaACL `json:"uaAcl"` + }{ + UaACL: uaACLObj, + }) + + if err != nil { + return nil, err + } + + return uaACLObj, nil +} + +// Deprecated +// SetLimitRate - set limited speed +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - limitRate: the limited rate, "1024" means the transmittal speed is less than 1024 Byte/s +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetLimitRate(cli bce.Client, domain string, limitRate int) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "limitRate": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + LimitRate int `json:"limitRate"` + }{ + LimitRate: limitRate, + }, nil) + if err != nil { + return err + } + + return nil +} + +// SetTrafficLimit - set the traffic limitation for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ujxzi418e +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - trafficLimit: config of traffic limitation +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetTrafficLimit(cli bce.Client, domain string, trafficLimit *TrafficLimit) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "trafficLimit": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + TrafficLimit *TrafficLimit `json:"trafficLimit"` + }{ + TrafficLimit: trafficLimit, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetTrafficLimit - get the traffic limitation of the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7k4npdru0 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *TrafficLimit: config of traffic limitation +// - error: nil if success otherwise the specific error +func GetTrafficLimit(cli bce.Client, domain string) (*TrafficLimit, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "trafficLimit": "", + } + + respObj := &struct { + TrafficLimit TrafficLimit `json:"trafficLimit"` + }{} + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return &respObj.TrafficLimit, nil +} + +// SetDomainHttps - set a rule for speed HTTPS' request +// For details, please refer https://cloud.baidu.com/doc/CDN/s/rjy6v3tnr +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - httpsConfig: the rules about the HTTP configure +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetDomainHttps(cli bce.Client, domain string, httpsConfig *HTTPSConfig) error { + if httpsConfig.HttpRedirect && httpsConfig.HttpsRedirect { + return errors.New("httpRedirect and httpsRedirect can not be true at the same time") + } + if httpsConfig.HttpRedirect { + if httpsConfig.HttpRedirectCode != 0 && httpsConfig.HttpRedirectCode != 301 && httpsConfig.HttpRedirectCode != 302 { + return errors.New("invalid httpRedirectCode") + } + } + + if httpsConfig.HttpsRedirect { + if httpsConfig.HttpsRedirectCode != 0 && httpsConfig.HttpsRedirectCode != 301 && httpsConfig.HttpsRedirectCode != 302 { + return errors.New("invalid httpsRedirectCode") + } + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "https": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + HttpsConfig *HTTPSConfig `json:"https"` + }{ + HttpsConfig: httpsConfig, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetDomainHttps - get the setting about HTTPS +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *HTTPSConfig: the rules about the HTTP configure +// - error: nil if success otherwise the specific error +func GetDomainHttps(cli bce.Client, domain string) (*HTTPSConfig, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "https": "", + } + + respObj := &struct { + HttpsConfig *HTTPSConfig `json:"https"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.HttpsConfig, nil +} + +// SetOCSP - set "OCSP" for the specified domain, +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Pkf2c0ugn +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - enabled: true for "OCSP" opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetOCSP(cli bce.Client, domain string, enabled bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ocsp": "", + } + + reqObj := map[string]interface{}{ + "ocsp": enabled, + } + err := httpRequest(cli, "PUT", urlPath, params, reqObj, nil) + if err != nil { + return err + } + + return nil +} + +// GetOCSP - get "OCSP" switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Xkhyjzcvd +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true for "OCSP" opening otherwise closed +// - error: nil if success otherwise the specific error +func GetOCSP(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ocsp": "", + } + + respObj := &struct { + OfflineMode bool `json:"ocsp"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.OfflineMode, nil +} + +// SetDomainRequestAuth - set the authorized rules for requesting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/njxzi59g9 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - requestAuth: the rules about the auth +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetDomainRequestAuth(cli bce.Client, domain string, requestAuth *RequestAuth) error { + if requestAuth != nil && requestAuth.TimestampMetric != 0 { + return errors.New("TimestampMetric is deprecated, please use TimestampFormat as replacement") + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "requestAuth": "", + } + + var body interface{} + body = &struct { + RequestAuth *RequestAuth `json:"requestAuth"` + }{ + RequestAuth: requestAuth, + } + + err := httpRequest(cli, "PUT", urlPath, params, body, nil) + if err != nil { + return err + } + + return nil +} + +// SetFollowProtocol - set whether using the same protocol or not when back to the sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/9jxzi89k2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - isFollowProtocol: true in using the same protocol or not when back to the sourced server, false for other +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetFollowProtocol(cli bce.Client, domain string, isFollowProtocol bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "followProtocol": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + FollowProtocol bool `json:"followProtocol"` + }{ + FollowProtocol: isFollowProtocol, + }, nil) + if err != nil { + return err + } + + return nil +} + +// SetHttpHeader -set some HTTP headers which can be added or deleted when response form CDN edge node +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjxzil1sd +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - httpHeaders: the HTTP headers' setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetHttpHeader(cli bce.Client, domain string, httpHeaders []HttpHeader) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "httpHeader": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + HttpHeaders []HttpHeader `json:"httpHeader"` + }{ + HttpHeaders: httpHeaders, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetHttpHeader - get the HTTP headers' setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/6jxzip3wn +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - []HttpHeader: the HTTP headers in setting +func GetHttpHeader(cli bce.Client, domain string) ([]HttpHeader, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "httpHeader": "", + } + + respObj := &struct { + HttpHeaders []HttpHeader `json:"httpHeader"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.HttpHeaders, nil +} + +// SetErrorPage - set the page that redirected to when error occurred +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ejy6vc4yb +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - errorPages: the custom pages' setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetErrorPage(cli bce.Client, domain string, errorPages []ErrorPage) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "errorPage": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + ErrorPage []ErrorPage `json:"errorPage"` + }{ + ErrorPage: errorPages, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetErrorPage - get the custom pages' setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjy6vfk2u +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - []ErrorPage: the pages' setting +// - error: nil if success otherwise the specific error +func GetErrorPage(cli bce.Client, domain string) ([]ErrorPage, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "errorPage": "", + } + + respObj := &struct { + ErrorPage []ErrorPage `json:"errorPage"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.ErrorPage, nil +} + +// SetCacheShared - set sharing cache with the other domain. +// For example, 1.test.com shared cache with 2.test.com. +// First, we query http://2.test.com/index.html and got missed. +// Secondly, we query http://1.test.com/index.html and got hit +// because of the CacheShared setting before. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0kf272ds7 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - cacheSharedConfig: enabled sets true for shared with the specified domain, otherwise no shared. +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetCacheShared(cli bce.Client, domain string, cacheSharedConfig *CacheShared) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheShare": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, map[string]interface{}{ + "cacheShare": cacheSharedConfig, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetCacheShared - get shared cache setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vo9z2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *CacheShared: shared cache setting +// - error: nil if success otherwise the specific error +func GetCacheShared(cli bce.Client, domain string) (*CacheShared, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheShare": "", + } + + respObj := &struct { + CacheShared CacheShared `json:"cacheShare"` + }{} + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return &respObj.CacheShared, nil +} + +// SetMediaDrag - set the media setting about mp4 and flv +// For details, please refer https://cloud.baidu.com/doc/CDN/s/4jy6v6xk3 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - mediaDragConf: media setting about mp4 and flv +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetMediaDrag(cli bce.Client, domain string, mediaDragConf *MediaDragConf) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "mediaDrag": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + MediaDragConf *MediaDragConf `json:"mediaDragConf"` + }{ + MediaDragConf: mediaDragConf, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetMediaDrag - get the media setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ojy6v9q8f +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *MediaDragConf: the media setting about mp4 and flv +// - error: nil if success otherwise the specific error +func GetMediaDrag(cli bce.Client, domain string) (*MediaDragConf, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "mediaDrag": "", + } + + respObj := &struct { + MediaDragConf *MediaDragConf `json:"mediaDragConf"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.MediaDragConf, nil +} + +// SetFileTrim - trim the text file or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Xjy6vimct +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - fileTrim: true means trimming the text file, false means do nothing +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetFileTrim(cli bce.Client, domain string, fileTrim bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "fileTrim": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + FileTrim bool `json:"fileTrim"` + }{ + FileTrim: fileTrim, + }, nil) + if err != nil { + return err + } + + return nil +} + +// GetFileTrim - get the trim setting about text file +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ujy6vjxnl +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true means trimming the text file, false means do nothing +// - error: nil if success otherwise the specific error +func GetFileTrim(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "fileTrim": "", + } + + respObj := &struct { + FileTrim bool `json:"fileTrim"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.FileTrim, nil +} + +// SetIPv6 - open/close IPv6 +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qkggncsxp +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - enabled: true for setting IPv6 switch on otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetIPv6(cli bce.Client, domain string, enabled bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ipv6Dispatch": "", + } + + reqObj := map[string]interface{}{ + "ipv6Dispatch": map[string]interface{}{ + "enable": enabled, + }, + } + err := httpRequest(cli, "PUT", urlPath, params, reqObj, nil) + if err != nil { + return err + } + + return nil +} + +// GetIPv6 - get IPv6 switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ykggnobxd +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true for setting IPv6 switch on otherwise closed +// - error: nil if success otherwise the specific error +func GetIPv6(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "ipv6Dispatch": "", + } + + respObj := &struct { + Ipv6Dispatch struct { + Enabled bool `json:"enable"` + } `json:"ipv6Dispatch"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.Ipv6Dispatch.Enabled, nil +} + +// SetQUIC - open or close QUIC. open QUIC require enabled HTTPS first +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Qkggmoz7p +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - enabled: true for QUIC opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetQUIC(cli bce.Client, domain string, enabled bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "quic": "", + } + + reqObj := map[string]interface{}{ + "quic": enabled, + } + err := httpRequest(cli, "PUT", urlPath, params, reqObj, nil) + if err != nil { + return err + } + + return nil +} + +// GetQUIC - get QUIC switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/pkggn6l1f +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true for QUIC opening otherwise closed +// - error: nil if success otherwise the specific error +func GetQUIC(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "quic": "", + } + + respObj := &struct { + QUIC bool `json:"quic"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.QUIC, nil +} + +// SetOfflineMode - set "offlineMode" for the specified domain, +// setting true means also response old cached object when got origin server error +// instead of response error to client directly. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/xkhopuj48 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - enabled: true for offlineMode opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetOfflineMode(cli bce.Client, domain string, enabled bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "offlineMode": "", + } + + reqObj := map[string]interface{}{ + "offlineMode": enabled, + } + err := httpRequest(cli, "PUT", urlPath, params, reqObj, nil) + if err != nil { + return err + } + + return nil +} + +// GetOfflineMode - get "offlineMode" switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/tkhopvlkj +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true for offlineMode opening otherwise closed +// - error: nil if success otherwise the specific error +func GetOfflineMode(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "offlineMode": "", + } + + respObj := &struct { + OfflineMode bool `json:"offlineMode"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.OfflineMode, nil +} + +// SetMobileAccess - distinguish the client or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vmv6g +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - distinguishClient: true means distinguishing the client, false means not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetMobileAccess(cli bce.Client, domain string, distinguishClient bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "mobileAccess": "", + } + + type mobileAccess struct { + DistinguishClient bool `json:"distinguishClient"` + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + MobileAccess *mobileAccess `json:"mobileAccess"` + }{ + MobileAccess: &mobileAccess{ + DistinguishClient: distinguishClient, + }, + }, nil) + + return err +} + +// GetMobileAccess - get the setting about distinguishing the client or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vo9z2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true means distinguishing the client, false means not +// - error: nil if success otherwise the specific error +func GetMobileAccess(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "mobileAccess": "", + } + + respObj := &struct { + MobileAccess *struct { + DistinguishClient bool `json:"distinguishClient"` + } `json:"mobileAccess"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return false, err + } + + return respObj.MobileAccess.DistinguishClient, nil +} + +// SetClientIp - set the specified HTTP header for the origin server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Kjy6umyrm +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - clientIp: header setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetClientIp(cli bce.Client, domain string, clientIp *ClientIp) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "clientIp": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + ClientIp *ClientIp `json:"clientIp"` + }{ + ClientIp: clientIp, + }, nil) + + return err +} + +// GetClientIp - get the setting about getting client IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jy6urcq5 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *ClientIp: the HTTP header setting for origin server to get client IP +// - error: nil if success otherwise the specific error +func GetClientIp(cli bce.Client, domain string) (*ClientIp, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "clientIp": "", + } + + respObj := &struct { + ClientIp *ClientIp `json:"clientIp"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.ClientIp, nil +} + +// SetRetryOrigin - set the policy for retry origin servers if got failed +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ukhopl3bq +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - retryOrigin: retry policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetRetryOrigin(cli bce.Client, domain string, retryOrigin *RetryOrigin) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "retryOrigin": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + RetryOrigin *RetryOrigin `json:"retryOrigin"` + }{ + RetryOrigin: retryOrigin, + }, nil) + + return err +} + +// GetRetryOrigin - get the policy for retry origin servers +// For details, please refer https://cloud.baidu.com/doc/CDN/s/bkhoppbhd +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *RetryOrigin: policy of retry origin servers +// - error: nil if success otherwise the specific error +func GetRetryOrigin(cli bce.Client, domain string) (*RetryOrigin, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "retryOrigin": "", + } + + respObj := &struct { + RetryOrigin *RetryOrigin `json:"retryOrigin"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.RetryOrigin, nil +} + +// SetAccessLimit - set the qps for on one client +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Kjy6v02wt +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - accessLimit: the access setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetAccessLimit(cli bce.Client, domain string, accessLimit *AccessLimit) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "accessLimit": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + AccessLimit *AccessLimit `json:"accessLimit"` + }{ + AccessLimit: accessLimit, + }, nil) + + return err +} + +// GetAccessLimit - get the qps setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/rjy6v3tnr +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *AccessLimit: the access setting +// - error: nil if success otherwise the specific error +func GetAccessLimit(cli bce.Client, domain string) (*AccessLimit, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "accessLimit": "", + } + + respObj := &struct { + AccessLimit *AccessLimit `json:"accessLimit"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.AccessLimit, nil +} + +// SetCacheUrlArgs - tell the CDN system cache the url's params or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/vjxzho0kx +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - cacheFullUrl: whether cache the full url or not, full url means include params, also some extra params can be avoided +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetCacheUrlArgs(cli bce.Client, domain string, cacheFullUrl *CacheUrlArgs) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheFullUrl": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, cacheFullUrl, nil) + + return err +} + +// GetCacheUrlArgs - get the cached rules +// For details, please refer https://cloud.baidu.com/doc/CDN/s/sjxzhsb6h +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *CacheUrlArgs: the details about cached rules +// - error: nil if success otherwise the specific error +func GetCacheUrlArgs(cli bce.Client, domain string) (*CacheUrlArgs, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cacheFullUrl": "", + } + + respObj := &CacheUrlArgs{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + if err != nil { + return nil, err + } + + return respObj, nil +} + +// SetTlsVersions - set some TLS versions that you want +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - tlsVersions: TLS version settings +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetTlsVersions(cli bce.Client, domain string, tlsVersions []string) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "tlsVersions": "", + } + + reqData := &struct { + Data []string `json:"data"` + }{ + Data: tlsVersions, + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + TlsVersions interface{} `json:"tlsVersions"` + }{ + TlsVersions: reqData, + }, nil) + + return err +} + +// SetCors - set about Cross-origin resource sharing +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Rjxzi1cfs +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - isAllow: true means allow Cors, false means not allow +// - originList: the origin setting, it's invalid when isAllow is false +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetCors(cli bce.Client, domain string, isAllow bool, originList []string) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cors": "", + } + + allow := "off" + var origins []string + origins = []string{} + + if isAllow { + allow = "on" + origins = originList + } + + reqData := &struct { + Allow string `json:"allow"` + OriginList []string `json:"originList"` + }{ + Allow: allow, + OriginList: origins, + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + Cors interface{} `json:"cors"` + }{ + Cors: reqData, + }, nil) + + return err +} + +// GetCors - get the Cors setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/tjxzi2d7t +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - *CorsCfg: the Cors setting +// - error: nil if success otherwise the specific error +func GetCors(cli bce.Client, domain string) (*CorsCfg, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "cors": "", + } + + respObj := &struct { + Allow string `json:"allow"` + OriginList []string `json:"originList,omitempty"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, &struct { + Cors interface{} `json:"cors"` + }{ + Cors: respObj, + }) + + if err != nil { + return nil, err + } + + corsCfg := &CorsCfg{ + IsAllow: false, + Origins: respObj.OriginList, + } + + if strings.ToUpper(respObj.Allow) == "ON" { + corsCfg.IsAllow = true + } + + return corsCfg, nil +} + +// SetRangeSwitch - set the range setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Fjxziabst +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - enabled: true means enable range cached, false means disable range cached +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetRangeSwitch(cli bce.Client, domain string, enabled bool) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "rangeSwitch": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + RangeSwitch bool `json:"rangeSwitch"` + }{ + RangeSwitch: enabled, + }, nil) + + return err +} + +// GetRangeSwitch - get the range setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jxzid6o9 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - bool: true means enable range cached, false means disable range cached +// - error: nil if success otherwise the specific error +func GetRangeSwitch(cli bce.Client, domain string) (bool, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "rangeSwitch": "", + } + + respObj := &struct { + RangeSwitch string `json:"rangeSwitch"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, respObj) + + if err != nil { + return false, err + } + + enabled := false + if strings.ToUpper(respObj.RangeSwitch) == "ON" { + enabled = true + } + + return enabled, nil +} + +// SetContentEncoding - set Content-Encoding +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0jyqyahsb +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - enabled: true means using the specified encoding algorithm indicated by "encodingType" in transferring, +// false means disable encoding +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetContentEncoding(cli bce.Client, domain string, enabled bool, encodingType string) error { + if enabled && encodingType != "gzip" && encodingType != "br" && encodingType != "all" { + errMsg := fmt.Sprintf("invalid encoding type \"%s\" for setting Content-Encoding,"+ + " it must in \"gzip\", \"br\" and \"all\"", encodingType) + return errors.New(errMsg) + } + + if !enabled { + encodingType = "" + } + + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "compress": "", + } + + respObj := &struct { + Allow bool `json:"allow"` + Type string `json:"type,omitempty"` + }{ + Allow: enabled, + Type: encodingType, + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + ContentEncoding interface{} `json:"compress"` + }{ + ContentEncoding: respObj, + }, nil) + + return err +} + +// GetContentEncoding - get the setting about Content-Encoding +// For details, please refer https://cloud.baidu.com/doc/CDN/s/bjyqycw8g +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - string: the encoding algorithm for transferring, empty means disable encoding in transferring +// - error: nil if success otherwise the specific error +func GetContentEncoding(cli bce.Client, domain string) (string, error) { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "compress": "", + } + + respObj := &struct { + Allow string `json:"allow"` + Type string `json:"type,omitempty"` + }{} + + err := httpRequest(cli, "GET", urlPath, params, nil, &struct { + ContentEncoding interface{} `json:"compress"` + }{ + ContentEncoding: respObj, + }) + + if err != nil { + return "", err + } + + contentEncoding := respObj.Type + if respObj.Allow == "off" { + contentEncoding = "" + } + + return contentEncoding, nil +} diff --git a/bce-sdk-go/services/cdn/api/dsa.go b/bce-sdk-go/services/cdn/api/dsa.go new file mode 100644 index 0000000..bee1b07 --- /dev/null +++ b/bce-sdk-go/services/cdn/api/dsa.go @@ -0,0 +1,115 @@ +package api + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" +) + +// DSARule defined a struct for DSA urls +type DSARule struct { + Type string `json:"type"` + Value string `json:"value"` +} + +// DSADomain defined a struct about the specified domain's DSA setting +type DSADomain struct { + Domain string `json:"domain"` + Rules []DSARule `json:"rules"` + ModifyTime string `json:"modifyTime"` + Comment string `json:"comment"` +} + +// DSAConfig defined a struct for DSA configuration +type DSAConfig struct { + Enabled bool `json:"enabled"` + Rules []DSARule `json:"rules"` + Comment string `json:"comment,omitempty"` +} + +func setDsa(cli bce.Client, action string) error { + err := httpRequest(cli, "PUT", "/v2/dsa", nil, &struct { + Action string `json:"action"` + }{ + Action: action, + }, nil) + if err != nil { + return err + } + + return nil +} + +// EnableDsa - enable DSA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7jwvyf1h5 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - error: nil if success otherwise the specific error +func EnableDsa(cli bce.Client) error { + return setDsa(cli, "enable") +} + +// DisableDsa - disable DSA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7jwvyf1h5 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - error: nil if success otherwise the specific error +func DisableDsa(cli bce.Client) error { + return setDsa(cli, "disable") +} + +// ListDsaDomains - retrieve all the domains in DSA service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf1sq +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - []DSADomain: the details about DSA domains +// - error: nil if success otherwise the specific error +func ListDsaDomains(cli bce.Client) ([]DSADomain, error) { + respObj := &struct { + Domains []DSADomain `json:"domains"` + }{} + + err := httpRequest(cli, "GET", "/v2/dsa/domain", nil, nil, respObj) + if err != nil { + return nil, err + } + + return respObj.Domains, nil +} + +// SetDsaConfig - set the DSA configuration +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0jwvyf26d +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - dsaConfig: the specified configuration for the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SetDsaConfig(cli bce.Client, domain string, dsaConfig *DSAConfig) error { + urlPath := fmt.Sprintf("/v2/domain/%s/config", domain) + params := map[string]string{ + "dsa": "", + } + + err := httpRequest(cli, "PUT", urlPath, params, &struct { + Dsa *DSAConfig `json:"dsa"` + }{ + Dsa: dsaConfig, + }, nil) + if err != nil { + return err + } + + return nil +} diff --git a/bce-sdk-go/services/cdn/api/log.go b/bce-sdk-go/services/cdn/api/log.go new file mode 100644 index 0000000..0555c2e --- /dev/null +++ b/bce-sdk-go/services/cdn/api/log.go @@ -0,0 +1,169 @@ +package api + +import ( + "errors" + "fmt" + "time" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/util" +) + +type LogBase struct { + Domain string `json:"domain"` + Url string `json:"url"` + Name string `json:"name"` + Size int64 `json:"size"` +} + +// TimeInterval defined a struct contains the started time and the end time +type TimeInterval struct { + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +// LogEntry defined a struct for log information +type LogEntry struct { + *LogBase + *TimeInterval +} + +// LogQueryData defined a struct for query conditions +type LogQueryData struct { + TimeInterval + Type int `json:"type,omitempty"` + Domains []string `json:"domains,omitempty"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` +} + +// GetDomainLog -get one domain's log urls +// For details, please refer https://cloud.baidu.com/doc/CDN/s/cjwvyf0r9 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - timeInterval: the specified time interval +// +// RETURNS: +// - []LogEntry: the log detail list +// - error: nil if success otherwise the specific error +func GetDomainLog(cli bce.Client, domain string, timeInterval TimeInterval) ([]LogEntry, error) { + if err := checkTimeInterval(timeInterval, 14*24*60*60); err != nil { + return nil, err + } + + urlPath := fmt.Sprintf("/v2/log/%s/log", domain) + params := map[string]string{} + params["startTime"] = timeInterval.StartTime + params["endTime"] = timeInterval.EndTime + + respObj := &struct { + Logs []LogEntry `json:"logs"` + }{} + + if err := httpRequest(cli, "GET", urlPath, params, nil, respObj); err != nil { + return nil, err + } + + for i, _ := range respObj.Logs { + respObj.Logs[i].Domain = domain + } + + return respObj.Logs, nil +} + +// GetMultiDomainLog - get many domains' log urls +// For details, please refer https://cloud.baidu.com/doc/CDN/API.html#.49.B0.4F.9D.D3.1A.FB.6F.59.A6.8A.B6.08.E9.BC.EF +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryData: the querying conditions +// - error: nil if success otherwise the specific error +func GetMultiDomainLog(cli bce.Client, queryData *LogQueryData) ([]LogEntry, error) { + if queryData == nil { + return nil, errors.New("queryData could not be nil") + } + if err := checkTimeInterval(queryData.TimeInterval, 180*24*60*60); err != nil { + return nil, err + } + + if queryData.PageNo == 0 { + queryData.PageNo = 1 + } + if queryData.PageNo < 0 { + return nil, errors.New("invalid queryData.PageNo, it should be larger than 0") + } + + if queryData.PageSize < 0 { + return nil, errors.New("invalid queryData.PageSize, it should be larger than 0") + } + + respObj := &struct { + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + TotalCount int `json:"totalCount"` + Urls []struct { + *LogBase + LogTimeBegin string `json:"logTimeBegin"` + LogTimeEnd string `json:"logTimeEnd"` + } `json:"urls"` + }{} + + err := httpRequest(cli, "POST", "/v2/log/list", nil, queryData, respObj) + if err != nil { + return nil, err + } + + result := make([]LogEntry, 0, len(respObj.Urls)) + + for i, _ := range respObj.Urls { + log := LogEntry{ + LogBase: respObj.Urls[i].LogBase, + TimeInterval: &TimeInterval{ + StartTime: respObj.Urls[i].LogTimeBegin, + EndTime: respObj.Urls[i].LogTimeEnd, + }, + } + result = append(result, log) + } + + return result, nil +} + +func checkTimeInterval(timeInterval TimeInterval, maxTimeRange int64) error { + if timeInterval.StartTime == "" { + return errors.New("lack of startTime") + } + + if timeInterval.EndTime == "" { + return errors.New("lack of endTime") + } + + st, err := util.ParseISO8601Date(timeInterval.StartTime) + if err != nil { + return errors.New(fmt.Sprintf("invalid startTime, %s", err.Error())) + } + startTs := st.Unix() + + et, err := util.ParseISO8601Date(timeInterval.EndTime) + if err != nil { + return errors.New(fmt.Sprintf("invalid endTime, %s", err.Error())) + } + endTs := et.Unix() + + if startTs > endTs { + return errors.New(fmt.Sprintf("startTime shouble be less than endTime")) + } + + curTs := time.Now().Unix() + if curTs-startTs > maxTimeRange { + return errors.New(fmt.Sprintf("only the first %d seconds of log files can be downloaded, please reset startTime", maxTimeRange)) + } + + if startTs > curTs { + return errors.New("startTime could not larger than current time") + } + + return nil +} diff --git a/bce-sdk-go/services/cdn/api/stat.go b/bce-sdk-go/services/cdn/api/stat.go new file mode 100644 index 0000000..b59049f --- /dev/null +++ b/bce-sdk-go/services/cdn/api/stat.go @@ -0,0 +1,767 @@ +package api + +import ( + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + statisticsObjectKey = "/v2/stat/query" + statisticsBillingKey = "/v2/billing" +) + +// QueryCondition defined a struct for query condition +type QueryCondition struct { + EndTime string `json:"endTime,omitempty"` + StartTime string `json:"startTime,omitempty"` + Period int `json:"period,omitempty"` + KeyType int `json:"key_type"` + Key []string `json:"key,omitempty"` + GroupBy string `json:"groupBy,omitempty"` +} + +type DetailBase struct { + Timestamp string `json:"timestamp"` + Key string `json:"key,omitempty"` +} + +// NetSite defined a struct for the ISP's information +type NetSite struct { + Location string `json:"location"` + Isp string `json:"isp"` +} + +type AvgSpeedRegionData struct { + *NetSite + AvgSpeed int64 `json:"avgspeed"` +} + +type AvgSpeedDetail struct { + *DetailBase + AvgSpeed int64 `json:"avgspeed"` +} + +type AvgSpeedRegionDetail struct { + *DetailBase + Distribution []AvgSpeedRegionData `json:"distribution"` +} + +type PvDetail struct { + *DetailBase + Pv int64 `json:"pv"` + Qps int64 `json:"qps"` +} + +type PVRegionData struct { + *NetSite + Pv int64 `json:"pv"` + Qps int64 `json:"qps"` +} + +type PvRegionDetail struct { + *DetailBase + Distribution []PVRegionData `json:"distribution"` +} + +type UvDetail struct { + *DetailBase + Uv int64 `json:"uv"` +} + +type FlowDetail struct { + *DetailBase + Flow float64 `json:"flow"` + Bps int64 `json:"bps"` +} + +type FlowRegionData struct { + *NetSite + Flow float64 `json:"flow"` + Bps int64 `json:"bps"` +} + +type FlowRegionDetail struct { + *DetailBase + Distribution []FlowRegionData `json:"distribution"` +} + +type HitDetail struct { + *DetailBase + HitRate float64 `json:"hitrate"` +} + +type KvCounter struct { + Name int64 `json:"name"` + Count int64 `json:"count"` +} + +type HttpCodeDetail struct { + *DetailBase + Counters []KvCounter `json:"counters"` +} + +type HttpCodeRegionData struct { + *NetSite + Counters []KvCounter `json:"counters"` +} + +type HttpCodeRegionDetail struct { + *DetailBase + Distribution []HttpCodeRegionData `json:"distribution"` +} + +type TopNCounter struct { + Name string `json:"name"` + Pv int64 `json:"pv"` + Flow float64 `json:"flow"` +} + +type TopNDetail struct { + *DetailBase + TotalPv int64 `json:"total_pv"` + TotalFlow float64 `json:"total_flow"` + Counters []TopNCounter `json:"counters"` +} + +type ErrorKvCounter struct { + Code string `json:"code"` + Counters []KvCounter `json:"counters"` +} + +type ErrorDetail struct { + *DetailBase + Counters []ErrorKvCounter `json:"counters"` +} + +// GetAvgSpeed - get the average speed +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E5%B9%B3%E5%9D%87%E9%80%9F%E7%8E%87 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []AvgSpeedDetail: the detail list about the average speed +// - error: nil if success otherwise the specific error +func GetAvgSpeed(cli bce.Client, queryCondition *QueryCondition) ([]AvgSpeedDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []AvgSpeedDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: "avg_speed", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetAvgSpeed - get the average speed filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%E6%9F%A5%E8%AF%A2%E5%B9%B3%E5%9D%87%E9%80%9F%E7%8E%87 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []AvgSpeedRegionDetail: the detail list about the average speed +// - error: nil if success otherwise the specific error +func GetAvgSpeedByRegion(cli bce.Client, queryCondition *QueryCondition, prov string, isp string) ([]AvgSpeedRegionDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []AvgSpeedRegionDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Prov string `json:"prov,omitempty"` + Isp string `json:"isp,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Prov: prov, + Isp: isp, + Metric: "avg_speed_region", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetPv - get the PV data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#pvqps%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - level: the node level, the available values are "edge", "internal" and "all" +// +// RETURNS: +// - []PvDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func GetPv(cli bce.Client, queryCondition *QueryCondition, level string) ([]PvDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []PvDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Level string `json:"level,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Level: level, + Metric: "pv", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetSrcPv - get the PV data in back to the sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%9B%9E%E6%BA%90pvqps%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []PvDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func GetSrcPv(cli bce.Client, queryCondition *QueryCondition) ([]PvDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []PvDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: "pv_src", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetAvgPvByRegion - get the PV data filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2pvqps%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []PvRegionDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func GetPvByRegion(cli bce.Client, queryCondition *QueryCondition, prov string, isp string) ([]PvRegionDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []PvRegionDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Prov string `json:"prov,omitempty"` + Isp string `json:"isp,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Prov: prov, + Isp: isp, + Metric: "pv_region", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetUv - get the UV data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#uv%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []UvDetail: the detail list about unique visitor +// - error: nil if success otherwise the specific error +func GetUv(cli bce.Client, queryCondition *QueryCondition) ([]UvDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []UvDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Level string `json:"level,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: "uv", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetFlow - get the flow data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func GetFlow(cli bce.Client, queryCondition *QueryCondition, level string) ([]FlowDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []FlowDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Level string `json:"level,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Level: level, + Metric: "flow", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetFlowByProtocol - get the flow data filter by protocol +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD%E5%88%86%E5%8D%8F%E8%AE%AE +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - protocol: the specified HTTP protocol, like "http" or "https", "all" means both "http" and "https" +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func GetFlowByProtocol(cli bce.Client, queryCondition *QueryCondition, protocol string) ([]FlowDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []FlowDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Protocol string `json:"protocol,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Protocol: protocol, + Metric: "flow_protocol", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetFlowByRegion - get the flow data filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD%EF%BC%88%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%EF%BC%89 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []FlowRegionDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func GetFlowByRegion(cli bce.Client, queryCondition *QueryCondition, prov string, isp string) ([]FlowRegionDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []FlowRegionDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Prov string `json:"prov,omitempty"` + Isp string `json:"isp,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Prov: prov, + Isp: isp, + Metric: "flow_region", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetSrcFlow - get the flow data in backed to sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E5%9B%9E%E6%BA%90%E6%B5%81%E9%87%8F%E3%80%81%E5%9B%9E%E6%BA%90%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func GetSrcFlow(cli bce.Client, queryCondition *QueryCondition) ([]FlowDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []FlowDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Protocol string `json:"protocol,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: "src_flow", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +func getHit(cli bce.Client, queryCondition *QueryCondition, metric string) ([]HitDetail, error) { + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []HitDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Protocol string `json:"protocol,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: metric, + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetRealHit - get the detail about byte hit rate +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%AD%97%E8%8A%82%E5%91%BD%E4%B8%AD%E7%8E%87%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HitDetail: the detail list about byte rate +// - error: nil if success otherwise the specific error +func GetRealHit(cli bce.Client, queryCondition *QueryCondition) ([]HitDetail, error) { + return getHit(cli, queryCondition, "real_hit") +} + +// GetPvHit - get the detail about PV hit rate +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E8%AF%B7%E6%B1%82%E5%91%BD%E4%B8%AD%E7%8E%87%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HitDetail: the detail list about pv rate +// - error: nil if success otherwise the specific error +func GetPvHit(cli bce.Client, queryCondition *QueryCondition) ([]HitDetail, error) { + return getHit(cli, queryCondition, "pv_hit") +} + +func getHttpCode(cli bce.Client, queryCondition *QueryCondition, metric string) ([]HttpCodeDetail, error) { + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []HttpCodeDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Protocol string `json:"protocol,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: metric, + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetHttpCode - get the http code's statistics +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E7%8A%B6%E6%80%81%E7%A0%81%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HttpCodeDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func GetHttpCode(cli bce.Client, queryCondition *QueryCondition) ([]HttpCodeDetail, error) { + return getHttpCode(cli, queryCondition, "httpcode") +} + +// GetSrcHttpCode - get the http code's statistics in backed to sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%9B%9E%E6%BA%90%E7%8A%B6%E6%80%81%E7%A0%81%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HttpCodeDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func GetSrcHttpCode(cli bce.Client, queryCondition *QueryCondition) ([]HttpCodeDetail, error) { + return getHttpCode(cli, queryCondition, "src_httpcode") +} + +// GetHttpCodeByRegion - get the http code's statistics filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E7%8A%B6%E6%80%81%E7%A0%81%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2%EF%BC%88%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%EF%BC%89 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []HttpCodeRegionDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func GetHttpCodeByRegion(cli bce.Client, queryCondition *QueryCondition, prov string, isp string) ([]HttpCodeRegionDetail, error) { + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []HttpCodeRegionDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Prov string `json:"prov,omitempty"` + Isp string `json:"isp,omitempty"` + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Prov: prov, + Isp: isp, + Metric: "httpcode_region", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +func getTopN(cli bce.Client, queryCondition *QueryCondition, httpCode string, metric string) ([]TopNDetail, error) { + extra, err := strconv.ParseInt(httpCode, 10, 64) + if err != nil { + extra = 0 + } + + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []TopNDetail `json:"details"` + }{} + + err = httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Protocol string `json:"protocol,omitempty"` + Metric string `json:"metric"` + Extra int64 `json:"extra,omitempty"` + }{ + QueryCondition: queryCondition, + Metric: metric, + Extra: extra, + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetTopNUrls - get the top N urls that requested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-urls +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N urls' detail +// - error: nil if success otherwise the specific error +func GetTopNUrls(cli bce.Client, queryCondition *QueryCondition, httpCode string) ([]TopNDetail, error) { + return getTopN(cli, queryCondition, httpCode, "top_urls") +} + +// GetTopNReferers - get the top N urls that brought by requested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-referers +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N referer urls' detail +// - error: nil if success otherwise the specific error +func GetTopNReferers(cli bce.Client, queryCondition *QueryCondition, httpCode string) ([]TopNDetail, error) { + return getTopN(cli, queryCondition, httpCode, "top_referers") +} + +// GetTopNDomains - get the top N domains that requested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-domains +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N domains' detail +// - error: nil if success otherwise the specific error +func GetTopNDomains(cli bce.Client, queryCondition *QueryCondition, httpCode string) ([]TopNDetail, error) { + return getTopN(cli, queryCondition, httpCode, "top_domains") +} + +// GetError - get the error code's data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#cdn%E9%94%99%E8%AF%AF%E7%A0%81%E5%88%86%E7%B1%BB%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - queryCondition: the querying conditions +// +// RETURNS: +// - []ErrorDetail: the top N error details +// - error: nil if success otherwise the specific error +func GetError(cli bce.Client, queryCondition *QueryCondition) ([]ErrorDetail, error) { + respObj := &struct { + Status string `json:"status"` + Count int64 `json:"count"` + Details []ErrorDetail `json:"details"` + }{} + + err := httpRequest(cli, "POST", statisticsObjectKey, nil, &struct { + *QueryCondition + Metric string `json:"metric"` + }{ + QueryCondition: queryCondition, + Metric: "error", + }, respObj) + if err != nil { + return nil, err + } + + return respObj.Details, nil +} + +// GetPeak95Bandwidth - get peak 95 bandwidth for the specified tags or domains. +// For details, pleader refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%9C%8895%E5%B3%B0%E5%80%BC%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - startTime: start time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - endTime: end time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - domains: a list of domains, only one of `tags` and `domains` can contains item +// - tags: a list of tag names, only one of `tags` and `domains` can contains item +// +// RETURNS: +// - string: the peak95 time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - int64: peak95 bandwidth +// - error: nil if success otherwise the specific error +func GetPeak95Bandwidth(cli bce.Client, startTime, endTime string, domains, tags []string) (peak95Time string, peak95Band int64, err error) { + respObj := &struct { + Details struct { + Bandwidth int64 `json:"bill_band"` + Time string `json:"bill_time"` + } `json:"billing_details"` + }{} + + tagOrDomains, withTag := domains, false + if len(tags) != 0 { + tagOrDomains, withTag = tags, true + } + err = httpRequest(cli, "POST", statisticsBillingKey, nil, map[string]interface{}{ + "domains": strings.Join(tagOrDomains, ","), + "type": "peak95", + "withTag": withTag, + "byTime": true, + "startTime": startTime, + "endTime": endTime, + }, respObj) + if err != nil { + return + } + return respObj.Details.Time, respObj.Details.Bandwidth, nil +} diff --git a/bce-sdk-go/services/cdn/api/tool.go b/bce-sdk-go/services/cdn/api/tool.go new file mode 100644 index 0000000..1af347f --- /dev/null +++ b/bce-sdk-go/services/cdn/api/tool.go @@ -0,0 +1,93 @@ +package api + +import "github.com/baidubce/bce-sdk-go/bce" + +// IpInfo defined a struct for IP info +type IpInfo struct { + IP string `json:"ip"` + IsCdnIp bool `json:"cdnIP"` + Isp string `json:"isp"` + Region string `json:"region"` +} + +// BackOriginNode defined a struct for CDN node which may request the origin server if cache missed. +type BackOriginNode struct { + CNNAME string `json:"cnname"` + IP string `json:"ip"` + Level string `json:"level"` + City string `json:"city"` + Province string `json:"province"` + ISP string `json:"isp"` +} + +// GetIpInfo - retrieves information about the specified IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyeunq#%E5%8D%95%E4%B8%AAip%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - ip: the specified ip addr +// - action: the action for operating the ip addr +// +// RETURNS: +// - *IpInfo: the information about the specified ip addr +// - error: nil if success otherwise the specific error +func GetIpInfo(cli bce.Client, ip string, action string) (*IpInfo, error) { + params := map[string]string{ + "ip": ip, + "action": action, + } + + respObj := &IpInfo{} + err := httpRequest(cli, "GET", "/v2/utils", params, nil, respObj) + if err != nil { + return nil, err + } + respObj.IP = ip + return respObj, nil +} + +// GetIpListInfo - retrieves information about the specified IP list +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyeunq#ip-list-%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - ips: IP list +// - action: the action for operating the ip addr +// +// RETURNS: +// - []IpInfo: IP list's information +// - error: nil if success otherwise the specific error +func GetIpListInfo(cli bce.Client, ips []string, action string) ([]IpInfo, error) { + reqObj := map[string]interface{}{ + "ips": ips, + "action": action, + } + + var respObj []IpInfo + err := httpRequest(cli, "POST", "/v2/utils/ips", nil, reqObj, &respObj) + if err != nil { + return nil, err + } + return respObj, nil +} + +// GetBackOriginNodes - get CDN nodes that may request the origin server if cache missed +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyeunq#%E7%99%BE%E5%BA%A6%E5%9B%9E%E6%BA%90ip%E5%9C%B0%E5%9D%80%E6%AE%B5%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3 +// +// PARAMS: +// - cli: the client agent can execute sending request +// +// RETURNS: +// - []BackOriginNode: list of CDN node +// - error: nil if success otherwise the specific error +func GetBackOriginNodes(cli bce.Client) ([]BackOriginNode, error) { + respObj := &struct { + Status int `json:"status"` + BackOriginNodes []BackOriginNode `json:"details"` + }{} + err := httpRequest(cli, "GET", "/v2/nodes/list", nil, nil, &respObj) + if err != nil { + return nil, err + } + return respObj.BackOriginNodes, nil +} diff --git a/bce-sdk-go/services/cdn/api/util.go b/bce-sdk-go/services/cdn/api/util.go new file mode 100644 index 0000000..36af003 --- /dev/null +++ b/bce-sdk-go/services/cdn/api/util.go @@ -0,0 +1,80 @@ +package api + +import ( + "bytes" + "encoding/json" + "errors" + + "github.com/baidubce/bce-sdk-go/bce" +) + +// SendCustomRequest - send a HTTP request, and response data or error, it use the default times for retrying +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - method: the HTTP requested method, e.g. "GET", "POST", "PUT" ... +// - urlPath: a path component, consisting of a sequence of path segments separated by a slash ( / ). +// - params: the query params, which will be append to the query path, and separate by "&" +// e.g. http://www.baidu.com?query_param1=value1&query_param2=value2 +// - reqHeaders: the request http headers +// - bodyObj: the HTTP requested body content transferred to a goland object +// - respObj: the HTTP response content transferred to a goland object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func SendCustomRequest(cli bce.Client, method string, urlPath string, params, reqHeaders map[string]string, bodyObj interface{}, respObj interface{}) error { + if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" { + return errors.New("invalid http method") + } + + req := &bce.BceRequest{} + req.SetUri(urlPath) + req.SetMethod(method) + req.SetParams(params) + req.SetHeaders(reqHeaders) + + if bodyObj != nil { + bodyBytes, err := newBodyFromJsonObj(bodyObj) + if err != nil { + return err + } + req.SetBody(bodyBytes) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + defer func() { + reader := resp.Body() + if reader != nil { + _ = reader.Close() + } + }() + + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(resp.Body()) + respBodyBytes := buf.Bytes() + + if respObj != nil { + err := json.Unmarshal(respBodyBytes, respObj) + if err != nil { + return err + } + } + + return nil +} + +func httpRequest(cli bce.Client, method string, urlPath string, params map[string]string, bodyObj interface{}, respObj interface{}) error { + return SendCustomRequest(cli, method, urlPath, params, nil, bodyObj, respObj) +} + +func newBodyFromJsonObj(obj interface{}) (*bce.Body, error) { + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bce.NewBodyFromBytes(data) +} diff --git a/bce-sdk-go/services/cdn/client.go b/bce-sdk-go/services/cdn/client.go new file mode 100644 index 0000000..be69932 --- /dev/null +++ b/bce-sdk-go/services/cdn/client.go @@ -0,0 +1,1387 @@ +package cdn + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/cdn/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "cdn.baidubce.com" +) + +// Client of CDN service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the CDN service client with default configuration +// Use `cli.Config.xxx` to access the config or change it to non-default value +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// SendCustomRequest - send a HTTP request, and response data or error, it use the default times for retrying +// +// PARAMS: +// - method: the HTTP requested method, e.g. "GET", "POST", "PUT" ... +// - urlPath: a path component, consisting of a sequence of path segments separated by a slash ( / ). +// - params: the query params, which will be append to the query path, and separate by "&" +// e.g. http://www.baidu.com?query_param1=value1&query_param2=value2 +// - reqHeaders: the request http headers +// - bodyObj: the HTTP requested body content transferred to a goland object +// - respObj: the HTTP response content transferred to a goland object +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SendCustomRequest(method string, urlPath string, params, reqHeaders map[string]string, bodyObj interface{}, respObj interface{}) error { + return api.SendCustomRequest(cli, method, urlPath, params, reqHeaders, bodyObj, respObj) +} + +// ListDomains - list all domains that in CDN service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/sjwvyewt1 +// +// PARAMS: +// - marker: a marker is a start point of searching +// +// RETURNS: +// - []string: domains belongs to the user +// - string: a marker for next searching, empty if is in the end +// - error: nil if success otherwise the specific error +func (cli *Client) ListDomains(marker string) ([]string, string, error) { + return api.ListDomains(cli, marker) +} + +// GetDomainStatus - get domains' details +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyewf1 +// +// PARAMS: +// - status: the specified running status, the available values are "RUNNING", "STOPPED", OPERATING or "ALL" +// - rule: the regex matching rule +// +// RETURNS: +// - []DomainStatus: domain details list +// - error: nil if success otherwise the specific error +func (cli *Client) GetDomainStatus(status string, rule string) ([]api.DomainStatus, error) { + return api.GetDomainStatus(cli, status, rule) +} + +// IsValidDomain - check the specified domain whether it can be added to CDN service or not. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjwvyexh6 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *DomainValidInfo: available information about the specified domain +// - error: nil if success otherwise the specific error +func (cli *Client) IsValidDomain(domain string) (*api.DomainValidInfo, error) { + return api.IsValidDomain(cli, domain) +} + +// CreateDomain - add a specified domain into CDN service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/gjwvyex4o +// +// PARAMS: +// - domain: the specified domain +// - originInit: initialized data for a CDN domain +// +// RETURNS: +// - *DomainCreatedInfo: the details about created a CDN domain +// - error: nil if success otherwise the specific error +func (cli *Client) CreateDomain(domain string, originInit *api.OriginInit) (*api.DomainCreatedInfo, error) { + return api.CreateDomain(cli, domain, originInit) +} + +// EnableDomain - enable a specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjwvyexv8 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) EnableDomain(domain string) error { + return api.EnableDomain(cli, domain) +} + +// DisableDomain - disable a specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/9jwvyew3e +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) DisableDomain(domain string) error { + return api.DisableDomain(cli, domain) +} + +// DeleteDomain - delete a specified domain from BCE CDN system. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Njwvyey7f +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) DeleteDomain(domain string) error { + return api.DeleteDomain(cli, domain) +} + +// GetIpInfo - retrieves information about the specified IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyeunq +// +// PARAMS: +// - ip: the specified ip addr +// - action: the action for operating the ip addr +// +// RETURNS: +// - *IpInfo: the information about the specified ip addr +// - error: nil if success otherwise the specific error +func (cli *Client) GetIpInfo(ip string, action string) (*api.IpInfo, error) { + return api.GetIpInfo(cli, ip, action) +} + +// GetIpListInfo - retrieves information about the specified IP list +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jwvyeunq#ip-list-%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3 +// +// PARAMS: +// - ips: IP list +// - action: the action for operating the ip addr +// +// RETURNS: +// - []IpInfo: IP list's information +// - error: nil if success otherwise the specific error +func (cli *Client) GetIpListInfo(ips []string, action string) ([]api.IpInfo, error) { + return api.GetIpListInfo(cli, ips, action) +} + +// GetBackOriginNodes - get CDN nodes that may request the origin server if cache missed +// +// RETURNS: +// - []BackOriginNode: list of CDN node +// - error: nil if success otherwise the specific error +func (cli *Client) GetBackOriginNodes() ([]api.BackOriginNode, error) { + return api.GetBackOriginNodes(cli) +} + +// GetDomainConfig - get the configuration for the specified domain. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/2jwvyf39o +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *DomainConfig: the configuration about the specified domain +// - error: nil if success otherwise the specific error +func (cli *Client) GetDomainConfig(domain string) (*api.DomainConfig, error) { + return api.GetDomainConfig(cli, domain) +} + +// SetDomainOrigin - set the origin setting for the new +// For details, please refer https://cloud.baidu.com/doc/CDN/s/xjxzi7729 +// +// PARAMS: +// - domain: the specified domain +// - origins: the origin servers +// - defaultHost: the default host +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetDomainOrigin(domain string, origins []api.OriginPeer, defaultHost string) error { + return api.SetDomainOrigin(cli, domain, origins, defaultHost) +} + +// SetOriginProtocol - set the http protocol back to backend server. +// The valid "originProtocol" must be "http", "https" or "*", +// "http" means send the HTTP request to the backend server, +// "https" means send the HTTPS request to the backend server, +// "*" means send the request follow the client's requesting protocol. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7k9jdhhlm +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// - originProtocol: the protocol used for back to the backend server +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetOriginProtocol(domain string, originProtocol string) error { + return api.SetOriginProtocol(cli, domain, originProtocol) +} + +// GetOriginProtocol - get the protocol used for back to the backend server. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/dk9jdoob4 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - domain: the specified domain +// +// RETURNS: +// - string: the protocol used for back to the backend server, it's value must be "http", "https" or "*" +// - error: nil if success otherwise the specific error +func (cli *Client) GetOriginProtocol(domain string) (string, error) { + return api.GetOriginProtocol(cli, domain) +} + +// SetDomainSeo - set SEO setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjxziuq4y +// +// PARAMS: +// - domain: the specified domain +// - seoSwitch: the setting about SEO +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetDomainSeo(domain string, seoSwitch *api.SeoSwitch) error { + return api.SetDomainSeo(cli, domain, seoSwitch) +} + +// GetDomainSeo - retrieve the setting about SEO +// There are two types of data that the server responds to +// 1. `{"seoSwitch":[]}` indicates no setting about SEO +// 2. `{"seoSwitch":{"diretlyOrigin":"ON","pushRecord":"OFF"}}` indicates it had normal setting about SEO. +// So the code need to handle the complex affairs +// +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Djxzjfz8f +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *SeoSwitch: the setting about SEO +// - error: nil if success otherwise the specific error +func (cli *Client) GetDomainSeo(domain string) (*api.SeoSwitch, error) { + return api.GetDomainSeo(cli, domain) +} + +// GetCacheTTL - get the current cached setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ljxzhl9bu +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - []CacheTTL: the cache setting list +// - error: nil if success otherwise the specific error +func (cli *Client) GetCacheTTL(domain string) ([]api.CacheTTL, error) { + return api.GetCacheTTL(cli, domain) +} + +// SetCacheTTL - add some rules for cached setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/wjxzhgxnx +// +// PARAMS: +// - domain: the specified domain +// - cacheTTLs: the cache setting list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetCacheTTL(domain string, cacheTTLs []api.CacheTTL) error { + return api.SetCacheTTL(cli, domain, cacheTTLs) +} + +// SetRefererACL - set a rule for filter some HTTP request, blackList and whiteList only one can be set +// For details, please refer https://cloud.baidu.com/doc/CDN/s/yjxzhvf21 +// +// PARAMS: +// - domain: the specified domain +// - blackList: the forbidden host +// - whiteList: the available host +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetRefererACL(domain string, blackList []string, whiteList []string, isAllowEmpty bool) error { + return api.SetRefererACL(cli, domain, blackList, whiteList, isAllowEmpty) +} + +// GetRefererACL - get referer ACL setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ujzkotvtb +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *api.RefererACL: referer ACL setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetRefererACL(domain string) (*api.RefererACL, error) { + return api.GetRefererACL(cli, domain) +} + +// SetIpACL - set a rule for filter some HTTP request, blackList and whiteList only one can be set +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jxzhwc4d +// +// PARAMS: +// - domain: the specified domain +// - blackList: the forbidden ip +// - whiteList: the available ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetIpACL(domain string, blackList []string, whiteList []string) error { + return api.SetIpACL(cli, domain, blackList, whiteList) +} + +// GetIpACL - get black IP or white IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/jjzkp5ku7 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *api.IpACL: ip setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetIpACL(domain string) (*api.IpACL, error) { + return api.GetIpACL(cli, domain) +} + +// SetUaACL - set a rule for filter the specified HTTP header named "User-Agent" +// For details, please refer https://cloud.baidu.com/doc/CDN/s/uk88i2a86 +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// - blackList: the forbidden UA +// - whiteList: the available UA +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetUaACL(domain string, blackList []string, whiteList []string) error { + return api.SetUaACL(cli, domain, blackList, whiteList) +} + +// GetUaACL - get black UA or white UA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ak88ix19h +// +// PARAMS: +// - cli: the client agent can execute sending request +// - domain: the specified domain +// +// RETURNS: +// - *api.UaACL: filter config for UA +// - error: nil if success otherwise the specific error +func (cli *Client) GetUaACL(domain string) (*api.UaACL, error) { + return api.GetUaACL(cli, domain) +} + +// Deprecated, please use SetTrafficLimit as substitute +// SetLimitRate - set limited speed +// +// PARAMS: +// - domain: the specified domain +// - limitRate: the limited rate, "1024" means the transmittal speed is less than 1024 Byte/s +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetLimitRate(domain string, limitRate int) error { + return api.SetLimitRate(cli, domain, limitRate) +} + +// SetTrafficLimit - set the traffic limitation for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ujxzi418e +// +// PARAMS: +// - domain: the specified domain +// - trafficLimit: config of traffic limitation +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetTrafficLimit(domain string, trafficLimit *api.TrafficLimit) error { + return api.SetTrafficLimit(cli, domain, trafficLimit) +} + +// GetTrafficLimit - get the traffic limitation of the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7k4npdru0 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *TrafficLimit: config of traffic limitation +// - error: nil if success otherwise the specific error +func (cli *Client) GetTrafficLimit(domain string) (*api.TrafficLimit, error) { + return api.GetTrafficLimit(cli, domain) +} + +// SetDomainHttps - set a rule for speed HTTPS' request +// For details, please refer https://cloud.baidu.com/doc/CDN/s/rjy6v3tnr +// +// PARAMS: +// - domain: the specified domain +// - httpsConfig: the rules about the HTTP configure +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetDomainHttps(domain string, httpsConfig *api.HTTPSConfig) error { + return api.SetDomainHttps(cli, domain, httpsConfig) +} + +// GetDomainHttps - get the setting about HTTPS +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *HTTPSConfig: the rules about the HTTP configure +// - error: nil if success otherwise the specific error +func (cli *Client) GetDomainHttps(domain string) (*api.HTTPSConfig, error) { + return api.GetDomainHttps(cli, domain) +} + +// PutCert - put the certificate data for the specified domain to server, you can also enable HTTPS or not. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8 +// +// PARAMS: +// - domain: the specified domain +// - userCert: certificate data +// - httpsEnabled: "ON" for enable HTTPS, "OFF" for disable HTTPS, otherwise invalid. +// +// RETURNS: +// - string: certId +// - error: nil if success otherwise the specific error +func (cli *Client) PutCert(domain string, userCert *api.UserCertificate, httpsEnabled string) (certId string, err error) { + return api.PutCert(cli, domain, userCert, httpsEnabled) +} + +// GetCert - query the certificate data for the specified domain. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/kjzuvz70t +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *CertificateDetail: certificate details +// - error: nil if success otherwise the specific error +func (cli *Client) GetCert(domain string) (certDetail *api.CertificateDetail, err error) { + return api.GetCert(cli, domain) +} + +// DeleteCert - delete the certificate data for the specified domain. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ljzuylmee +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *CertificateDetail: certificate details +// - error: nil if success otherwise the specific error +func (cli *Client) DeleteCert(domain string) error { + return api.DeleteCert(cli, domain) +} + +// SetOCSP - set "OCSP" for the specified domain, +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Pkf2c0ugn +// +// PARAMS: +// - domain: the specified domain +// - enabled: true for "OCSP" opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetOCSP(domain string, enabled bool) error { + return api.SetOCSP(cli, domain, enabled) +} + +// GetOCSP - get "OCSP" switch details for the specified domain +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true for "OCSP" opening otherwise closed +// - error: nil if success otherwise the specific error +func (cli *Client) GetOCSP(domain string) (bool, error) { + return api.GetOCSP(cli, domain) +} + +// SetDomainRequestAuth - set the authorized rules for requesting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/njxzi59g9 +// +// PARAMS: +// - domain: the specified domain +// - requestAuth: the rules about the auth +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetDomainRequestAuth(domain string, requestAuth *api.RequestAuth) error { + return api.SetDomainRequestAuth(cli, domain, requestAuth) +} + +// Deprecated: We suggest use the SetOriginProtocol as an alternative +// SetFollowProtocol - set whether using the same protocol or not when back to the sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/9jxzi89k2 +// +// PARAMS: +// - domain: the specified domain +// - isFollowProtocol: true in using the same protocol or not when back to the sourced server, false for other +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetFollowProtocol(domain string, isFollowProtocol bool) error { + return api.SetFollowProtocol(cli, domain, isFollowProtocol) +} + +// SetHttpHeader -set some HTTP headers which can be added or deleted when response form CDN edge node +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Jjxzil1sd +// +// PARAMS: +// - domain: the specified domain +// - httpHeaders: the HTTP headers' setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetHttpHeader(domain string, httpHeaders []api.HttpHeader) error { + return api.SetHttpHeader(cli, domain, httpHeaders) +} + +// GetHttpHeader - get the HTTP headers' setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/6jxzip3wn +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - []HttpHeader: the HTTP headers in setting +func (cli *Client) GetHttpHeader(domain string) ([]api.HttpHeader, error) { + return api.GetHttpHeader(cli, domain) +} + +// SetErrorPage - set the page that redirected to when error occurred +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ejy6vc4yb +// +// PARAMS: +// - domain: the specified domain +// - errorPages: the custom pages' setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetErrorPage(domain string, errorPages []api.ErrorPage) error { + return api.SetErrorPage(cli, domain, errorPages) +} + +// GetErrorPage - get the custom pages' setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qjy6vfk2u +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - []ErrorPage: the pages' setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetErrorPage(domain string) ([]api.ErrorPage, error) { + return api.GetErrorPage(cli, domain) +} + +// SetCacheShared - set sharing cache with the other domain. +// For example, 1.test.com shared cache with 2.test.com. +// First, we query http://2.test.com/index.html and got missed. +// Secondly, we query http://1.test.com/index.html and got hit +// because of the CacheShared setting before. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0kf272ds7 +// +// PARAMS: +// - domain: the specified domain +// - cacheSharedConfig: enabled sets true for shared with the specified domain, otherwise no shared. +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetCacheShared(domain string, config *api.CacheShared) error { + return api.SetCacheShared(cli, domain, config) +} + +// GetCacheShared - get shared cache setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vo9z2 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *CacheShared: shared cache setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetCacheShared(domain string) (*api.CacheShared, error) { + return api.GetCacheShared(cli, domain) +} + +// SetMediaDrag - set the media setting about mp4 and flv +// For details, please refer https://cloud.baidu.com/doc/CDN/s/4jy6v6xk3 +// +// PARAMS: +// - domain: the specified domain +// - mediaDragConf: media setting about mp4 and flv +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetMediaDrag(domain string, mediaDragConf *api.MediaDragConf) error { + return api.SetMediaDrag(cli, domain, mediaDragConf) +} + +// GetMediaDrag - get the media setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ojy6v9q8f +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *MediaDragConf: the media setting about mp4 and flv +// - error: nil if success otherwise the specific error +func (cli *Client) GetMediaDrag(domain string) (*api.MediaDragConf, error) { + return api.GetMediaDrag(cli, domain) +} + +// SetFileTrim - trim the text file or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Xjy6vimct +// +// PARAMS: +// - domain: the specified domain +// - fileTrim: true means trimming the text file, false means do nothing +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetFileTrim(domain string, fileTrim bool) error { + return api.SetFileTrim(cli, domain, fileTrim) +} + +// GetFileTrim - get the trim setting about text file +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ujy6vjxnl +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true means trimming the text file, false means do nothing +// - error: nil if success otherwise the specific error +func (cli *Client) GetFileTrim(domain string) (bool, error) { + return api.GetFileTrim(cli, domain) +} + +// SetIPv6 - open/close IPv6 +// For details, please refer https://cloud.baidu.com/doc/CDN/s/qkggncsxp +// +// PARAMS: +// - domain: the specified domain +// - enabled: true for setting IPv6 switch on otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetIPv6(domain string, enabled bool) error { + return api.SetIPv6(cli, domain, enabled) +} + +// GetIPv6 - get IPv6 switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Ykggnobxd +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true for setting IPv6 switch on otherwise closed +// - error: nil if success otherwise the specific error +func (cli *Client) GetIPv6(domain string) (bool, error) { + return api.GetIPv6(cli, domain) +} + +// SetQUIC - open or close QUIC. open QUIC require enabled HTTPS first +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Qkggmoz7p +// +// PARAMS: +// - domain: the specified domain +// - enabled: true for QUIC opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetQUIC(domain string, enabled bool) error { + return api.SetQUIC(cli, domain, enabled) +} + +// GetQUIC - get QUIC switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/pkggn6l1f +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true for QUIC opening otherwise closed +// - error: nil if success otherwise the specific error +func (cli *Client) GetQUIC(domain string) (bool, error) { + return api.GetQUIC(cli, domain) +} + +// SetOfflineMode - set "offlineMode" for the specified domain, +// setting true means also response old cached object when got origin server error +// instead of response error to client directly. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/xkhopuj48 +// +// PARAMS: +// - domain: the specified domain +// - enabled: true for offlineMode opening otherwise closed +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetOfflineMode(domain string, enabled bool) error { + return api.SetOfflineMode(cli, domain, enabled) +} + +// GetOfflineMode - get "offlineMode" switch details for the specified domain +// For details, please refer https://cloud.baidu.com/doc/CDN/s/tkhopvlkj +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true for offlineMode opening otherwise closed +// - error: nil if success otherwise the specific error +func (cli *Client) GetOfflineMode(domain string) (bool, error) { + return api.GetOfflineMode(cli, domain) +} + +// SetMobileAccess - distinguish the client or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vmv6g +// +// PARAMS: +// - domain: the specified domain +// - distinguishClient: true means distinguishing the client, false means not +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetMobileAccess(domain string, distinguishClient bool) error { + return api.SetMobileAccess(cli, domain, distinguishClient) +} + +// GetMobileAccess - get the setting about distinguishing the client or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Mjy6vo9z2 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true means distinguishing the client, false means not +// - error: nil if success otherwise the specific error +func (cli *Client) GetMobileAccess(domain string) (bool, error) { + return api.GetMobileAccess(cli, domain) +} + +// SetClientIp - set the specified HTTP header for the origin server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Kjy6umyrm +// +// PARAMS: +// - domain: the specified domain +// - clientIp: header setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetClientIp(domain string, clientIp *api.ClientIp) error { + return api.SetClientIp(cli, domain, clientIp) +} + +// GetClientIp - get the setting about getting client IP +// For details, please refer https://cloud.baidu.com/doc/CDN/s/8jy6urcq5 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *ClientIp: the HTTP header setting for origin server to get client IP +// - error: nil if success otherwise the specific error +func (cli *Client) GetClientIp(domain string) (*api.ClientIp, error) { + return api.GetClientIp(cli, domain) +} + +// SetRetryOrigin - set the policy for retry origin servers if got failed +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ukhopl3bq +// +// PARAMS: +// - domain: the specified domain +// - retryOrigin: retry policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetRetryOrigin(domain string, retryOrigin *api.RetryOrigin) error { + return api.SetRetryOrigin(cli, domain, retryOrigin) +} + +// GetRetryOrigin - get the policy for retry origin servers +// For details, please refer https://cloud.baidu.com/doc/CDN/s/bkhoppbhd +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *RetryOrigin: policy of retry origin servers +// - error: nil if success otherwise the specific error +func (cli *Client) GetRetryOrigin(domain string) (*api.RetryOrigin, error) { + return api.GetRetryOrigin(cli, domain) +} + +// SetAccessLimit - set the qps for on one client +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Kjy6v02wt +// +// PARAMS: +// - domain: the specified domain +// - accessLimit: the access setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetAccessLimit(domain string, accessLimit *api.AccessLimit) error { + return api.SetAccessLimit(cli, domain, accessLimit) +} + +// GetAccessLimit - get the qps setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/rjy6v3tnr +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *AccessLimit: the access setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetAccessLimit(domain string) (*api.AccessLimit, error) { + return api.GetAccessLimit(cli, domain) +} + +// SetCacheUrlArgs - tell the CDN system cache the url's params or not +// For details, please refer https://cloud.baidu.com/doc/CDN/s/vjxzho0kx +// +// PARAMS: +// - domain: the specified domain +// - cacheFullUrl: whether cache the full url or not, full url means include params, also some extra params can be avoided +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetCacheUrlArgs(domain string, cacheFullUrl *api.CacheUrlArgs) error { + return api.SetCacheUrlArgs(cli, domain, cacheFullUrl) +} + +// GetCacheUrlArgs - get the cached rules +// For details, please refer https://cloud.baidu.com/doc/CDN/s/sjxzhsb6h +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *CacheUrlArgs: the details about cached rules +// - error: nil if success otherwise the specific error +func (cli *Client) GetCacheUrlArgs(domain string) (*api.CacheUrlArgs, error) { + return api.GetCacheUrlArgs(cli, domain) +} + +// SetCors - set about Cross-origin resource sharing +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Rjxzi1cfs +// PARAMS: +// - domain: the specified domain +// - isAllow: true means allow Cors, false means not allow +// - originList: the origin setting, it's invalid when isAllow is false +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetCors(domain string, isAllow bool, originList []string) error { + return api.SetCors(cli, domain, isAllow, originList) +} + +// GetCors - get the Cors setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/tjxzi2d7t +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - *CorsCfg: the Cors setting +// - error: nil if success otherwise the specific error +func (cli *Client) GetCors(domain string) (*api.CorsCfg, error) { + return api.GetCors(cli, domain) +} + +// SetRangeSwitch - set the range setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Fjxziabst +// +// PARAMS: +// - domain: the specified domain +// - enabled: true means enable range cached, false means disable range cached +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetRangeSwitch(domain string, enabled bool) error { + return api.SetRangeSwitch(cli, domain, enabled) +} + +// GetRangeSwitch - get the range setting +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jxzid6o9 +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - bool: true means enable range cached, false means disable range cached +// - error: nil if success otherwise the specific error +func (cli *Client) GetRangeSwitch(domain string) (bool, error) { + return api.GetRangeSwitch(cli, domain) +} + +// SetContentEncoding - set Content-Encoding +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0jyqyahsb +// +// PARAMS: +// - domain: the specified domain +// - enabled: true means using the specified encoding algorithm indicated by "encodingType" in transferring, +// false means disable encoding +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetContentEncoding(domain string, enabled bool, encodingType string) error { + return api.SetContentEncoding(cli, domain, enabled, encodingType) +} + +// GetContentEncoding - get the setting about Content-Encoding +// For details, please refer https://cloud.baidu.com/doc/CDN/s/bjyqycw8g +// +// PARAMS: +// - domain: the specified domain +// +// RETURNS: +// - string: the encoding algorithm for transferring, empty means disable encoding in transferring +// - error: nil if success otherwise the specific error +func (cli *Client) GetContentEncoding(domain string) (string, error) { + return api.GetContentEncoding(cli, domain) +} + +// Purge - tells the CDN system to purge the specified files +// For more details, please refer https://cloud.baidu.com/doc/CDN/s/ijwvyeyyj +// +// PARAMS: +// - tasks: the tasks about purging the files from the CDN nodes +// +// RETURNS: +// - PurgedId: an ID representing a purged task, using it to search the task progress +// - error: nil if success otherwise the specific error. +func (cli *Client) Purge(tasks []api.PurgeTask) (api.PurgedId, error) { + return api.Purge(cli, tasks) +} + +// GetPurgedStatus - get the purged progress +// For details, please refer https://cloud.baidu.com/doc/CDN/s/ujwvyezqm +// +// PARAMS: +// - queryData: querying conditions, it contains the time interval, the task ID and the specified url +// +// RETURNS: +// - *PurgedStatus: the details about the purged +// - error: nil if success otherwise the specific error +func (cli *Client) GetPurgedStatus(queryData *api.CStatusQueryData) (*api.PurgedStatus, error) { + return api.GetPurgedStatus(cli, queryData) +} + +// Prefetch - tells the CDN system to prefetch the specified files +// For details, please refer https://cloud.baidu.com/doc/CDN/s/Rjwvyf0ff +// +// PARAMS: +// - tasks: the tasks about prefetch the files from the CDN nodes +// - error: nil if success otherwise the specific error +func (cli *Client) Prefetch(tasks []api.PrefetchTask) (api.PrefetchId, error) { + return api.Prefetch(cli, tasks) +} + +// GetPrefetchStatus - get the prefetch progress +// For details, please refer https://cloud.baidu.com/doc/CDN/s/4jwvyf01w +// +// PARAMS: +// - queryData: querying conditions, it contains the time interval, the task ID and the specified url. +// +// RETURNS: +// - *PrefetchStatus: the details about the prefetch +// - error: nil if success otherwise the specific error +func (cli *Client) GetPrefetchStatus(queryData *api.CStatusQueryData) (*api.PrefetchStatus, error) { + return api.GetPrefetchStatus(cli, queryData) +} + +// GetQuota - get the quota about purge and prefetch +// For details, please refer https://cloud.baidu.com/doc/CDN/s/zjwvyeze3 +// +// RETURNS: +// - QuotaDetail: the quota details about a specified user +// - error: nil if success otherwise the specific error +func (cli *Client) GetQuota() (*api.QuotaDetail, error) { + return api.GetQuota(cli) +} + +// GetCacheOpRecords get the history operating records +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jypnzjqt +// +// PARAMS: +// - queryData: querying conditions, it contains the time interval, the task type and the specified url +// +// RETURNS: +// - *RecordDetails: the details about the records +// - error: nil if success otherwise the specific error +func (cli *Client) GetCacheOpRecords(queryData *api.CRecordQueryData) (*api.RecordDetails, error) { + return api.GetCacheOpRecords(cli, queryData) +} + +// EnableDsa - enable DSA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7jwvyf1h5 +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) EnableDsa() error { + return api.EnableDsa(cli) +} + +// DisableDsa - disable DSA +// For details, please refer https://cloud.baidu.com/doc/CDN/s/7jwvyf1h5 +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) DisableDsa() error { + return api.DisableDsa(cli) +} + +// ListDsaDomains - retrieve all the domains in DSA service +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf1sq +// +// RETURNS: +// - []DSADomain: the details about DSA domains +// - error: nil if success otherwise the specific error +func (cli *Client) ListDsaDomains() ([]api.DSADomain, error) { + return api.ListDsaDomains(cli) +} + +// SetDsaConfig - set the DSA configuration +// For details, please refer https://cloud.baidu.com/doc/CDN/s/0jwvyf26d +// +// PARAMS: +// - domain: the specified domain +// - dsaConfig: the specified configuration for the specified domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) SetDsaConfig(domain string, dsaConfig *api.DSAConfig) error { + return api.SetDsaConfig(cli, domain, dsaConfig) +} + +// GetDomainLog -get one domain's log urls +// For details, please refer https://cloud.baidu.com/doc/CDN/s/cjwvyf0r9 +// +// PARAMS: +// - domain: the specified domain +// - timeInterval: the specified time interval +// +// RETURNS: +// - []LogEntry: the log detail list +// - error: nil if success otherwise the specific error +func (cli *Client) GetDomainLog(domain string, timeInterval api.TimeInterval) ([]api.LogEntry, error) { + return api.GetDomainLog(cli, domain, timeInterval) +} + +// GetMultiDomainLog - get many domains' log urls +// For details, please refer https://cloud.baidu.com/doc/CDN/s/cjwvyf0r9 +// +// PARAMS: +// - queryData: the querying conditions +// - error: nil if success otherwise the specific error +func (cli *Client) GetMultiDomainLog(queryData *api.LogQueryData) ([]api.LogEntry, error) { + return api.GetMultiDomainLog(cli, queryData) +} + +// GetAvgSpeed - get the average speed +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E5%B9%B3%E5%9D%87%E9%80%9F%E7%8E%87 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []AvgSpeedDetail: the detail list about the average speed +// - error: nil if success otherwise the specific error +func (cli *Client) GetAvgSpeed(queryCondition *api.QueryCondition) ([]api.AvgSpeedDetail, error) { + return api.GetAvgSpeed(cli, queryCondition) +} + +// GetAvgSpeed - get the average speed filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%E6%9F%A5%E8%AF%A2%E5%B9%B3%E5%9D%87%E9%80%9F%E7%8E%87 +// +// PARAMS: +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []AvgSpeedRegionDetail: the detail list about the average speed +// - error: nil if success otherwise the specific error +func (cli *Client) GetAvgSpeedByRegion(queryCondition *api.QueryCondition, prov string, isp string) ([]api.AvgSpeedRegionDetail, error) { + return api.GetAvgSpeedByRegion(cli, queryCondition, prov, isp) +} + +// GetPv - get the PV data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#pvqps%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// - level: the node level, the available values are "edge", "internal" and "all" +// +// RETURNS: +// - []PvDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func (cli *Client) GetPv(queryCondition *api.QueryCondition, level string) ([]api.PvDetail, error) { + return api.GetPv(cli, queryCondition, level) +} + +// GetSrcPv - get the PV data in back to the sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%9B%9E%E6%BA%90pvqps%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []PvDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func (cli *Client) GetSrcPv(queryCondition *api.QueryCondition) ([]api.PvDetail, error) { + return api.GetSrcPv(cli, queryCondition) +} + +// GetAvgPvByRegion - get the PV data filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2pvqps%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83 +// +// PARAMS: +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []PvRegionDetail: the detail list about page view +// - error: nil if success otherwise the specific error +func (cli *Client) GetPvByRegion(queryCondition *api.QueryCondition, prov string, isp string) ([]api.PvRegionDetail, error) { + return api.GetPvByRegion(cli, queryCondition, prov, isp) +} + +// GetUv - get the UV data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#uv%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []UvDetail: the detail list about unique visitor +// - error: nil if success otherwise the specific error +func (cli *Client) GetUv(queryCondition *api.QueryCondition) ([]api.UvDetail, error) { + return api.GetUv(cli, queryCondition) +} + +// GetFlow - get the flow data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func (cli *Client) GetFlow(queryCondition *api.QueryCondition, level string) ([]api.FlowDetail, error) { + return api.GetFlow(cli, queryCondition, level) +} + +// GetFlowByProtocol - get the flow data filter by protocol +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD%E5%88%86%E5%8D%8F%E8%AE%AE +// +// PARAMS: +// - queryCondition: the querying conditions +// - protocol: the specified HTTP protocol, like "http" or "https", "all" means both "http" and "https" +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func (cli *Client) GetFlowByProtocol(queryCondition *api.QueryCondition, protocol string) ([]api.FlowDetail, error) { + return api.GetFlowByProtocol(cli, queryCondition, protocol) +} + +// GetFlowByRegion - get the flow data filter by location. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%B5%81%E9%87%8F%E3%80%81%E5%B8%A6%E5%AE%BD%EF%BC%88%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%EF%BC%89 +// +// PARAMS: +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []FlowRegionDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func (cli *Client) GetFlowByRegion(queryCondition *api.QueryCondition, prov string, isp string) ([]api.FlowRegionDetail, error) { + return api.GetFlowByRegion(cli, queryCondition, prov, isp) +} + +// GetSrcFlow - get the flow data in backed to sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E5%9B%9E%E6%BA%90%E6%B5%81%E9%87%8F%E3%80%81%E5%9B%9E%E6%BA%90%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []FlowDetail: the detail list about flow +// - error: nil if success otherwise the specific error +func (cli *Client) GetSrcFlow(queryCondition *api.QueryCondition) ([]api.FlowDetail, error) { + return api.GetSrcFlow(cli, queryCondition) +} + +// GetRealHit - get the detail about byte hit rate +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%AD%97%E8%8A%82%E5%91%BD%E4%B8%AD%E7%8E%87%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HitDetail: the detail list about byte rate +// - error: nil if success otherwise the specific error +func (cli *Client) GetRealHit(queryCondition *api.QueryCondition) ([]api.HitDetail, error) { + return api.GetRealHit(cli, queryCondition) +} + +// GetPvHit - get the detail about PV hit rate. +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E8%AF%B7%E6%B1%82%E5%91%BD%E4%B8%AD%E7%8E%87%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HitDetail: the detail list about pv rate +// - error: nil if success otherwise the specific error +func (cli *Client) GetPvHit(queryCondition *api.QueryCondition) ([]api.HitDetail, error) { + return api.GetPvHit(cli, queryCondition) +} + +// GetHttpCode - get the http code's statistics +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E7%8A%B6%E6%80%81%E7%A0%81%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HttpCodeDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func (cli *Client) GetHttpCode(queryCondition *api.QueryCondition) ([]api.HttpCodeDetail, error) { + return api.GetHttpCode(cli, queryCondition) +} + +// GetSrcHttpCode - get the http code's statistics in backed to sourced server +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E5%9B%9E%E6%BA%90%E7%8A%B6%E6%80%81%E7%A0%81%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []HttpCodeDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func (cli *Client) GetSrcHttpCode(queryCondition *api.QueryCondition) ([]api.HttpCodeDetail, error) { + return api.GetSrcHttpCode(cli, queryCondition) +} + +// GetHttpCodeByRegion - get the http code's statistics filter by location +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E7%8A%B6%E6%80%81%E7%A0%81%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2%EF%BC%88%E5%88%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%BF%E9%97%AE%E5%88%86%E5%B8%83%EF%BC%89 +// +// PARAMS: +// - queryCondition: the querying conditions +// - prov: the specified area, like "beijing" +// - isp: the specified ISP, like "ct" +// +// RETURNS: +// - []HttpCodeRegionDetail: the detail list about http code +// - error: nil if success otherwise the specific error +func (cli *Client) GetHttpCodeByRegion(queryCondition *api.QueryCondition, prov string, isp string) ([]api.HttpCodeRegionDetail, error) { + return api.GetHttpCodeByRegion(cli, queryCondition, prov, isp) +} + +// GetTopNUrls - get the top N urls that requested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-urls +// +// PARAMS: +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N urls' detail +// - error: nil if success otherwise the specific error +func (cli *Client) GetTopNUrls(queryCondition *api.QueryCondition, httpCode string) ([]api.TopNDetail, error) { + return api.GetTopNUrls(cli, queryCondition, httpCode) +} + +// GetTopNReferers - get the top N urls that brought by requested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-referers +// +// PARAMS: +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N referer urls' detail +// - error: nil if success otherwise the specific error +func (cli *Client) GetTopNReferers(queryCondition *api.QueryCondition, httpCode string) ([]api.TopNDetail, error) { + return api.GetTopNReferers(cli, queryCondition, httpCode) +} + +// GetTopNDomains - get the top N domains that equested +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#topn-domains +// +// PARAMS: +// - queryCondition: the querying conditions +// - httpCode: the specified HTTP code, like "200" +// +// RETURNS: +// - []TopNDetail: the top N domains' detail +// - error: nil if success otherwise the specific error +func (cli *Client) GetTopNDomains(queryCondition *api.QueryCondition, httpCode string) ([]api.TopNDetail, error) { + return api.GetTopNDomains(cli, queryCondition, httpCode) +} + +// GetError - get the error code's data +// For details, please refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#cdn%E9%94%99%E8%AF%AF%E7%A0%81%E5%88%86%E7%B1%BB%E7%BB%9F%E8%AE%A1%E6%9F%A5%E8%AF%A2 +// +// PARAMS: +// - queryCondition: the querying conditions +// +// RETURNS: +// - []ErrorDetail: the top N error details +// - error: nil if success otherwise the specific error +func (cli *Client) GetError(queryCondition *api.QueryCondition) ([]api.ErrorDetail, error) { + return api.GetError(cli, queryCondition) +} + +// GetPeak95Bandwidth - get peak 95 bandwidth for the specified tags or domains. +// For details, pleader refer https://cloud.baidu.com/doc/CDN/s/5jwvyf8zn#%E6%9F%A5%E8%AF%A2%E6%9C%8895%E5%B3%B0%E5%80%BC%E5%B8%A6%E5%AE%BD +// +// PARAMS: +// - startTime: start time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - endTime: end time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - domains: a list of domains, only one of `tags` and `domains` can contains item +// - tags: a list of tag names, only one of `tags` and `domains` can contains item +// +// RETURNS: +// - string: the peak95 time which in `YYYY-mm-ddTHH:ii:ssZ` style +// - int64: peak95 bandwidth +// - error: nil if success otherwise the specific error +func (cli *Client) GetPeak95Bandwidth(startTime, endTime string, domains, tags []string) (string, int64, error) { + return api.GetPeak95Bandwidth(cli, startTime, endTime, domains, tags) +} diff --git a/bce-sdk-go/services/cdn/client_test.go b/bce-sdk-go/services/cdn/client_test.go new file mode 100644 index 0000000..b7d84d6 --- /dev/null +++ b/bce-sdk-go/services/cdn/client_test.go @@ -0,0 +1,1049 @@ +package cdn + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/cdn/api" + "github.com/baidubce/bce-sdk-go/util" +) + +const ( + testAuthorityDomain = "your_valid_domain" + testEndpoint = "cdn.baidubce.com" + testAK = "your_access_key_id" + testSK = "your_secret_key_id" + + // set testConfigOk true for unit test + testConfigOk = false +) + +var testCli *Client + +func TestMain(m *testing.M) { + if !testConfigOk { + fmt.Printf("TestMain terminated, please check testing config") + return + } + + var err error + testCli, err = NewClient(testAK, testSK, testEndpoint) + if err != nil { + fmt.Printf("TestMain terminated, err:%+v\n", err) + return + } + + if err := prepareForTest(testAuthorityDomain); err != nil { + fmt.Printf("TestMain terminated, error:%s", err.Error()) + return + } + + m.Run() +} + +func prepareForTest(domain string) error { + _, _ = testCli.CreateDomain(testAuthorityDomain, &api.OriginInit{ + Origin: []api.OriginPeer{ + { + Peer: "1.2.3.4", + Host: "1.2.3.4", + }, + }, + }) + + domainStatus, err := testCli.GetDomainStatus("ALL", "") + if err != nil { + return err + } + + for _, item := range domainStatus { + if item.Domain == domain { + return nil + } + } + + return fmt.Errorf("prepare failed, invalid domain:%s", domain) +} + +func checkClientErr(t *testing.T, funcName string, err error) { + //time.Sleep(time.Second * 1) + if funcName == "" { + t.Fatalf(`error param when called checkClientErr, the funcName is ""`) + } + + if !testConfigOk { + t.Logf("Configuration did not complete initialization\n") + return + } + + if err == nil { + return + } + + e, ok := err.(*bce.BceServiceError) + if !ok { + t.Fatalf("%s: %v\n", funcName, err) + return + } + + // `AccessDenied` indicates unauthorized AK/SK. + // `InvalidArgument` indicates sending the error params to server. + // `NotFound` indicates using error method. + if e.Code == "AccessDenied" || e.Code == "InvalidArgument" || e.Code == "NotFound" { + t.Fatalf("%s: %v\n", funcName, err) + } + + // we do not judge the errors in business logic. + t.Logf("%s: UT is ok, but there is a logic error:\n%s", funcName, err.Error()) +} + +func TestSendCustomRequest(t *testing.T) { + method := "GET" + urlPath := fmt.Sprintf("/v2/domain/%s/valid", testAuthorityDomain) + var params map[string]string + reqHeaders := map[string]string{ + "X-Test": "go-sdk-test", + } + var bodyObj interface{} + var respObj interface{} + err := testCli.SendCustomRequest(method, urlPath, params, reqHeaders, bodyObj, &respObj) + t.Logf("respObj details:\n\ttype:%T\n\tvalue:%+v", respObj, respObj) + checkClientErr(t, "SendCustomRequest", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about operating domain. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestListDomains(t *testing.T) { + domains, _, err := testCli.ListDomains("") + + t.Logf("domains: %v", domains) + checkClientErr(t, "ListDomains", err) +} + +func TestGetDomainStatus(t *testing.T) { + domainStatus, err := testCli.GetDomainStatus("ALL", "") + + t.Logf("domainStatus: %v", domainStatus) + checkClientErr(t, "GetDomainStatus", err) +} + +func TestIsValidDomain(t *testing.T) { + domainValidInfo, err := testCli.IsValidDomain(testAuthorityDomain) + + t.Logf("domainValidInfo: %v", domainValidInfo) + checkClientErr(t, "IsValidDomain", err) +} + +func TestCreateDomain(t *testing.T) { + domainCreatedInfo, err := testCli.CreateDomain(testAuthorityDomain, &api.OriginInit{ + Origin: []api.OriginPeer{ + { + Peer: "1.2.3.4", + Host: "1.2.3.4", + }, + }, + }) + + t.Logf("domainCreatedInfo: %v", domainCreatedInfo) + checkClientErr(t, "CreateDomain", err) +} + +func TestDisableDomain(t *testing.T) { + err := testCli.DisableDomain(testAuthorityDomain) + checkClientErr(t, "DisableDomain", err) +} + +func TestEnableDomain(t *testing.T) { + err := testCli.EnableDomain(testAuthorityDomain) + checkClientErr(t, "EnableDomain", err) +} + +// ignore delete +//func TestDeleteDomain(t *testing.T) { +// err := testCli.DeleteDomain(testAuthorityDomain) +// checkClientErr(t, "TestDeleteDomain", err) +//} + +func TestGetIpInfo(t *testing.T) { + ipInfo, err := testCli.GetIpInfo("116.114.98.35", "describeIp") + t.Logf("ipInfo: %v", ipInfo) + checkClientErr(t, "GetIpInfo", err) +} + +func TestGetIpListInfo(t *testing.T) { + ipsInfo, err := testCli.GetIpListInfo([]string{"116.114.98.35", "59.24.3.174"}, "describeIp") + t.Logf("ipsInfo: %+v", ipsInfo) + checkClientErr(t, "GetIpListInfo", err) +} + +func TestGetBackOriginNodes(t *testing.T) { + backOriginNodes, err := testCli.GetBackOriginNodes() + t.Logf("backOriginNodes: %+v", backOriginNodes) + checkClientErr(t, "GetBackOriginNodes", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about CRUD domain config. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestGetDomainConfig(t *testing.T) { + domainConfig, err := testCli.GetDomainConfig(testAuthorityDomain) + + data, _ := json.Marshal(domainConfig) + t.Logf("domainConfig: %s", string(data)) + checkClientErr(t, "GetDomainConfig", err) +} + +func TestSetDomainOrigin(t *testing.T) { + err := testCli.SetDomainOrigin(testAuthorityDomain, []api.OriginPeer{ + { + Peer: "1.1.1.1", + Host: "www.baidu.com", + Backup: true, + Follow302: true, + }, + { + Peer: "http://2.2.2.2", + Host: "www.baidu.com", + Backup: false, + Follow302: true, + }, + }, "www.baidu.com") + + checkClientErr(t, "SetDomainOrigin", err) +} + +func TestClientSetOriginProtocol(t *testing.T) { + err := testCli.SetOriginProtocol(testAuthorityDomain, "*") + checkClientErr(t, "SetOriginProtocol", err) +} + +func TestClientGetOriginProtocol(t *testing.T) { + originProtocol, err := testCli.GetOriginProtocol(testAuthorityDomain) + t.Logf("originProtocol: %s", originProtocol) + checkClientErr(t, "GetDomainSeo", err) +} + +func TestSetDomainSeo(t *testing.T) { + err := testCli.SetDomainSeo(testAuthorityDomain, &api.SeoSwitch{ + DirectlyOrigin: "ON", + PushRecord: "OFF", + }) + + checkClientErr(t, "SetDomainSeo", err) +} + +func TestGetDomainSeo(t *testing.T) { + seoSwitch, err := testCli.GetDomainSeo(testAuthorityDomain) + + data, _ := json.Marshal(seoSwitch) + t.Logf("seoSwitch: %s", string(data)) + checkClientErr(t, "GetDomainSeo", err) +} + +func TestGetCacheTTL(t *testing.T) { + cacheTTls, err := testCli.GetCacheTTL(testAuthorityDomain) + + data, _ := json.Marshal(cacheTTls) + t.Logf("cacheTTls: %s", string(data)) + checkClientErr(t, "GetCacheTTL", err) +} + +func TestSetCacheTTL(t *testing.T) { + err := testCli.SetCacheTTL(testAuthorityDomain, []api.CacheTTL{ + { + Type: "suffix", + Value: ".jpg", + TTL: 420000, + Weight: 30, + }, + { + Type: "suffix", + Value: ".mp4", + TTL: 10000, + }, + }) + + checkClientErr(t, "SetCacheTTL", err) +} + +func TestSetRefererACL(t *testing.T) { + // set white referer list + err := testCli.SetRefererACL(testAuthorityDomain, nil, []string{ + "a.bbbbbb.c", + "*.baidu.com.*", + }, true) + + checkClientErr(t, "SetRefererACL", err) + + // set black referer list + err = testCli.SetRefererACL(testAuthorityDomain, []string{ + "a.b.c", + "*.xxxxx.com.*", + }, nil, true) + checkClientErr(t, "SetRefererACL", err) +} + +func TestGetRefererACL(t *testing.T) { + refererACL, err := testCli.GetRefererACL(testAuthorityDomain) + data, _ := json.Marshal(refererACL) + t.Logf("refererACL: %s", string(data)) + checkClientErr(t, "GetRefererACL", err) +} + +func TestSetIpACL(t *testing.T) { + err := testCli.SetIpACL(testAuthorityDomain, []string{ + "5.5.5.5", + "6.6.6.6", + }, nil) + + checkClientErr(t, "SetIpACL", err) + + err = testCli.SetIpACL(testAuthorityDomain, nil, []string{ + "1.2.3.4/24", + }) + + checkClientErr(t, "SetIpACL", err) +} + +func TestGetIpACL(t *testing.T) { + ipACL, err := testCli.GetIpACL(testAuthorityDomain) + data, _ := json.Marshal(ipACL) + t.Logf("ipACL: %s", string(data)) + checkClientErr(t, "GetIpACL", err) +} + +func TestSetUaACL(t *testing.T) { + err := testCli.SetUaACL(testAuthorityDomain, []string{ + "Test-Bad-UA", + }, nil) + + checkClientErr(t, "SetUaACL", err) + + err = testCli.SetUaACL(testAuthorityDomain, nil, []string{ + "curl/7.73.0", + }) + + checkClientErr(t, "SetUaACL", err) +} + +func TestGetUaACL(t *testing.T) { + uaACL, err := testCli.GetUaACL(testAuthorityDomain) + data, _ := json.Marshal(uaACL) + t.Logf("uaACL: %s", string(data)) + checkClientErr(t, "GetUaACL", err) +} + +func TestSetTrafficLimit(t *testing.T) { + trafficLimit := &api.TrafficLimit{ + Enabled: false, + LimitRate: 10000, + LimitStartHour: 10, + LimitEndHour: 12, + TrafficLimitUnit: "k", + } + err := testCli.SetTrafficLimit(testAuthorityDomain, trafficLimit) + checkClientErr(t, "SetTrafficLimit", err) +} + +func TestGetTrafficLimit(t *testing.T) { + trafficLimit, err := testCli.GetTrafficLimit(testAuthorityDomain) + + data, _ := json.Marshal(trafficLimit) + t.Logf("trafficLimit: %s", string(data)) + checkClientErr(t, "GetTrafficLimit", err) +} + +func TestSetDomainHttps(t *testing.T) { + err := testCli.SetDomainHttps(testAuthorityDomain, &api.HTTPSConfig{ + Enabled: true, + CertId: "ssl-xxxxxx", + Http2Enabled: true, + HttpRedirect: true, + HttpRedirectCode: 301, + }) + + checkClientErr(t, "SetDomainHttps", err) +} + +func TestPutCert(t *testing.T) { + privateKey := "证书私钥数据" + certData := "证书内容(一般需要包含证书链信息)" + certId, err := testCli.PutCert(testAuthorityDomain, &api.UserCertificate{ + CertName: "test", + ServerData: certData, + PrivateData: privateKey, + }, "ON") + + t.Logf("TestPutCert got certId %s", certId) + checkClientErr(t, "TestPutCert", err) +} + +func TestGetCert(t *testing.T) { + detail, err := testCli.GetCert(testAuthorityDomain) + data, _ := json.Marshal(detail) + t.Logf("TestGetCert: %s", string(data)) + checkClientErr(t, "TestGetCert", err) +} + +func TestDeleteCert(t *testing.T) { + err := testCli.DeleteCert(testAuthorityDomain) + checkClientErr(t, "TestGetCert", err) +} + +func TestSetOCSP(t *testing.T) { + err := testCli.SetOCSP(testAuthorityDomain, false) + checkClientErr(t, "SetOCSP", err) +} + +func TestGetOCSP(t *testing.T) { + ocspSwitch, err := testCli.GetOCSP(testAuthorityDomain) + + t.Logf("ocspSwitch: %v", ocspSwitch) + checkClientErr(t, "GetOCSP", err) +} + +func TestSetDomainRequestAuth(t *testing.T) { + var err error + err = testCli.SetDomainRequestAuth(testAuthorityDomain, &api.RequestAuth{ + Type: "c", + Key1: "secretekey1", + Key2: "secretekey2", + Timeout: 1800, + WhiteList: []string{ + "/crossdomain.xml", + }, + SignArg: "sign", + TimeArg: "t", + TimestampFormat: "hex", + }) + checkClientErr(t, "SetDomainRequestAuth", err) + + err = testCli.SetDomainRequestAuth(testAuthorityDomain, &api.RequestAuth{ + Type: "b", + Key1: "secretekey1", + Key2: "secretekey2", + Timeout: 3600, + WhiteList: []string{ + "/crossdomain.xml", + }, + TimestampFormat: "yyyyMMDDhhmm", + }) + checkClientErr(t, "SetDomainRequestAuth", err) + + // Type A does not support TimestampMetric 'yyyyMMDDhhmm', SetDomainRequestAuth would return error. + err = testCli.SetDomainRequestAuth(testAuthorityDomain, &api.RequestAuth{ + Type: "a", + Key1: "secretekey1", + Key2: "secretekey2", + Timeout: 3600, + WhiteList: []string{ + "/crossdomain.xml", + }, + TimestampFormat: "yyyyMMDDhhmm", + }) + if err == nil { + t.Fatalf(`Type A support TimestampMetric 'yyyyMMDDhhmm' is unexpected`) + } +} + +func TestSetFollowProtocol(t *testing.T) { + err := testCli.SetFollowProtocol(testAuthorityDomain, true) + checkClientErr(t, "SetFollowProtocol", err) +} + +func TestSetHttpHeader(t *testing.T) { + err := testCli.SetHttpHeader(testAuthorityDomain, []api.HttpHeader{ + { + Type: "origin", + Header: "x-auth-cn", + Value: "xxxxxxxxx", + Action: "remove", + }, + { + Type: "response", + Header: "content-type", + Value: "application/octet-stream", + Action: "add", + }, + }) + + checkClientErr(t, "SetHttpHeader", err) +} + +func TestGetHttpHerder(t *testing.T) { + headers, err := testCli.GetHttpHeader(testAuthorityDomain) + + data, _ := json.Marshal(headers) + t.Logf("headers: %s", string(data)) + checkClientErr(t, "GetHttpHeader", err) +} + +func TestSetErrorPage(t *testing.T) { + err := testCli.SetErrorPage(testAuthorityDomain, []api.ErrorPage{ + { + Code: 510, + RedirectCode: 302, + Url: "/customer_404.html", + }, + { + Code: 403, + Url: "/custom_403.html", + }, + }) + + checkClientErr(t, "SetErrorPage", err) +} + +func TestGetErrorPage(t *testing.T) { + errorPages, err := testCli.GetErrorPage(testAuthorityDomain) + + data, _ := json.Marshal(errorPages) + t.Logf("errorPages: %s", string(data)) + checkClientErr(t, "GetErrorPage", err) +} + +func TestSetCacheCached(t *testing.T) { + err := testCli.SetCacheShared(testAuthorityDomain, &api.CacheShared{ + Enabled: false, + }) + checkClientErr(t, "SetCacheShared", err) +} + +func TestGetCacheCached(t *testing.T) { + cacheSharedConfig, err := testCli.GetCacheShared(testAuthorityDomain) + data, _ := json.Marshal(cacheSharedConfig) + t.Logf("cacheSharedConfig: %s", string(data)) + checkClientErr(t, "GetCacheShared", err) +} + +func TestSetMediaDrag(t *testing.T) { + err := testCli.SetMediaDrag(testAuthorityDomain, &api.MediaDragConf{ + Mp4: &api.MediaCfg{ + DragMode: "second", + FileSuffix: []string{ + "mp4", + "m4a", + "m4z", + }, + StartArgName: "startIndex", + }, + Flv: &api.MediaCfg{ + DragMode: "byteAV", + FileSuffix: []string{}, + }, + }) + + checkClientErr(t, "SetMediaDrag", err) +} + +func TestGetMediaDrag(t *testing.T) { + mediaDragConf, err := testCli.GetMediaDrag(testAuthorityDomain) + + data, _ := json.Marshal(mediaDragConf) + t.Logf("mediaDragConf: %s", string(data)) + checkClientErr(t, "GetMediaDrag", err) +} + +func TestSetFileTrim(t *testing.T) { + err := testCli.SetFileTrim(testAuthorityDomain, true) + checkClientErr(t, "SetFileTrim", err) +} + +func TestGetFileTrim(t *testing.T) { + fileTrim, err := testCli.GetFileTrim(testAuthorityDomain) + + t.Logf("fileTrim: %v", fileTrim) + checkClientErr(t, "GetFileTrim", err) +} + +func TestSetIPv6(t *testing.T) { + err := testCli.SetIPv6(testAuthorityDomain, false) + checkClientErr(t, "SetIPv6", err) +} + +func TestGetIPv6(t *testing.T) { + ipv6Switch, err := testCli.GetIPv6(testAuthorityDomain) + + t.Logf("ipv6Switch: %v", ipv6Switch) + checkClientErr(t, "GetIPv6", err) +} + +func TestSetQUIC(t *testing.T) { + err := testCli.SetQUIC(testAuthorityDomain, false) + checkClientErr(t, "SetQUIC", err) +} + +func TestGetQUIC(t *testing.T) { + quicSwitch, err := testCli.GetQUIC(testAuthorityDomain) + + t.Logf("quicSwitch: %v", quicSwitch) + checkClientErr(t, "GetQUIC", err) +} + +func TestSetOfflineMode(t *testing.T) { + err := testCli.SetOfflineMode(testAuthorityDomain, true) + checkClientErr(t, "SetOfflineMode", err) +} + +func TestGetOfflineMode(t *testing.T) { + offlineMode, err := testCli.GetOfflineMode(testAuthorityDomain) + + t.Logf("offlineMode: %v", offlineMode) + checkClientErr(t, "GetOfflineMode", err) +} + +func TestSetMobileAccess(t *testing.T) { + err := testCli.SetMobileAccess(testAuthorityDomain, true) + checkClientErr(t, "SetMobileAccess", err) +} + +func TestGetMobileAccess(t *testing.T) { + distinguishClient, err := testCli.GetMobileAccess(testAuthorityDomain) + + t.Logf("distinguishClient: %v", distinguishClient) + checkClientErr(t, "GetMobileAccess", err) +} + +func TestSetClientIp(t *testing.T) { + err := testCli.SetClientIp(testAuthorityDomain, &api.ClientIp{ + Enabled: true, + Name: "X-Real-IP", + }) + + checkClientErr(t, "SetClientIp", err) +} + +func TestGetClientIp(t *testing.T) { + clientIp, err := testCli.GetClientIp(testAuthorityDomain) + + data, _ := json.Marshal(clientIp) + t.Logf("clientIp: %s", data) + checkClientErr(t, "GetClientIp", err) +} + +func TestSetRetryOrigin(t *testing.T) { + err := testCli.SetRetryOrigin(testAuthorityDomain, &api.RetryOrigin{ + Codes: []int{429, 500, 502, 503}, + }) + + checkClientErr(t, "SetRetryOrigin", err) +} + +func TestGetRetryOrigin(t *testing.T) { + retryOrigin, err := testCli.GetRetryOrigin(testAuthorityDomain) + + data, _ := json.Marshal(retryOrigin) + t.Logf("retryOrigin: %s", data) + checkClientErr(t, "GetRetryOrigin", err) +} + +func TestSetAccessLimit(t *testing.T) { + err := testCli.SetAccessLimit(testAuthorityDomain, &api.AccessLimit{ + Enabled: true, + Limit: 200, + }) + + checkClientErr(t, "SetAccessLimit", err) +} + +func TestGetAccessLimit(t *testing.T) { + accessLimit, err := testCli.GetAccessLimit(testAuthorityDomain) + + data, _ := json.Marshal(accessLimit) + t.Logf("accessLimit: %s", data) + checkClientErr(t, "GetAccessLimit", err) +} + +func TestSetCacheUrlArgs(t *testing.T) { + err := testCli.SetCacheUrlArgs(testAuthorityDomain, &api.CacheUrlArgs{ + CacheFullUrl: false, + CacheUrlArgs: []string{"1", "2"}, + }) + + checkClientErr(t, "SetCacheUrlArgs", err) +} + +func TestGetCacheUrlArgs(t *testing.T) { + cacheUrlArgs, err := testCli.GetCacheUrlArgs(testAuthorityDomain) + + data, _ := json.Marshal(cacheUrlArgs) + t.Logf("cacheUrlArgs: %s", string(data)) + checkClientErr(t, "GetCacheUrlArgs", err) +} + +func TestSetCors(t *testing.T) { + err := testCli.SetCors(testAuthorityDomain, true, []string{ + "http://www.baidu.com", + "http://*.bce.com", + }) + + checkClientErr(t, "SetCors", err) +} + +func TestGetCors(t *testing.T) { + cors, err := testCli.GetCors(testAuthorityDomain) + + data, _ := json.Marshal(cors) + t.Logf("cors: %s", string(data)) + checkClientErr(t, "GetCors", err) +} + +func TestSetRangeSwitch(t *testing.T) { + err := testCli.SetRangeSwitch(testAuthorityDomain, false) + + checkClientErr(t, "SetRangeSwitch", err) +} + +func TestGetRangeSwitch(t *testing.T) { + rangeSwitch, err := testCli.GetRangeSwitch(testAuthorityDomain) + + t.Logf("rangeSwitch: %+v", rangeSwitch) + checkClientErr(t, "GetRangeSwitch", err) +} + +func TestSetContentEncoding(t *testing.T) { + err := testCli.SetContentEncoding(testAuthorityDomain, true, "br") + checkClientErr(t, "SetContentEncoding", err) +} + +func TestGetContentEncoding(t *testing.T) { + contentEncoding, err := testCli.GetContentEncoding(testAuthorityDomain) + t.Logf("contentEncoding: %+v", contentEncoding) + checkClientErr(t, "GetContentEncoding", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about purge/prefetch. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestPurge(t *testing.T) { + purgedId, err := testCli.Purge([]api.PurgeTask{ + { + Url: "http://my.domain.com/path/to/purge/2.data", + }, + { + Url: "http://my.domain.com/path/to/purege/html/", + Type: "directory", + }, + }) + + t.Logf("purgedId: %s", string(purgedId)) + checkClientErr(t, "Purge", err) +} + +func TestGetPurgedStatus(t *testing.T) { + purgedStatus, err := testCli.GetPurgedStatus(nil) + + data, _ := json.Marshal(purgedStatus) + t.Logf("purgedStatus: %s", string(data)) + checkClientErr(t, "GetPurgedStatus", err) +} + +func TestPrefetch(t *testing.T) { + prefetchId, err := testCli.Prefetch([]api.PrefetchTask{ + { + Url: "http://my.domain.com/path/to/purge/1.data", + }, + }) + + t.Logf("prefetchId: %s", string(prefetchId)) + checkClientErr(t, "Prefetch", err) +} + +func TestGetPrefetchStatus(t *testing.T) { + prefetchStatus, err := testCli.GetPrefetchStatus(nil) + + data, _ := json.Marshal(prefetchStatus) + t.Logf("prefetchStatus: %s", string(data)) + checkClientErr(t, "GetPrefetchStatus", err) +} + +func TestGetQuota(t *testing.T) { + quotaDetail, err := testCli.GetQuota() + + data, _ := json.Marshal(quotaDetail) + t.Logf("quotaDetail: %s", string(data)) + checkClientErr(t, "GetQuota", err) +} + +func TestGetCacheOpRecords(t *testing.T) { + recordDetails, err := testCli.GetCacheOpRecords(&api.CRecordQueryData{ + StartTime: "2019-08-12T12:00:00Z", + EndTime: "2019-08-14T12:00:00Z", + }) + data, _ := json.Marshal(recordDetails) + t.Logf("GetCacheOpRecords: %s", string(data)) + checkClientErr(t, "GetCacheOpRecords", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about DSA. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestEnableDsa(t *testing.T) { + err := testCli.EnableDsa() + checkClientErr(t, "EnableDsa", err) +} + +func TestDisableDsa(t *testing.T) { + err := testCli.DisableDsa() + checkClientErr(t, "DisableDsa", err) +} + +func TestListDsaDomains(t *testing.T) { + dsaDomains, err := testCli.ListDsaDomains() + data, _ := json.Marshal(dsaDomains) + fmt.Println(string(data)) + checkClientErr(t, "ListDsaDomains", err) +} + +func TestSetDsaConfig(t *testing.T) { + err := testCli.SetDsaConfig(testAuthorityDomain, &api.DSAConfig{ + Enabled: true, + Rules: []api.DSARule{ + { + Type: "suffix", + Value: ".mp4;.jpg;.php", + }, + { + Type: "path", + Value: "/path", + }, + { + Type: "exactPath", + Value: "/path/to/file.mp4", + }, + }, + Comment: "test", + }) + + checkClientErr(t, "SetDsaConfig", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about log. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestGetDomainLog(t *testing.T) { + endTs := time.Now().Unix() + startTs := endTs - 24*60*60 + endTime := util.FormatISO8601Date(endTs) + startTime := util.FormatISO8601Date(startTs) + domainLogs, err := testCli.GetDomainLog(testAuthorityDomain, api.TimeInterval{ + StartTime: startTime, + EndTime: endTime, + }) + + data, _ := json.Marshal(domainLogs) + t.Logf("domainLogs: %s", string(data)) + checkClientErr(t, "GetDomainLog", err) +} + +func TestGetMultiDomainLog(t *testing.T) { + endTs := time.Now().Unix() + startTs := endTs - 24*60*60 + endTime := util.FormatISO8601Date(endTs) + startTime := util.FormatISO8601Date(startTs) + + domainLogs, err := testCli.GetMultiDomainLog(&api.LogQueryData{ + TimeInterval: api.TimeInterval{ + StartTime: startTime, + EndTime: endTime, + }, + Type: 1, + Domains: []string{"1.baidu.com", "2.baidu.com"}, + }) + + data, _ := json.Marshal(domainLogs) + t.Logf("domainLogs: %s", string(data)) + checkClientErr(t, "GetMultiDomainLog", err) +} + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Test function about query statistics. +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func TestGetAvgSpeed(t *testing.T) { + queryCondition := &api.QueryCondition{} + avgSpeedDetails, err := testCli.GetAvgSpeed(queryCondition) + + data, _ := json.Marshal(avgSpeedDetails) + t.Logf("avgSpeedDetails: %s", string(data)) + checkClientErr(t, "GetAvgSpeed", err) +} + +func TestGetAvgSpeedByRegion(t *testing.T) { + queryCondition := &api.QueryCondition{} + avgSpeedDetails, err := testCli.GetAvgSpeedByRegion(queryCondition, "beijing", "") + + data, _ := json.Marshal(avgSpeedDetails) + t.Logf("avgSpeedDetails: %s", string(data)) + checkClientErr(t, "GetAvgSpeedByRegion", err) +} + +func TestGetPv(t *testing.T) { + queryCondition := &api.QueryCondition{} + pvDetails, err := testCli.GetPv(queryCondition, "all") + + data, _ := json.Marshal(pvDetails) + t.Logf("pvDetails: %s", string(data)) + checkClientErr(t, "GetPv", err) +} + +func TestGetSrcPv(t *testing.T) { + queryCondition := &api.QueryCondition{} + pvDetails, err := testCli.GetSrcPv(queryCondition) + + data, _ := json.Marshal(pvDetails) + t.Logf("pvDetails: %s", string(data)) + checkClientErr(t, "GetSrcPv", err) +} + +func TestGetPvInRegion(t *testing.T) { + queryCondition := &api.QueryCondition{} + pvRegionDetails, err := testCli.GetPvByRegion(queryCondition, "beijing", "") + + data, _ := json.Marshal(pvRegionDetails) + t.Logf("pvRegionDetails: %s", string(data)) + checkClientErr(t, "GetPvByRegion", err) +} + +func TestGetUv(t *testing.T) { + queryCondition := &api.QueryCondition{} + uvDetails, err := testCli.GetUv(queryCondition) + + data, _ := json.Marshal(uvDetails) + t.Logf("uvDetails: %s", string(data)) + checkClientErr(t, "GetUv", err) +} + +func TestGetFlow(t *testing.T) { + queryCondition := &api.QueryCondition{ + StartTime: "2019-06-16T16:00:00Z", + EndTime: "2019-06-19T16:00:00Z", + Period: 86400, + KeyType: 0, + Key: []string{testAuthorityDomain}, + GroupBy: "key", + } + flowDetails, err := testCli.GetFlow(queryCondition, "all") + + data, _ := json.Marshal(flowDetails) + t.Logf("flowDetails: %s", string(data)) + checkClientErr(t, "GetFlow", err) +} + +func TestGetFlowByProtocol(t *testing.T) { + queryCondition := &api.QueryCondition{} + flowDetails, err := testCli.GetFlowByProtocol(queryCondition, "all") + + data, _ := json.Marshal(flowDetails) + t.Logf("flowDetails: %s", string(data)) + checkClientErr(t, "GetFlowByProtocol", err) +} + +func TestGetFlowByRegion(t *testing.T) { + queryCondition := &api.QueryCondition{} + flowRegionDetails, err := testCli.GetFlowByRegion(queryCondition, "beijing", "") + + data, _ := json.Marshal(flowRegionDetails) + t.Logf("flowRegionDetails: %s", string(data)) + checkClientErr(t, "GetFlowByRegion", err) +} + +func TestGetSrcFlow(t *testing.T) { + queryCondition := &api.QueryCondition{} + flowDetails, err := testCli.GetSrcFlow(queryCondition) + + data, _ := json.Marshal(flowDetails) + t.Logf("flowDetails: %s", string(data)) + checkClientErr(t, "GetFlowByRegion", err) +} + +func TestGetRealHit(t *testing.T) { + queryCondition := &api.QueryCondition{} + hitDetails, err := testCli.GetRealHit(queryCondition) + + data, _ := json.Marshal(hitDetails) + t.Logf("hitDetails: %s", string(data)) + checkClientErr(t, "GetRealHit", err) +} + +func TestGetPvHit(t *testing.T) { + queryCondition := &api.QueryCondition{} + hitDetails, err := testCli.GetPvHit(queryCondition) + + data, _ := json.Marshal(hitDetails) + t.Logf("hitDetails: %s", string(data)) + checkClientErr(t, "GetPvHit", err) +} + +func TestGetHttpCode(t *testing.T) { + queryCondition := &api.QueryCondition{} + httpCodeDetails, err := testCli.GetHttpCode(queryCondition) + + data, _ := json.Marshal(httpCodeDetails) + t.Logf("httpCodeDetails: %s", string(data)) + checkClientErr(t, "GetHttpCode", err) +} + +func TestGetSrcHttpCode(t *testing.T) { + queryCondition := &api.QueryCondition{} + httpCodeDetails, err := testCli.GetSrcHttpCode(queryCondition) + + data, _ := json.Marshal(httpCodeDetails) + t.Logf("httpCodeDetails: %s", string(data)) + checkClientErr(t, "GetSrcHttpCode", err) +} + +func TestGetHttpCodeByRegion(t *testing.T) { + queryCondition := &api.QueryCondition{} + httpCodeDetails, err := testCli.GetHttpCodeByRegion(queryCondition, "beijing", "") + + data, _ := json.Marshal(httpCodeDetails) + t.Logf("httpCodeDetails: %s", string(data)) + checkClientErr(t, "GetHttpCodeByRegion", err) +} + +func TestGetTopNUrls(t *testing.T) { + queryCondition := &api.QueryCondition{} + topNUrls, err := testCli.GetTopNUrls(queryCondition, "200") + + data, _ := json.Marshal(topNUrls) + t.Logf("topNUrls: %s", string(data)) + checkClientErr(t, "GetTopNUrls", err) +} + +func TestGetTopNReferers(t *testing.T) { + queryCondition := &api.QueryCondition{} + topNReferers, err := testCli.GetTopNReferers(queryCondition, "") + + data, _ := json.Marshal(topNReferers) + t.Logf("topNReferers: %s", string(data)) + checkClientErr(t, "GetTopNReferers", err) +} + +func TestGetTopNDomains(t *testing.T) { + queryCondition := &api.QueryCondition{} + topNDomains, err := testCli.GetTopNDomains(queryCondition, "") + + data, _ := json.Marshal(topNDomains) + t.Logf("topNDomains: %s", string(data)) + checkClientErr(t, "GetTopNDomains", err) +} + +func TestGetError(t *testing.T) { + queryCondition := &api.QueryCondition{} + errorDetails, err := testCli.GetError(queryCondition) + + data, _ := json.Marshal(errorDetails) + t.Logf("errorDetails: %s", string(data)) + checkClientErr(t, "GetErrorCount", err) +} + +func TestGetPeak95Bandwidth(t *testing.T) { + peak95Time, peak95Band, err := testCli.GetPeak95Bandwidth( + "2020-05-01T00:00:00Z", "2020-05-10T00:00:00Z", []string{"www.test.com"}, nil) + t.Logf("peak95Time %s, peak95Band %d", peak95Time, peak95Band) + checkClientErr(t, "TestGetPeak95Bandwidth", err) +} diff --git a/bce-sdk-go/services/cert/cert.go b/bce-sdk-go/services/cert/cert.go new file mode 100644 index 0000000..2b78978 --- /dev/null +++ b/bce-sdk-go/services/cert/cert.go @@ -0,0 +1,164 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// cert.go - the certificate APIs definition supported by the Cert service +package cert + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateCert - create a cert with the specific parameters +// +// PARAMS: +// - args: the arguments to create a cert +// +// RETURNS: +// - *CreateCertResult: the result of create Cert, contains new Cert's ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateCert(args *CreateCertArgs) (*CreateCertResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.CertName == "" { + return nil, fmt.Errorf("unset CertName") + } + + if args.CertServerData == "" { + return nil, fmt.Errorf("unset CertServerData") + } + + if args.CertPrivateData == "" { + return nil, fmt.Errorf("unset CertPrivateData") + } + + result := &CreateCertResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getCertUri()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateCertName - update a cert's name +// +// PARAMS: +// - id: the specific cert's ID +// - args: the arguments to update a cert's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateCertName(id string, args *UpdateCertNameArgs) error { + if args == nil || args.CertName == "" { + return fmt.Errorf("unset CertName") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getCertUriWithId(id)). + WithQueryParam("certName", ""). + WithBody(args). + Do() +} + +// ListCerts - list all certs +// +// RETURNS: +// - *ListCertResult: the result of list all certs, contains all certs' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListCerts() (*ListCertResult, error) { + result := &ListCertResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getCertUri()). + WithResult(result). + Do() + + return result, err +} + +// GetCertMeta - get a specific cert's meta +// +// PARAMS: +// - id: the specific cert's ID +// +// RETURNS: +// - *CertificateMeta: the specific cert's meta with +// - error: nil if success otherwise the specific error +func (c *Client) GetCertMeta(id string) (*CertificateMeta, error) { + result := &CertificateMeta{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getCertUriWithId(id)). + WithResult(result). + Do() + + return result, err +} + +// DeleteCert - delete a specific cert +// +// PARAMS: +// - id: the specific cert's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteCert(id string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getCertUriWithId(id)). + Do() +} + +// UpdateCertData - update a specific cert's data, include update key +// +// PARAMS: +// - id: the specific cert's ID +// - args: the arguments to update a specific cert +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateCertData(id string, args *UpdateCertDataArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.CertName == "" { + return fmt.Errorf("unset CertName") + } + + if args.CertServerData == "" { + return fmt.Errorf("unset CertServerData") + } + + if args.CertPrivateData == "" { + return fmt.Errorf("unset CertPrivateData") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getCertUriWithId(id)). + WithQueryParam("certData", ""). + WithBody(args). + Do() + + return err +} diff --git a/bce-sdk-go/services/cert/client.go b/bce-sdk-go/services/cert/client.go new file mode 100644 index 0000000..cf81967 --- /dev/null +++ b/bce-sdk-go/services/cert/client.go @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Cert service + +// Package cert defines the Cert services of BCE. The supported APIs are all defined in sub-package +package cert + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + DEFAULT_ENDPOINT = "certificate.baidubce.com" + REQUEST_CERT_URL = "/certificate" +) + +// Client of Cert service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getCertUri() string { + return URI_PREFIX + REQUEST_CERT_URL +} + +func getCertUriWithId(id string) string { + return URI_PREFIX + REQUEST_CERT_URL + "/" + id +} diff --git a/bce-sdk-go/services/cert/client_test.go b/bce-sdk-go/services/cert/client_test.go new file mode 100644 index 0000000..4594855 --- /dev/null +++ b/bce-sdk-go/services/cert/client_test.go @@ -0,0 +1,124 @@ +package cert + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CERT_CLIENT *Client + CERT_ID string + + // set these values before start test + testCertServerData = `` + testCertPrivateData = `` + + testUpdateCertServerData = `` + testUpdateCertPrivateData = `` +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CERT_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateCert(t *testing.T) { + args := &CreateCertArgs{ + CertName: "sdkCreateTest", + CertServerData: testCertServerData, + CertPrivateData: testCertPrivateData, + } + createResult, err := CERT_CLIENT.CreateCert(args) + ExpectEqual(t.Errorf, nil, err) + + CERT_ID = createResult.CertId +} + +func TestClient_ListCerts(t *testing.T) { + _, err := CERT_CLIENT.ListCerts() + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetCertMeta(t *testing.T) { + result, err := CERT_CLIENT.GetCertMeta(CERT_ID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "sdkCreateTest", result.CertName) + ExpectEqual(t.Errorf, 1, result.CertType) +} + +func TestClient_UpdateCertName(t *testing.T) { + args := &UpdateCertNameArgs{ + CertName: "sdkUpdateNameTest", + } + err := CERT_CLIENT.UpdateCertName(CERT_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateCertData(t *testing.T) { + args := &UpdateCertDataArgs{ + CertName: "sdkTest", + CertServerData: testUpdateCertServerData, + CertPrivateData: testUpdateCertPrivateData, + } + err := CERT_CLIENT.UpdateCertData(CERT_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCert(t *testing.T) { + err := CERT_CLIENT.DeleteCert(CERT_ID) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/cert/model.go b/bce-sdk-go/services/cert/model.go new file mode 100644 index 0000000..edeb463 --- /dev/null +++ b/bce-sdk-go/services/cert/model.go @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package cert + +type CreateCertArgs struct { + CertName string `json:"certName"` + CertServerData string `json:"certServerData"` + CertPrivateData string `json:"certPrivateData"` + CertLinkData string `json:"certLinkData,omitempty"` + CertType int `json:"certType,omitempty"` +} + +type CreateCertResult struct { + CertName string `json:"certName"` + CertId string `json:"certId"` +} + +type UpdateCertNameArgs struct { + CertName string `json:"certName"` +} + +type CertificateMeta struct { + CertId string `json:"certId"` + CertName string `json:"certName"` + CertCommonName string `json:"certCommonName"` + CertStartTime string `json:"certStartTime"` + CertStopTime string `json:"certStopTime"` + CertCreateTime string `json:"certCreateTime"` + CertUpdateTime string `json:"certUpdateTime"` + CertType int `json:"certType"` +} + +type ListCertResult struct { + Certs []CertificateMeta `json:"certs"` +} + +type UpdateCertDataArgs struct { + CertName string `json:"certName"` + CertServerData string `json:"certServerData"` + CertPrivateData string `json:"certPrivateData"` + CertLinkData string `json:"certLinkData,omitempty"` + CertType int `json:"certType,omitempty"` +} diff --git a/bce-sdk-go/services/cfc/api/cfc.go b/bce-sdk-go/services/cfc/api/cfc.go new file mode 100644 index 0000000..b8c3de0 --- /dev/null +++ b/bce-sdk-go/services/cfc/api/cfc.go @@ -0,0 +1,762 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 api + +import ( + "encoding/base64" + "encoding/json" + "io/ioutil" + "reflect" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func ListFunctions(cli bce.Client, args *ListFunctionsArgs) (*ListFunctionsResult, error) { + op := &Operation{ + HTTPUri: getFunctionsUri(), + HTTPMethod: GET, + } + + if args.MaxItems <= 0 { + args.MaxItems = 10000 + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "FunctionVersion": args.FunctionVersion, + "Marker": args.Marker, + "MaxItems": args.MaxItems, + }, + } + result := &cfcResult{ + Result: &ListFunctionsResult{}, + } + + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListFunctionsResult); ok { + return value, nil + } + return nil, nil +} + +func GetFunction(cli bce.Client, args *GetFunctionArgs) (*GetFunctionResult, error) { + op := &Operation{ + HTTPUri: getFunctionUri(args.FunctionName), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "Qualifier": args.Qualifier, + }, + } + result := &cfcResult{ + Result: &GetFunctionResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*GetFunctionResult); ok { + return value, nil + } + return nil, nil +} + +func CreateFunction(cli bce.Client, args *CreateFunctionArgs) (*CreateFunctionResult, error) { + op := &Operation{ + HTTPUri: getFunctionsUri(), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &CreateFunctionResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*CreateFunctionResult); ok { + return value, nil + } + return nil, nil +} + +func CreateFunctionByBlueprint(cli bce.Client, args *CreateFunctionByBlueprintArgs) (*CreateFunctionResult, error) { + op := &Operation{ + HTTPUri: getBlueprintUri(args.BlueprintID), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &CreateFunctionResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*CreateFunctionResult); ok { + return value, nil + } + return nil, nil +} + +func DeleteFunction(cli bce.Client, args *DeleteFunctionArgs) error { + op := &Operation{ + HTTPUri: getFunctionUri(args.FunctionName), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "Qualifier": args.Qualifier, + }, + } + result := &cfcResult{ + Result: nil, + } + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} + +func UpdateFunctionCode(cli bce.Client, args *UpdateFunctionCodeArgs) (*UpdateFunctionCodeResult, error) { + op := &Operation{ + HTTPUri: getFunctionCodeUri(args.FunctionName), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &UpdateFunctionCodeResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*UpdateFunctionCodeResult); ok { + return value, nil + } + return nil, nil +} + +func GetFunctionConfiguration(cli bce.Client, args *GetFunctionConfigurationArgs) (*GetFunctionConfigurationResult, error) { + op := &Operation{ + HTTPUri: getFunctionConfigurationUri(args.FunctionName), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "Qualifier": args.Qualifier, + }, + } + result := &cfcResult{ + Result: &GetFunctionConfigurationResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*GetFunctionConfigurationResult); ok { + return value, nil + } + return nil, nil +} + +func UpdateFunctionConfiguration(cli bce.Client, args *UpdateFunctionConfigurationArgs) (*UpdateFunctionConfigurationResult, error) { + op := &Operation{ + HTTPUri: getFunctionConfigurationUri(args.FunctionName), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &UpdateFunctionConfigurationResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*UpdateFunctionConfigurationResult); ok { + return value, nil + } + return nil, nil +} + +func SetReservedConcurrentExecutions(cli bce.Client, args *ReservedConcurrentExecutionsArgs) error { + op := &Operation{ + HTTPUri: getFunctionConCurrentUri(args.FunctionName), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: nil, + } + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} + +func DeleteReservedConcurrentExecutions(cli bce.Client, args *DeleteReservedConcurrentExecutionsArgs) error { + op := &Operation{ + HTTPUri: getFunctionConCurrentUri(args.FunctionName), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Args: args, + } + result := &cfcResult{} + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} + +func Invocations(cli bce.Client, args *InvocationsArgs) (*InvocationsResult, error) { + if err := args.Validate(); err != nil { + return nil, err + } + if len(args.InvocationType) == 0 { + args.InvocationType = InvocationTypeRequestResponse + } + if len(args.LogType) == 0 { + args.LogType = LogTypeNone + } + + req := &bce.BceRequest{} + req.SetRequestId(args.RequestId) + req.SetUri(getInvocationsUri(args.FunctionName)) + req.SetMethod(http.POST) + req.SetParam("invocationType", string(args.InvocationType)) + req.SetParam("logType", string(args.LogType)) + if args.Qualifier != "" { + req.SetParam("qualifier", args.Qualifier) + } + + if args.Payload != nil { + var payloadBytes []byte + var err error + switch args.Payload.(type) { + case string: + payloadBytes = []byte(args.Payload.(string)) + case []byte: + payloadBytes = args.Payload.([]byte) + default: + payloadBytes, err = json.Marshal(args.Payload) + if err != nil { + return nil, err + } + } + var js interface{} + if json.Unmarshal([]byte(payloadBytes), &js) != nil { + return nil, ParseJsonError + } + requestBody, err := bce.NewBodyFromBytes(payloadBytes) + if err != nil { + return nil, err + } + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(requestBody) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + defer resp.Body().Close() + if resp.IsFail() { + return nil, resp.ServiceError() + } + body, err := ioutil.ReadAll(resp.Body()) + if err != nil { + return nil, err + } + result := &InvocationsResult{ + Payload: string(body), + } + errorStr := resp.Header("x-bce-function-error") + if len(errorStr) > 0 { + result.FunctionError = errorStr + } + logResult := resp.Header("x-bce-log-result") + if len(logResult) > 0 { + decodeBytes, err := base64.StdEncoding.DecodeString(string(logResult)) + if err != nil { + return nil, err + } + result.LogResult = string(decodeBytes) + } + return result, nil +} + +func ListVersionsByFunction(cli bce.Client, args *ListVersionsByFunctionArgs) (*ListVersionsByFunctionResult, error) { + op := &Operation{ + HTTPUri: getFunctionVersionsUri(args.FunctionName), + HTTPMethod: GET, + } + if args.MaxItems <= 0 { + args.MaxItems = 10000 + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "Marker": args.Marker, + "MaxItems": args.MaxItems, + }, + } + result := &cfcResult{ + Result: &ListVersionsByFunctionResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListVersionsByFunctionResult); ok { + return value, nil + } + return nil, nil +} + +func PublishVersion(cli bce.Client, args *PublishVersionArgs) (*PublishVersionResult, error) { + op := &Operation{ + HTTPUri: getFunctionVersionsUri(args.FunctionName), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: map[string]interface{}{ + "Description": args.Description, + "CodeSha256": args.CodeSha256, + }, + } + result := &cfcResult{ + Result: &PublishVersionResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*PublishVersionResult); ok { + return value, nil + } + return nil, nil +} + +func ListAliases(cli bce.Client, args *ListAliasesArgs) (*ListAliasesResult, error) { + op := &Operation{ + HTTPUri: getFunctionAliasesUri(args.FunctionName), + HTTPMethod: GET, + } + if args.MaxItems <= 0 { + args.MaxItems = 10000 + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "FunctionVersion": args.FunctionVersion, + "Marker": args.Marker, + "MaxItems": args.MaxItems, + }, + } + result := &cfcResult{ + Result: &ListAliasesResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListAliasesResult); ok { + return value, nil + } + return nil, nil +} + +func CreateAlias(cli bce.Client, args *CreateAliasArgs) (*CreateAliasResult, error) { + op := &Operation{ + HTTPUri: getFunctionAliasesUri(args.FunctionName), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: map[string]interface{}{ + "FunctionVersion": args.FunctionVersion, + "Name": args.Name, + "Description": args.Description, + }, + } + result := &cfcResult{ + Result: &CreateAliasResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*CreateAliasResult); ok { + return value, nil + } + return nil, nil +} + +func GetAlias(cli bce.Client, args *GetAliasArgs) (*GetAliasResult, error) { + op := &Operation{ + HTTPUri: getFunctionAliasUri(args.FunctionName, args.AliasName), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + } + result := &cfcResult{ + Result: &GetAliasResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*GetAliasResult); ok { + return value, nil + } + return nil, nil +} + +func UpdateAlias(cli bce.Client, args *UpdateAliasArgs) (*UpdateAliasResult, error) { + op := &Operation{ + HTTPUri: getFunctionAliasUri(args.FunctionName, args.AliasName), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: map[string]interface{}{ + "FunctionVersion": args.FunctionVersion, + "Description": args.Description, + }, + } + result := &cfcResult{ + Result: &UpdateAliasResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*UpdateAliasResult); ok { + return value, nil + } + return nil, nil +} + +func DeleteAlias(cli bce.Client, args *DeleteAliasArgs) error { + op := &Operation{ + HTTPUri: getFunctionAliasUri(args.FunctionName, args.AliasName), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Args: args, + } + result := &cfcResult{} + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} + +func ListTriggers(cli bce.Client, args *ListTriggersArgs) (*ListTriggersResult, error) { + op := &Operation{ + HTTPUri: getTriggerUri(), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "FunctionBrn": args.FunctionBrn, + "ScopeType": args.ScopeType, + }, + } + result := &cfcResult{ + Result: &ListTriggersResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListTriggersResult); ok { + return value, nil + } + return nil, nil +} + +func CreateTrigger(cli bce.Client, args *CreateTriggerArgs) (*CreateTriggerResult, error) { + op := &Operation{ + HTTPUri: getTriggerUri(), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &CreateTriggerResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*CreateTriggerResult); ok { + return value, nil + } + return nil, nil + +} + +func UpdateTrigger(cli bce.Client, args *UpdateTriggerArgs) (*UpdateTriggerResult, error) { + op := &Operation{ + HTTPUri: getTriggerUri(), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: map[string]interface{}{ + "RelationId": args.RelationId, + "Target": args.Target, + "Source": args.Source, + "Data": args.Data, + }, + } + result := &cfcResult{ + Result: &UpdateTriggerResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*UpdateTriggerResult); ok { + return value, nil + } + return nil, nil +} + +func DeleteTrigger(cli bce.Client, args *DeleteTriggerArgs) error { + op := &Operation{ + HTTPUri: getTriggerUri(), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "RelationId": args.RelationId, + "Target": args.Target, + "Source": string(args.Source), + }, + } + result := &cfcResult{} + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} + +func caller(cli bce.Client, op *Operation, request *cfcRequest, response *cfcResult) error { + if request.Args != nil { + if err := request.Args.(Validator).Validate(); err != nil { + return err + } + } + req := new(bce.BceRequest) + if op.HTTPUri == "" { + return URIIllegal + } + req.SetUri(op.HTTPUri) + if op.HTTPMethod == "" { + return MethodIllegal + } + req.SetMethod(op.HTTPMethod) + if request.Params != nil { + for key, value := range request.Params { + rv := reflect.ValueOf(value) + switch rv.Kind() { + case reflect.String: + req.SetParam(key, value.(string)) + case reflect.Int: + req.SetParam(key, strconv.Itoa(value.(int))) + case reflect.Struct: + if valueBytes, err := json.Marshal(value); err != nil { + req.SetParam(key, string(valueBytes)) + } + } + } + } + if request.Body != nil { + argsBytes, err := json.Marshal(request.Body) + if err != nil { + return err + } + requestBody, err := bce.NewBodyFromBytes(argsBytes) + if err != nil { + return err + } + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(requestBody) + } + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + defer resp.Body().Close() + if resp.IsFail() { + return resp.ServiceError() + } + + if response.Result != nil { + if err := resp.ParseJsonBody(response.Result); err != nil { + return err + } + } + return nil +} + +// ListEventSource +func ListEventSource(cli bce.Client, args *ListEventSourceArgs) (*ListEventSourceResult, error) { + op := &Operation{ + HTTPUri: getEventSourceUri(), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "FunctionName": args.FunctionName, + "Marker": args.Marker, + "MaxItems": args.MaxItems, + }, + } + result := &cfcResult{ + Result: &ListEventSourceResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListEventSourceResult); ok { + return value, nil + } + return nil, nil +} + +// GetEventSource +func GetEventSource(cli bce.Client, args *GetEventSourceArgs) (*GetEventSourceResult, error) { + op := &Operation{ + HTTPUri: getOneEventSourceUri(args.UUID), + HTTPMethod: GET, + } + request := &cfcRequest{ + Args: args, + } + result := &cfcResult{ + Result: &GetEventSourceResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*GetEventSourceResult); ok { + return value, nil + } + return nil, nil +} + +// CreateEventSource +func CreateEventSource(cli bce.Client, args *CreateEventSourceArgs) (*CreateEventSourceResult, error) { + op := &Operation{ + HTTPUri: getEventSourceUri(), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &CreateEventSourceResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*CreateEventSourceResult); ok { + return value, nil + } + return nil, nil +} + +// UpdateEventSource +func UpdateEventSource(cli bce.Client, args *UpdateEventSourceArgs) (*UpdateEventSourceResult, error) { + op := &Operation{ + HTTPUri: getOneEventSourceUri(args.UUID), + HTTPMethod: PUT, + } + request := &cfcRequest{ + Args: args, + Body: args.FuncEventSource, + } + result := &cfcResult{ + Result: &UpdateEventSourceResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*UpdateEventSourceResult); ok { + return value, nil + } + return nil, nil +} + +func DeleteEventSource(cli bce.Client, args *DeleteEventSourceArgs) error { + op := &Operation{ + HTTPUri: getOneEventSourceUri(args.UUID), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Args: args, + } + result := &cfcResult{} + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} diff --git a/bce-sdk-go/services/cfc/api/errors.go b/bce-sdk-go/services/cfc/api/errors.go new file mode 100644 index 0000000..4ec13bc --- /dev/null +++ b/bce-sdk-go/services/cfc/api/errors.go @@ -0,0 +1,32 @@ +package api + +import "errors" + +var ( + URIIllegal = errors.New("invalid request uri") + MethodIllegal = errors.New("invalid request method") + InvalidArgument = errors.New("invalid arguments") + ParseJsonError = errors.New("could not parse payload into json") +) + +const ( + requiredIllegal = "the %s field is required" + memoryRangeIllegal = "memory size %d must in %d MB ~ %d MB" + memorySizeIllegal = "memory size %d must be a multiple of %d MB" + functionNameInvalid = "the function name of %s must match " + RegularFunctionName + AliasNameInvalid = "the alias name of %s must match " + RegularAliasName + functionBRNInvalid = "the brn %s must match " + RegularFunctionBRN + FunctionCodeInvalid = "the code of function is invalidate" + VersionInvalid = "the version of function must match " + RegularVersion + QualifierInvalid = "the qualifier is not the function's version or alias" + PaginateInvalid = "the pagination must greater than 0" + EventSourceTypeNotSupport = "the event source type: %s not support" +) + +const ( + getExecutionHistoryLimitIllegal = "the limit field should be greater than 0" + flowNameInvalid = "the flow name %s must match " + RegularFlowName + executionNameInvalid = "the execution name %s must match " + RegularExecutionName + flowTypeInvalid = "the flow type %s not supported, only support: " + FlowType + executionInputInvalid = "the execution input is not valid json, err: %s" +) diff --git a/bce-sdk-go/services/cfc/api/model.go b/bce-sdk-go/services/cfc/api/model.go new file mode 100644 index 0000000..e39520c --- /dev/null +++ b/bce-sdk-go/services/cfc/api/model.go @@ -0,0 +1,520 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 api + +import ( + "time" +) + +type InvocationType string +type LogType string +type SourceType string +type TriggerType string + +const ( + InvocationTypeEvent InvocationType = "Event" + InvocationTypeRequestResponse InvocationType = "RequestResponse" + InvocationTypeDryRun InvocationType = "DryRun" + + LogTypeTail LogType = "Tail" + LogTypeNone LogType = "None" + + SourceTypeDuerOS SourceType = "dueros" + SourceTypeDuEdge SourceType = "duedge" + SourceTypeHTTP SourceType = "cfc-http-trigger/v1/CFCAPI" + SourceTypeCrontab SourceType = "cfc-crontab-trigger/v1/" + SourceTypeCDN SourceType = "cdn" + + TriggerTypeHTTP TriggerType = "cfc-http-trigger" + TriggerTypeGeneric TriggerType = "generic" + TypeEventSourceDatahubTopic = "datahub_topic" + TypeEventSourceBms = "bms" + + StartingPositionTriHorizon = "TRIM_HORIZON" + StartingPositionLatest = "LATEST" + StartingPositionAtTimeStamp = "AT_TIMESTAMP" + + DatahubTopicStartPointLatest = int64(-1) + DatahubTopicStartPointOldest = int64(-2) +) + +type Function struct { + Uid string `json:"Uid"` + Description string `json:"Description"` + FunctionBrn string `json:"FunctionBrn"` + Region string `json:"Region"` + Timeout int `json:"Timeout"` + VersionDesc string `json:"VersionDesc"` + UpdatedAt time.Time `json:"UpdatedAt"` + LastModified time.Time `json:"LastModified"` + CodeSha256 string `json:"CodeSha256"` + CodeSize int32 `json:"CodeSize"` + FunctionArn string `json:"FunctionArn"` + FunctionName string `json:"FunctionName"` + Handler string `json:"Handler"` + Version string `json:"Version"` + Runtime string `json:"Runtime"` + MemorySize int `json:"MemorySize"` + Environment *Environment `json:"Environment"` + CommitID string `json:"CommitID"` + CodeID string `json:"CodeID"` + Role string `json:"Role"` + VpcConfig *VpcConfig `json:"VpcConfig"` + LogType string `json:"LogType"` + LogBosDir string `json:"LogBosDir"` + SourceTag string `json:"SourceTag"` +} + +// functionInfo +type FunctionInfo struct { + Code *CodeStorage `json:"Code"` + Configuration *Function `json:"Configuration"` +} + +type Alias struct { + AliasBrn string `json:"AliasBrn"` + AliasArn string `json:"AliasArn"` + FunctionName string `json:"FunctionName"` + FunctionVersion string `json:"FunctionVersion"` + Name string `json:"Name"` + Description string `json:"Description"` + Uid string `json:"Uid"` + UpdatedAt time.Time `json:"UpdatedAt"` + CreatedAt time.Time `json:"CreatedAt"` +} + +type RelationInfo struct { + RelationId string `json:"RelationId"` + Sid string `json:"Sid"` + Source SourceType `json:"Source"` + Target string `json:"Target"` + Data interface{} `json:"Data"` +} + +type CodeStorage struct { + Location string `json:"Location"` + RepositoryType string `json:"RepositoryType"` +} + +type Environment struct { + Variables map[string]string +} + +type CodeFile struct { + Publish bool + DryRun bool + ZipFile []byte + BosBucket string + BosObject string +} + +type VpcConfig struct { + VpcId string + SubnetIds []string + SecurityGroupIds []string +} + +type InvocationsArgs struct { + FunctionName string + InvocationType InvocationType + LogType LogType + Qualifier string + Payload interface{} + RequestId string +} + +type InvocationsResult struct { + Payload string + FunctionError string + LogResult string +} + +type GetFunctionArgs struct { + FunctionName string + Qualifier string +} + +type GetFunctionResult struct { + Code CodeStorage + Configuration Function +} + +type ListFunctionsArgs struct { + FunctionVersion string + Marker int + MaxItems int +} + +type ListFunctionsResult struct { + Functions []*Function + NextMarker string +} + +type CreateFunctionArgs struct { + Code *CodeFile + FunctionName string + Handler string + Runtime string + MemorySize int + Timeout int + Description string + Environment *Environment + VpcConfig *VpcConfig + LogType string + LogBosDir string + ServiceName string +} + +type CreateFunctionByBlueprintArgs struct { + BlueprintID string + ServiceName string + FunctionName string + Environment *Environment +} + +type CreateFunctionResult Function + +type DeleteFunctionArgs struct { + FunctionName string + Qualifier string +} + +type GetFunctionConfigurationArgs struct { + FunctionName string + Qualifier string +} + +type GetFunctionConfigurationResult Function + +type UpdateFunctionConfigurationArgs struct { + FunctionName string + Timeout int `json:"Timeout,omitempty"` + MemorySize int `json:"MemorySize,omitempty"` + Description string + Handler string + Runtime string + Environment *Environment + VpcConfig *VpcConfig + LogType string + LogBosDir string +} + +type UpdateFunctionConfigurationResult Function + +type UpdateFunctionCodeArgs struct { + FunctionName string + ZipFile []byte + Publish bool + DryRun bool + BosBucket string + BosObject string +} + +type UpdateFunctionCodeResult Function + +type ReservedConcurrentExecutionsArgs struct { + FunctionName string + ReservedConcurrentExecutions int +} + +type DeleteReservedConcurrentExecutionsArgs struct { + FunctionName string +} + +type ListVersionsByFunctionArgs struct { + FunctionName string + Marker int + MaxItems int +} +type ListVersionsByFunctionResult struct { + Versions []*Function + NextMarker string +} + +type PublishVersionArgs struct { + FunctionName string + Description string + CodeSha256 string +} + +type PublishVersionResult Function + +type ListAliasesArgs struct { + FunctionName string + FunctionVersion string + Marker int + MaxItems int +} + +type ListAliasesResult struct { + Aliases []*Alias + NextMarker string +} + +type GetAliasArgs struct { + FunctionName string + AliasName string +} + +type GetAliasResult Alias + +type CreateAliasArgs struct { + FunctionName string + FunctionVersion string + Name string + Description string +} + +type CreateAliasResult Alias + +type UpdateAliasArgs struct { + FunctionName string + AliasName string + FunctionVersion string + Description string +} +type UpdateAliasResult Alias + +type DeleteAliasArgs struct { + FunctionName string + AliasName string +} + +type ListTriggersArgs struct { + FunctionBrn string + ScopeType string +} + +type ListTriggersResult struct { + Relation []*RelationInfo +} + +type BosEventType string + +const ( + BosEventTypePutObject BosEventType = "PutObject" + BosEventTypePostObject BosEventType = "PostObject" + BosEventTypeAppendObject BosEventType = "AppendObject" + BosEventTypeCopyObject BosEventType = "CopyObject" + BosEventTypeCompleteMultipartObject BosEventType = "CompleteMultipartUpload" +) + +type BosTriggerData struct { + Resource string + Status string + EventType []BosEventType + Name string +} + +type HttpTriggerData struct { + ResourcePath string + Method string + AuthType string +} + +type CDNEventType string + +const ( + CDNEventTypeCachedObjectsBlocked CDNEventType = "CachedObjectsBlocked" + CDNEventTypeCachedObjectsPushed CDNEventType = "CachedObjectsPushed" + CDNEventTypeCachedObjectsRefreshed CDNEventType = "CachedObjectsRefreshed" + CDNEventTypeCdnDomainCreated CDNEventType = "CdnDomainCreated" + CDNEventTypeCdnDomainDeleted CDNEventType = "CdnDomainDeleted" + CDNEventTypeLogFileCreated CDNEventType = "LogFileCreated" + CDNEventTypeCdnDomainStarted CDNEventType = "CdnDomainStarted" + CDNEventTypeCdnDomainStopped CDNEventType = "CdnDomainStopped" +) + +type CDNTriggerData struct { + EventType CDNEventType + Domains []string + Remark string + Status string +} + +type CrontabTriggerData struct { + Brn string + Enabled string + Input string + Name string + ScheduleExpression string +} + +type CFCEdgeTriggerData struct { + Domain string + EventType string + Path string +} + +type CreateTriggerArgs struct { + Target string + Source SourceType + Data interface{} +} +type CreateTriggerResult struct { + Relation *RelationInfo +} + +type UpdateTriggerArgs struct { + RelationId string + Target string + Source SourceType + Data interface{} +} +type UpdateTriggerResult struct { + Relation *RelationInfo +} + +type DeleteTriggerArgs struct { + RelationId string + Target string + Source SourceType +} + +type CreateEventSourceArgs FuncEventSource + +type CreateEventSourceResult FuncEventSource + +type DeleteEventSourceArgs struct { + UUID string +} +type UpdateEventSourceArgs struct { + UUID string + FuncEventSource FuncEventSource +} + +type UpdateEventSourceResult FuncEventSource + +type GetEventSourceArgs struct { + UUID string +} + +type GetEventSourceResult FuncEventSource + +type ListEventSourceArgs struct { + FunctionName string + Marker int + MaxItems int +} + +type ListEventSourceResult struct { + EventSourceMappings []FuncEventSource +} + +// EventSource +type FuncEventSource struct { + Uuid string `json:"UUID"` + BatchSize int // 一次最多消费多少条消息 + Enabled *bool `json:"Enabled,omitempty"` //是否开启消息触发器 + FunctionBrn string // 绑定的function brn + EventSourceBrn string // 百度消息触发器bms kafka的topic名;Datahub触发器的配置唯一标识符,无需用户传入,服务端自动生成 + FunctionArn string // 兼容aws,与FunctionBrn相同 + EventSourceArn string // 兼容aws,与EventSourceBrn相同 + Type string `json:"Type,omitempty"` // 类型 bms/datahub_topic + FunctionName string `json:"FunctionName,omitempty"` // 函数brn或者函数名 + StartingPosition string `json:"StartingPosition,omitempty"` // 百度消息触发器bms kalfka topic 起始位置 + StartingPositionTimestamp *time.Time `json:"StartingPositionTimestamp,omitempty"` // 百度消息触发器bms kalfka topic 起始时间 + StateTransitionReason string // 状态变更原因 + DatahubConfig // Datahub触发器相关配置 + + State string `json:"State"` // 消息触发器状态,开启或关闭,与aws兼容 + LastProcessingResult string `json:"LastProcessingResult"` // 最新一次触发器的执行结果 + LastModified time.Time `json:"LastModified,omitempty"` // 上次修改时间 +} + +type DatahubConfig struct { + MetaHostEndpoint string `json:"MetaHostEndpoint,omitempty"` // MetaHost endpoint + MetaHostPort int `json:"MetaHostPort,omitempty"` // MetaHost port + ClusterName string `json:"ClusterName,omitempty"` // 集群名 + PipeName string `json:"PipeName,omitempty"` // pipe名 + PipeletNum uint32 `json:"PipeletNum,omitempty"` // 订阅PipiletNum + StartPoint int64 `json:"StartPoint,omitempty"` // 起始订阅点 正常情况下id为正整数, 2个特殊的点 -1: 表示pipelet内的最新一条消息;-2: 表示pipelet内最旧的一条消息 + AclName string `json:"ACLName,omitempty"` // ACL name + AclPassword string `json:"ACLPassword,omitempty"` // ACL passwd +} + +type StartExecutionArgs struct { + FlowName string `json:"flowName,omitempty"` // flowName + ExecutionName string `json:"executionName,omitempty"` // executionName + Input string `json:"input,omitempty"` // input +} + +type Execution struct { + Name string `json:"name"` + Status string `json:"status"` + FlowName string `json:"flowName"` + FlowDefinition string `json:"flowDefinition"` + Input string `json:"input"` + Output string `json:"output"` + StartedTime int64 `json:"startedTime"` + StoppedTime int64 `json:"stoppedTime"` +} + +type ListExecutionsResult struct { + Total int `json:"total"` + Executions []Execution `json:"executions"` +} + +type GetWorkflowResult struct { + Code CodeStorage + Configuration Function +} + +type GetExecutionHistoryResult struct { + Events []*ExecutionEvent `json:"events"` +} + +type ExecutionEvent struct { + CostTime int64 `json:"costTime"` + EventDetail string `json:"eventDetail"` + EventId string `json:"eventId"` + ExecutionId string `json:"executionId"` + StateName string `json:"stateName"` + Time int64 `json:"time"` + Type string `json:"type"` +} + +type Flow struct { + Name string `json:"name"` + Type string `json:"type"` + Definition string `json:"definition"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type ListFlowResult struct { + Flows []*struct { + Name string `json:"name"` + Type string `json:"type"` + LastModifiedTime string `json:"lastModifiedTime"` + Description string `json:"description"` + Definition string `json:"definition"` + CreatedTime string `json:"createdTime"` + } `json:"flows"` +} + +type CreateUpdateFlowArgs struct { + Name string `json:"name"` + Type string `json:"type"` // 当前只支持 "FDL" + Definition string `json:"definition"` + Description string `json:"description"` +} + +type GetExecutionHistoryArgs struct { + FlowName string `json:"name"` + ExecutionName string `json:"type"` + Limit int `json:"limit"` +} diff --git a/bce-sdk-go/services/cfc/api/request.go b/bce-sdk-go/services/cfc/api/request.go new file mode 100644 index 0000000..d827cc8 --- /dev/null +++ b/bce-sdk-go/services/cfc/api/request.go @@ -0,0 +1,41 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 api + +type HTTPMethod = string + +const ( + GET HTTPMethod = "GET" + PUT HTTPMethod = "PUT" + POST HTTPMethod = "POST" + DELETE HTTPMethod = "DELETE" + HEAD HTTPMethod = "HEAD" + OPTIONS HTTPMethod = "OPTIONS" +) + +type Operation struct { + HTTPMethod HTTPMethod + HTTPUri string +} + +type cfcRequest struct { + Args interface{} + Params map[string]interface{} + Body interface{} +} + +type cfcResult struct { + Result interface{} +} diff --git a/bce-sdk-go/services/cfc/api/util.go b/bce-sdk-go/services/cfc/api/util.go new file mode 100644 index 0000000..676e804 --- /dev/null +++ b/bce-sdk-go/services/cfc/api/util.go @@ -0,0 +1,189 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 api + +import ( + "fmt" + "regexp" +) + +const ( + RegularFunctionName = `^[a-zA-Z0-9-_:]+|\$LATEST$` + RegularAliasName = `^[a-zA-Z0-9-_]+$` + RegularFunctionBRN = `^(brn:(bce[a-zA-Z-]*):cfc:)([a-z]{2,5}[0-9]*:)([0-9a-z]{32}:)(function:)([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?$` + RegularVersion = `^\$LATEST|([0-9]+)$` + + RegularFlowName = `^[a-zA-Z_]{1}[a-zA-Z0-9_-]{0,63}$` + RegularExecutionName = `^[a-zA-Z_0-9]{1}[a-zA-Z0-9_-]{0,63}$` + FlowType = "FDL" + + MemoryBase = 128 + minMemoryLimit = 128 + maxMemoryLimit = 3008 +) + +func getInvocationsUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/invocations", functionName) +} + +func getFunctionsUri() string { + return fmt.Sprintf("/v1/functions") +} + +func getFunctionUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s", functionName) +} + +func getFunctionCodeUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/code", functionName) +} + +func getFunctionConfigurationUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/configuration", functionName) +} + +func getFunctionConCurrentUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/concurrency", functionName) +} + +func getFunctionVersionsUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/versions", functionName) +} + +func getFunctionAliasesUri(functionName string) string { + return fmt.Sprintf("/v1/functions/%s/aliases", functionName) +} + +func getFunctionAliasUri(functionName string, aliasName string) string { + return fmt.Sprintf("/v1/functions/%s/aliases/%s", functionName, aliasName) +} + +func getEventSourceUri() string { + return "/v1/event-source-mappings" +} + +func getBlueprintUri(blueprintId string) string { + return fmt.Sprintf("/v1/blueprints/%s/function", blueprintId) +} + +func getExecutionStartUri() string { + return "/v1/execution/start" +} + +func getExecutionStopUri() string { + return "/v1/execution/stop" +} + +func getExecutionsUri() string { + return "/v1/executions" +} + +func getExecutionUri() string { + return "/v1/execution" +} + +func getExecutionHistory() string { + return "/v1/execution/history" +} + +func getWorkflowsUri() string { + return "/v1/flows" +} + +func getWorkflowUri() string { + return "/v1/flow" +} + +func getOneEventSourceUri(uuid string) string { + return fmt.Sprintf("/v1/event-source-mappings/%s", uuid) +} + +func getTriggerUri() string { + return fmt.Sprintf("/v1/console/relation") +} + +func validateFunctionName(name string) bool { + res, err := regexp.MatchString(RegularFunctionName, name) + if err != nil { + return false + } + return res +} + +func validateFlowName(name string) bool { + res, err := regexp.MatchString(RegularFlowName, name) + if err != nil { + return false + } + return res +} + +func validateExecutionName(name string) bool { + res, err := regexp.MatchString(RegularExecutionName, name) + if err != nil { + return false + } + return res +} + +func validateAliasName(name string) bool { + res, err := regexp.MatchString(RegularAliasName, name) + if err != nil { + return false + } + return res +} + +func validateMemorySize(size int) error { + if size%MemoryBase != 0 { + return fmt.Errorf(memorySizeIllegal, size, MemoryBase) + } + if size > maxMemoryLimit { + return fmt.Errorf(memoryRangeIllegal, size, minMemoryLimit, maxMemoryLimit) + } + if size < minMemoryLimit { + return fmt.Errorf(memoryRangeIllegal, size, minMemoryLimit, maxMemoryLimit) + } + return nil +} + +func validateFunctionBRN(brn string) bool { + res, err := regexp.MatchString(RegularFunctionBRN, brn) + if err != nil { + return false + } + return res +} + +func validateVersion(version string) bool { + res, err := regexp.MatchString(RegularVersion, version) + if err != nil { + return false + } + return res +} + +func validateQualifier(qualifier string) bool { + var res bool + res, err := regexp.MatchString(RegularAliasName, qualifier) + if err == nil && res == true { + return true + } + res, err = regexp.MatchString(RegularVersion, qualifier) + if err != nil { + return false + } + return res +} diff --git a/bce-sdk-go/services/cfc/api/validator.go b/bce-sdk-go/services/cfc/api/validator.go new file mode 100644 index 0000000..3db926d --- /dev/null +++ b/bce-sdk-go/services/cfc/api/validator.go @@ -0,0 +1,416 @@ +package api + +import ( + "encoding/json" + "fmt" +) + +type Validator interface { + Validate() error +} + +func (args InvocationsArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Qualifier != "" && !validateQualifier(args.Qualifier) { + return fmt.Errorf(QualifierInvalid) + } + return nil +} + +func (args ListFunctionsArgs) Validate() error { + if args.Marker < 0 || args.MaxItems < 0 { + return fmt.Errorf(PaginateInvalid) + } + if args.FunctionVersion != "" && !validateVersion(args.FunctionVersion) { + return fmt.Errorf(VersionInvalid) + } + return nil +} + +func (args GetFunctionArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Qualifier != "" && !validateQualifier(args.Qualifier) { + return fmt.Errorf(QualifierInvalid) + } + return nil +} + +func (args CreateFunctionArgs) Validate() error { + if args.Code == nil { + return fmt.Errorf(requiredIllegal, "Code") + } + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Handler == "" { + return fmt.Errorf(requiredIllegal, "Handler") + } + if args.Runtime == "" { + return fmt.Errorf(requiredIllegal, "Runtime") + } + if err := validateMemorySize(args.MemorySize); err != nil { + return err + } + if len(args.Code.ZipFile) == 0 && (len(args.Code.BosBucket) == 0 || len(args.Code.BosObject) == 0) { + return fmt.Errorf(FunctionCodeInvalid) + } + return nil +} + +func (args DeleteFunctionArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Qualifier != "" && !validateQualifier(args.Qualifier) { + return fmt.Errorf(QualifierInvalid) + } + return nil +} + +func (args UpdateFunctionCodeArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + return nil +} + +func (args GetFunctionConfigurationArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Qualifier != "" && !validateQualifier(args.Qualifier) { + return fmt.Errorf(QualifierInvalid) + } + return nil +} + +func (args UpdateFunctionConfigurationArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + return nil +} + +func (args ListVersionsByFunctionArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Marker < 0 || args.MaxItems < 0 { + return fmt.Errorf(PaginateInvalid) + } + return nil +} + +func (args PublishVersionArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + return nil +} + +func (args ListAliasesArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if args.Marker < 0 || args.MaxItems < 0 { + return fmt.Errorf(PaginateInvalid) + } + if args.FunctionVersion != "" && !validateVersion(args.FunctionVersion) { + return fmt.Errorf(VersionInvalid) + } + return nil +} + +func (args CreateAliasArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if args.FunctionVersion == "" { + return fmt.Errorf(requiredIllegal, "FunctionVersion") + } + if args.Name == "" { + return fmt.Errorf(requiredIllegal, "Name") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if !validateVersion(args.FunctionVersion) { + return fmt.Errorf(VersionInvalid) + } + if !validateAliasName(args.Name) { + return fmt.Errorf(AliasNameInvalid, args.FunctionName) + } + return nil +} + +func (args GetAliasArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if args.AliasName == "" { + return fmt.Errorf(requiredIllegal, "AliasName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if !validateAliasName(args.AliasName) { + return fmt.Errorf(AliasNameInvalid, args.FunctionName) + } + return nil +} + +func (args UpdateAliasArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if args.AliasName == "" { + return fmt.Errorf(requiredIllegal, "AliasName") + } + if args.FunctionVersion == "" { + return fmt.Errorf(requiredIllegal, "FunctionVersion") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if !validateAliasName(args.AliasName) { + return fmt.Errorf(AliasNameInvalid, args.FunctionName) + } + if !validateVersion(args.FunctionVersion) { + return fmt.Errorf(VersionInvalid) + } + return nil +} + +func (args DeleteAliasArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + if args.AliasName == "" { + return fmt.Errorf(requiredIllegal, "AliasName") + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + if !validateAliasName(args.AliasName) { + return fmt.Errorf(AliasNameInvalid, args.FunctionName) + } + return nil +} + +func (args ListTriggersArgs) Validate() error { + if args.FunctionBrn == "" { + return fmt.Errorf(requiredIllegal, "FunctionBrn") + } + if !validateFunctionBRN(args.FunctionBrn) { + return fmt.Errorf(functionBRNInvalid, args.FunctionBrn) + } + return nil +} + +func (args CreateTriggerArgs) Validate() error { + if args.Target == "" { + return fmt.Errorf(requiredIllegal, "Target") + } + if args.Source == "" { + return fmt.Errorf(requiredIllegal, "Source") + } + if !validateFunctionBRN(args.Target) { + return fmt.Errorf(functionBRNInvalid, args.Target) + } + + return nil +} + +func (args UpdateTriggerArgs) Validate() error { + if args.RelationId == "" { + return fmt.Errorf(requiredIllegal, "RelationId") + } + if args.Target == "" { + return fmt.Errorf(requiredIllegal, "Target") + } + if args.Source == "" { + return fmt.Errorf(requiredIllegal, "Source") + } + if !validateFunctionBRN(args.Target) { + return fmt.Errorf(functionBRNInvalid, args.Target) + } + + return nil +} + +func (args DeleteTriggerArgs) Validate() error { + if args.RelationId == "" { + return fmt.Errorf(requiredIllegal, "RelationId") + } + if args.Target == "" { + return fmt.Errorf(requiredIllegal, "Target") + } + if args.Source == "" { + return fmt.Errorf(requiredIllegal, "Source") + } + if !validateFunctionBRN(args.Target) { + return fmt.Errorf(functionBRNInvalid, args.Target) + } + return nil +} + +func (args ReservedConcurrentExecutionsArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + + if args.ReservedConcurrentExecutions < 0 || args.ReservedConcurrentExecutions > 90 { + return fmt.Errorf(requiredIllegal, "ReservedConcurrentExecutions") + } + + return nil +} + +func (args DeleteReservedConcurrentExecutionsArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + + return nil +} + +func (args ListEventSourceArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + // 先检查是否满足brn规则 再看是不是function name + if !validateFunctionBRN(args.FunctionName) || !validateFunctionName(args.FunctionName) { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + + if args.Marker < 0 || args.MaxItems < 0 { + return fmt.Errorf(PaginateInvalid) + } + return nil +} +func (args GetEventSourceArgs) Validate() error { + if args.UUID == "" { + return fmt.Errorf(requiredIllegal, "UUID") + } + return nil +} + +func (args CreateEventSourceArgs) Validate() error { + if args.FunctionName == "" { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + // 先检查是否满足brn规则 再看是不是function name + if !validateFunctionBRN(args.FunctionName) || !validateFunctionName(args.FunctionName) { + return fmt.Errorf(requiredIllegal, "FunctionName") + } + + if args.Type == TypeEventSourceDatahubTopic || args.Type == TypeEventSourceBms { + return nil + } else { + return fmt.Errorf(EventSourceTypeNotSupport, args.Type) + } +} + +func (args UpdateEventSourceArgs) Validate() error { + if args.UUID == "" { + return fmt.Errorf(requiredIllegal, "UUID") + } + if args.FuncEventSource.Type != TypeEventSourceDatahubTopic || args.FuncEventSource.Type != TypeEventSourceBms { + return nil + } else { + return fmt.Errorf(EventSourceTypeNotSupport, args.FuncEventSource.Type) + } + +} + +func (args DeleteEventSourceArgs) Validate() error { + if args.UUID == "" { + return fmt.Errorf(requiredIllegal, "UUID") + } + return nil +} + +func (args GetExecutionHistoryArgs) Validate() error { + if args.Limit <= 0 { + return fmt.Errorf(getExecutionHistoryLimitIllegal) + } + if !validateFlowName(args.FlowName) { + return fmt.Errorf(flowNameInvalid, args.FlowName) + } + if !validateExecutionName(args.ExecutionName) { + return fmt.Errorf(executionNameInvalid, args.ExecutionName) + } + return nil +} + +func (args CreateUpdateFlowArgs) Validate() error { + if !validateFlowName(args.Name) { + return fmt.Errorf(flowNameInvalid, args.Name) + } + if args.Type != "" && args.Type != "FDL" { + return fmt.Errorf(flowTypeInvalid, args.Type) + } + return nil +} + +func (args StartExecutionArgs) Validate() error { + if !validateFlowName(args.FlowName) { + return fmt.Errorf(flowNameInvalid, args.FlowName) + } + if args.ExecutionName != "" { + if !validateExecutionName(args.ExecutionName) { + return fmt.Errorf(executionNameInvalid, args.ExecutionName) + } + } + + var input = map[string]interface{}{} + err := json.Unmarshal([]byte(args.Input), &input) + if err != nil { + return fmt.Errorf(executionInputInvalid, err.Error()) + } + return nil +} + +func (args CreateFunctionByBlueprintArgs) Validate() error { + if args.BlueprintID == "" { + return fmt.Errorf(requiredIllegal, args.BlueprintID) + } + if !validateFunctionName(args.FunctionName) { + return fmt.Errorf(functionNameInvalid, args.FunctionName) + } + return nil +} diff --git a/bce-sdk-go/services/cfc/api/workflow.go b/bce-sdk-go/services/cfc/api/workflow.go new file mode 100644 index 0000000..97d57df --- /dev/null +++ b/bce-sdk-go/services/cfc/api/workflow.go @@ -0,0 +1,227 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +func StartExecution(cli bce.Client, args *StartExecutionArgs) (*Execution, error) { + op := &Operation{ + HTTPUri: getExecutionStartUri(), + HTTPMethod: POST, + } + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &Execution{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*Execution); ok { + return value, nil + } + return nil, nil +} + +func StopExecution(cli bce.Client, flowName, executionName string) (*Execution, error) { + op := &Operation{ + HTTPUri: getExecutionStopUri(), + HTTPMethod: POST, + } + request := &cfcRequest{ + Body: map[string]string{"flowName": flowName, "executionName": executionName}, + } + result := &cfcResult{ + Result: &Execution{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*Execution); ok { + return value, nil + } + return nil, nil +} + +func ListExecutions(cli bce.Client, flowName string) (*ListExecutionsResult, error) { + op := &Operation{ + HTTPUri: getExecutionsUri(), + HTTPMethod: GET, + } + request := &cfcRequest{ + Params: map[string]interface{}{"flowName": flowName}, + } + result := &cfcResult{ + Result: &ListExecutionsResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListExecutionsResult); ok { + return value, nil + } + return nil, nil +} + +func DescribeExecution(cli bce.Client, flowName, executionName string) (*Execution, error) { + op := &Operation{ + HTTPUri: getExecutionUri(), + HTTPMethod: GET, + } + request := &cfcRequest{ + Params: map[string]interface{}{ + "flowName": flowName, + "executionName": executionName, + }, + } + result := &cfcResult{ + Result: &Execution{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*Execution); ok { + return value, nil + } + return nil, nil +} + +func GetExecutionHistory(cli bce.Client, args *GetExecutionHistoryArgs) (*GetExecutionHistoryResult, error) { + op := &Operation{ + HTTPUri: getExecutionHistory(), + HTTPMethod: GET, + } + if args.Limit == 0 { + args.Limit = 100 + } + request := &cfcRequest{ + Args: args, + Params: map[string]interface{}{ + "flowName": args.FlowName, + "executionName": args.ExecutionName, + "limit": args.Limit, + }, + } + result := &cfcResult{ + Result: &GetExecutionHistoryResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*GetExecutionHistoryResult); ok { + return value, nil + } + return nil, nil +} + +func ListFlow(cli bce.Client) (*ListFlowResult, error) { + op := &Operation{ + HTTPUri: getWorkflowsUri(), + HTTPMethod: GET, + } + request := &cfcRequest{} + result := &cfcResult{ + Result: &ListFlowResult{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*ListFlowResult); ok { + return value, nil + } + return nil, nil +} + +func CreateFlow(cli bce.Client, args *CreateUpdateFlowArgs) (*Flow, error) { + op := &Operation{ + HTTPUri: getWorkflowUri(), + HTTPMethod: POST, + } + return createUpdateFlow(cli, args, op) +} + +func UpdateFlow(cli bce.Client, args *CreateUpdateFlowArgs) (*Flow, error) { + op := &Operation{ + HTTPUri: getWorkflowUri(), + HTTPMethod: PUT, + } + + return createUpdateFlow(cli, args, op) +} + +func createUpdateFlow(cli bce.Client, args *CreateUpdateFlowArgs, op *Operation) (*Flow, error) { + request := &cfcRequest{ + Args: args, + Body: args, + } + result := &cfcResult{ + Result: &Flow{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*Flow); ok { + return value, nil + } + return nil, nil +} + +func DescribeFlow(cli bce.Client, flowName string) (*Flow, error) { + op := &Operation{ + HTTPUri: getWorkflowUri(), + HTTPMethod: GET, + } + request := &cfcRequest{ + Params: map[string]interface{}{"flowName": flowName}, + } + result := &cfcResult{ + Result: &Flow{}, + } + err := caller(cli, op, request, result) + if err != nil { + return nil, err + } + if value, ok := result.Result.(*Flow); ok { + return value, nil + } + return nil, nil +} + +func DeleteFlow(cli bce.Client, flowName string) error { + op := &Operation{ + HTTPUri: getWorkflowUri(), + HTTPMethod: DELETE, + } + request := &cfcRequest{ + Params: map[string]interface{}{"flowName": flowName}, + } + result := &cfcResult{} + err := caller(cli, op, request, result) + if err != nil { + return err + } + return nil +} diff --git a/bce-sdk-go/services/cfc/client.go b/bce-sdk-go/services/cfc/client.go new file mode 100644 index 0000000..fa3c936 --- /dev/null +++ b/bce-sdk-go/services/cfc/client.go @@ -0,0 +1,525 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for CFC service + +// Package cfc defines the CFC services of BCE. The supported APIs are all defined in sub-package + +package cfc + +import ( + "errors" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/cfc/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "cfc." + bce.DEFAULT_REGION + ".baidubce.com" +) + +// Client of CFC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the CFC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func newClient(ak, sk, token, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 || len(sk) == 0 { + return nil, errors.New("ak sk illegal") + } + if len(token) == 0 { + credentials, err = auth.NewBceCredentials(ak, sk) + } else { + credentials, err = auth.NewSessionBceCredentials(ak, sk, token) + } + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func NewClient(ak, sk, endpoint string) (*Client, error) { + return newClient(ak, sk, "", endpoint) +} + +// Invocations - invocation a cfc function with specific parameters +// +// PARAMS: +// - args: the arguments to invocation cfc function +// +// RETURNS: +// - *api.InvocationsResult: the result of invocation cfc function +// - error: nil if success otherwise the specific error +func (c *Client) Invocations(args *api.InvocationsArgs) (*api.InvocationsResult, error) { + return api.Invocations(c, args) +} + +// Invoke - invoke a cfc function, the same as Invocations +// +// PARAMS: +// - args: the arguments to invocation cfc function +// +// RETURNS: +// - *api.InvocationsResult: the result of invocation cfc function +// - error: nil if success otherwise the specific error +func (c *Client) Invoke(args *api.InvocationsArgs) (*api.InvocationsResult, error) { + return api.Invocations(c, args) +} + +// ListFunctions - list all functions with the specific parameters +// +// PARAMS: +// - args: the arguments to list all functions +// +// RETURNS: +// - *api.ListFunctionsResult: the result of list all functions +// - error: nil if success otherwise the specific error +func (c *Client) ListFunctions(args *api.ListFunctionsArgs) (*api.ListFunctionsResult, error) { + return api.ListFunctions(c, args) +} + +// GetFunction - get a specific cfc function +// +// PARAMS: +// - args: the arguments to get a specific cfc function +// +// RETURNS: +// - *api.GetFunctionResult: the result of get function +// - error: nil if success otherwise the specific error +func (c *Client) GetFunction(args *api.GetFunctionArgs) (*api.GetFunctionResult, error) { + return api.GetFunction(c, args) +} + +// CreateFunction - create a cfc function with specific parameters +// +// PARAMS: +// - args: the arguments to create a cfc function +// +// RETURNS: +// - *api.CreateFunctionResult: the result of create a cfc function, it contains function information +// - error: nil if success otherwise the specific error +func (c *Client) CreateFunction(args *api.CreateFunctionArgs) (*api.CreateFunctionResult, error) { + return api.CreateFunction(c, args) +} + +// CreateFunctionByBlueprint - create a cfc function by a blueprint +// +// PARAMS: +// - args: the arguments to create a cfc function +// +// RETURNS: +// - *api.CreateFunctionResult: the result of create a cfc function, it contains function information +// - error: nil if success otherwise the specific error +func (c *Client) CreateFunctionByBlueprint(args *api.CreateFunctionByBlueprintArgs) (*api.CreateFunctionResult, error) { + return api.CreateFunctionByBlueprint(c, args) +} + +// DeleteFunction - delete a specific cfc function +// +// PARAMS: +// - args: the arguments to delete cfc function +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteFunction(args *api.DeleteFunctionArgs) error { + return api.DeleteFunction(c, args) +} + +// UpdateFunctionCode - update a cfc function code +// +// PARAMS: +// - args: the arguments to update function code +// +// RETURNS: +// - *api.UpdateFunctionCodeResult: the result of update function code +// - error: nil if success otherwise the specific error +func (c *Client) UpdateFunctionCode(args *api.UpdateFunctionCodeArgs) (*api.UpdateFunctionCodeResult, error) { + return api.UpdateFunctionCode(c, args) +} + +// GetFunctionConfiguration - get a specific cfc function configuration +// +// PARAMS: +// - args: the arguments to get function configuration +// +// RETURNS: +// - *api.GetFunctionConfigurationResult: the result of function configuration +// - error: nil if success otherwise the specific error +func (c *Client) GetFunctionConfiguration(args *api.GetFunctionConfigurationArgs) (*api.GetFunctionConfigurationResult, error) { + return api.GetFunctionConfiguration(c, args) +} + +// UpdateFunctionConfiguration - update a specific cfc function configuration +// +// PARAMS: +// - args: the arguments to update cfc function +// +// RETURNS: +// - *api.UpdateFunctionConfigurationResult: the result of update function configuration +// - error: nil if success otherwise the specific error +func (c *Client) UpdateFunctionConfiguration(args *api.UpdateFunctionConfigurationArgs) (*api.UpdateFunctionConfigurationResult, error) { + return api.UpdateFunctionConfiguration(c, args) +} + +// ListVersionsByFunction - list all versions about a specific cfc function +// +// PARAMS: +// - args: the arguments to list all versions +// +// RETURNS: +// - *api.ListVersionsByFunctionResult: the result of all versions information +// - error: nil if success otherwise the specific error +func (c *Client) ListVersionsByFunction(args *api.ListVersionsByFunctionArgs) (*api.ListVersionsByFunctionResult, error) { + return api.ListVersionsByFunction(c, args) +} + +// PublishVersion - publish a cfc function as a new version +// +// PARAMS: +// - args: the arguments to publish a version +// +// RETURNS: +// - *api.PublishVersionResult: the result of publish a function version +// - error: nil if success otherwise the specific error +func (c *Client) PublishVersion(args *api.PublishVersionArgs) (*api.PublishVersionResult, error) { + return api.PublishVersion(c, args) +} + +// ListAliases - list all alias about a specific cfc function with specific parameters +// +// PARAMS: +// - args: the arguments to list all alias +// +// RETURNS: +// - *api.ListAliasesResult: the result of list all alias +// - error: nil if success otherwise the specific error +func (c *Client) ListAliases(args *api.ListAliasesArgs) (*api.ListAliasesResult, error) { + return api.ListAliases(c, args) +} + +// CreateAlias - create an alias which bind one specific cfc function version +// +// PARAMS: +// - args: the arguments to create an alias +// +// RETURNS: +// - *api.CreateAliasResult: the result of create alias +// - error: nil if success otherwise the specific error +func (c *Client) CreateAlias(args *api.CreateAliasArgs) (*api.CreateAliasResult, error) { + return api.CreateAlias(c, args) +} + +// GetAlias - get alias information which bind one cfc function +// +// PARAMS: +// - args: the arguments to get an alias +// +// RETURNS: +// - *api.GetAliasResult: the result of get alias +// - error: nil if success otherwise the specific error +func (c *Client) GetAlias(args *api.GetAliasArgs) (*api.GetAliasResult, error) { + return api.GetAlias(c, args) +} + +// UpdateAlias - update an alias configuration +// +// PARAMS: +// - args: the arguments to update an alias +// +// RETURNS: +// - *api.UpdateAliasResult: the result of update an alias +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAlias(args *api.UpdateAliasArgs) (*api.UpdateAliasResult, error) { + return api.UpdateAlias(c, args) +} + +// DeleteAlias - delete an alias +// +// PARAMS: +// - args: the arguments to delete an alias +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAlias(args *api.DeleteAliasArgs) error { + return api.DeleteAlias(c, args) +} + +// ListTriggers - list all triggers in one cfc function version +// +// PARAMS: +// - args: the arguments to list all triggers +// +// RETURNS: +// - *api.ListTriggersResult: the result of list all triggers +// - error: nil if success otherwise the specific error +func (c *Client) ListTriggers(args *api.ListTriggersArgs) (*api.ListTriggersResult, error) { + return api.ListTriggers(c, args) +} + +// CreateTrigger - create a specific trigger +// +// PARAMS: +// - args: the arguments to create a trigger +// +// RETURNS: +// - *api.CreateTriggerResult: the result of create a trigger +// - error: nil if success otherwise the specific error +func (c *Client) CreateTrigger(args *api.CreateTriggerArgs) (*api.CreateTriggerResult, error) { + return api.CreateTrigger(c, args) +} + +// UpdateTrigger - update a trigger +// +// PARAMS: +// - args: the arguments to update a trigger +// +// RETURNS: +// - *api.UpdateTriggerResult: the result of update a trigger +// - error: nil if success otherwise the specific error +func (c *Client) UpdateTrigger(args *api.UpdateTriggerArgs) (*api.UpdateTriggerResult, error) { + return api.UpdateTrigger(c, args) +} + +// DeleteTrigger - delete a trigger +// +// PARAMS: +// - args: the arguments to delete a trigger +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteTrigger(args *api.DeleteTriggerArgs) error { + return api.DeleteTrigger(c, args) +} + +// SetReservedConcurrentExecutions - set a cfc function reserved concurrent executions +// +// PARAMS: +// - args: the arguments to set reserved concurrent executions +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SetReservedConcurrentExecutions(args *api.ReservedConcurrentExecutionsArgs) error { + return api.SetReservedConcurrentExecutions(c, args) +} + +// DeleteReservedConcurrentExecutions - delete one cfc function reserved concurrent executions setting +// +// PARAMS: +// - args: the arguments to delete reserved concurrent executions setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteReservedConcurrentExecutions(args *api.DeleteReservedConcurrentExecutionsArgs) error { + return api.DeleteReservedConcurrentExecutions(c, args) +} + +// ListEventSource - list all event source mapping settings in one cfc function version +// +// PARAMS: +// - args: the arguments to list all event source mapping settings +// +// RETURNS: +// - *api.ListEventSourceResult: the result of list all event source mapping settings +// - error: nil if success otherwise the specific error +func (c *Client) ListEventSource(args *api.ListEventSourceArgs) (*api.ListEventSourceResult, error) { + return api.ListEventSource(c, args) +} + +// GetEventSource - get info for a event source mapping setting +// +// PARAMS: +// - args: the arguments to get a event source mapping setting +// +// RETURNS: +// - *api.GetEventSourceResult: the result of get a event source mapping +// - error: nil if success otherwise the specific error +func (c *Client) GetEventSource(args *api.GetEventSourceArgs) (*api.GetEventSourceResult, error) { + return api.GetEventSource(c, args) +} + +// UpdateEventSource - update a event source mapping setting +// +// PARAMS: +// - args: the arguments to update a event source mapping +// +// RETURNS: +// - *api.UpdateEventSourceResult: the result of update a event source mapping +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEventSource(args *api.UpdateEventSourceArgs) (*api.UpdateEventSourceResult, error) { + return api.UpdateEventSource(c, args) +} + +// CreateEventSource - create a event source mapping setting +// +// PARAMS: +// - args: the arguments to create a event source mapping setting +// +// RETURNS: +// - *api.CreateEventSourceResult: the result of create event source mapping setting +// - error: nil if success otherwise the specific error +func (c *Client) CreateEventSource(args *api.CreateEventSourceArgs) (*api.CreateEventSourceResult, error) { + return api.CreateEventSource(c, args) +} + +// DeleteEventSource - delete one cfc event source mapping setting +// +// PARAMS: +// - args: the arguments to delete cfc event source mapping setting +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEventSource(args *api.DeleteEventSourceArgs) error { + return api.DeleteEventSource(c, args) + +} + +// StartExecution - start an execution of a workflow +// +// PARAMS: +// - args: the arguments to start execution, include flowName、executionName(optional) and input +// input is a serialized json string, for example: "{\"fruits\":[\"apple\", \"banana\"]}". +// +// RETURNS: +// - *api.Execution: information about the execution +// - error: nil if success otherwise the specific error +func (c *Client) StartExecution(args *api.StartExecutionArgs) (*api.Execution, error) { + return api.StartExecution(c, args) +} + +// StopExecution - stop a running execution of a workflow +// +// PARAMS: +// - flowName: which flow the execution belongs to +// - executionName: which execution to stop +// +// RETURNS: +// - *api.Execution: information about the execution +// - error: nil if success otherwise the specific error +func (c *Client) StopExecution(flowName, executionName string) (*api.Execution, error) { + return api.StopExecution(c, flowName, executionName) +} + +// ListExecutions - list all executions of a flow +// +// PARAMS: +// - flowName: which flow to list executions +// +// RETURNS: +// - *api.ListExecutionsResult: information about the executions +// - error: nil if success otherwise the specific error +func (c *Client) ListExecutions(flowName string) (*api.ListExecutionsResult, error) { + return api.ListExecutions(c, flowName) +} + +// DescribeExecution - describe detail info of an execution +// +// PARAMS: +// - flowName: which flow the execution belongs to +// - executionName: which execution to get +// +// RETURNS: +// - *api.Execution: information about the execution +// - error: nil if success otherwise the specific error +func (c *Client) DescribeExecution(flowName, executionName string) (*api.Execution, error) { + return api.DescribeExecution(c, flowName, executionName) +} + +// GetExecutionHistory - get all state event detail info of an execution +// +// PARAMS: +// - args: args to get history +// +// RETURNS: +// - *api.GetExecutionHistoryResult: state history of the execution +// - error: nil if success otherwise the specific error +func (c *Client) GetExecutionHistory(args *api.GetExecutionHistoryArgs) (*api.GetExecutionHistoryResult, error) { + return api.GetExecutionHistory(c, args) +} + +// ListFlow - list all flows +// +// RETURNS: +// - *api.ListFlowResult: list of all flows +// - error: nil if success otherwise the specific error +func (c *Client) ListFlow() (*api.ListFlowResult, error) { + return api.ListFlow(c) +} + +// CreateFlow - create a flow +// +// PARAMS: +// - args: args to create a flow +// +// RETURNS: +// - *api.Flow: the created flow information +// - error: nil if success otherwise the specific error +func (c *Client) CreateFlow(args *api.CreateUpdateFlowArgs) (*api.Flow, error) { + return api.CreateFlow(c, args) +} + +// UpdateFlow - update a flow +// +// PARAMS: +// - args: args to update a flow, args.Name is the name of flow to update +// +// RETURNS: +// - *api.Flow: flow information after update +// - error: nil if success otherwise the specific error +func (c *Client) UpdateFlow(args *api.CreateUpdateFlowArgs) (*api.Flow, error) { + return api.UpdateFlow(c, args) +} + +// DescribeFlow - get information of a flow +// +// PARAMS: +// - flowName: name of the flow to describe +// +// RETURNS: +// - *api.Flow: flow information +// - error: nil if success otherwise the specific error +func (c *Client) DescribeFlow(flowName string) (*api.Flow, error) { + return api.DescribeFlow(c, flowName) +} + +// DeleteFlow - delete a flow +// +// PARAMS: +// - flowName: name of the flow to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteFlow(flowName string) error { + return api.DeleteFlow(c, flowName) +} diff --git a/bce-sdk-go/services/cfc/client_test.go b/bce-sdk-go/services/cfc/client_test.go new file mode 100644 index 0000000..01cee23 --- /dev/null +++ b/bce-sdk-go/services/cfc/client_test.go @@ -0,0 +1,747 @@ +package cfc + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/services/cfc/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CfcClient *Client + FunctionName01 string + FunctionName02 string + AliasName01 string + AliasName02 string + FunctionBRN string + CodeSha256 string + RelationId string + zipFilePython string + zipFileNodejs01 string + zipFileNodejs02 string +) + +const ( + invokeTestReturnPayload = "Hello World" +) + +var ( + logSuccess = true +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +type PayloadDemo struct { + A string + B int +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given:(%s) err(%v)\n", conf, err) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + FunctionName01 = fmt.Sprintf("sdktest-function01-%s", time.Now().Format("2006-01-02T150405")) + FunctionName02 = fmt.Sprintf("sdktest-function02-%s", time.Now().Format("2006-01-02T150405")) + zipFilePython = filepath.Join(filepath.Dir(f), "./python.zip") + zipFileNodejs01 = filepath.Join(filepath.Dir(f), "./nodejs.zip") + zipFileNodejs02 = filepath.Join(filepath.Dir(f), "./nodejs2.zip") + + AliasName01 = fmt.Sprintf("sdktest-alias01-%s", time.Now().Format("2006-01-02T150405")) + AliasName02 = fmt.Sprintf("sdktest-alias02-%s", time.Now().Format("2006-01-02T150405")) + + CfcClient, err = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + if err != nil { + panic(err) + } + log.SetLogHandler(log.FILE) + //log.SetLogHandler(log.STDERR | log.FILE) + //log.SetRotateType(log.ROTATE_SIZE) + log.SetLogLevel(log.DEBUG) +} + +func TestCreateFunction(t *testing.T) { + codeFile, err := ioutil.ReadFile(zipFilePython) + if err != nil { + t.Fatalf("err (%v)", err) + } + + codeFile2, err := ioutil.ReadFile(zipFilePython) + if err != nil { + t.Fatalf("err (%v)", err) + } + // This function return Hello World + cases := []api.CreateFunctionArgs{ + { + Code: &api.CodeFile{ZipFile: codeFile}, + FunctionName: FunctionName01, + Handler: "index.handler", + Runtime: "python2", + MemorySize: 128, + Timeout: 3, + Description: "Description", + }, + { + Code: &api.CodeFile{ZipFile: codeFile2}, + FunctionName: FunctionName02, + Handler: "index.handler", + Runtime: "nodejs8.5", + MemorySize: 256, + Timeout: 3, + Description: "Description", + }, + } + for _, args := range cases { + res, err := CfcClient.CreateFunction(&args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } + } +} + +func TestCreateFunctionByBlueprint(t *testing.T) { + cases := []api.CreateFunctionByBlueprintArgs{ + { + FunctionName: "f2", + ServiceName: "default", + BlueprintID: "6b850453-00f2-473f-8f9c-b88e1705d9e6", + Environment: &api.Environment{ + Variables: map[string]string{ + "k1": "v1", + }, + }, + }, + } + for _, args := range cases { + res, err := CfcClient.CreateFunctionByBlueprint(&args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } + } +} + +func TestListFunctions(t *testing.T) { + args := &api.ListFunctionsArgs{ + FunctionVersion: "1", + Marker: 1, + MaxItems: 2, + } + res, err := CfcClient.ListFunctions(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestGetFunction(t *testing.T) { + res, err := CfcClient.GetFunction(&api.GetFunctionArgs{ + FunctionName: FunctionName01, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + FunctionBRN = res.Configuration.FunctionBrn + fmt.Printf(FunctionBRN) + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestInvocations(t *testing.T) { + cases := []struct { + args *api.InvocationsArgs + respPayload string + err error + }{ + { + args: &api.InvocationsArgs{ + FunctionName: FunctionName01, + InvocationType: api.InvocationTypeRequestResponse, + Payload: nil, + }, + respPayload: invokeTestReturnPayload, + err: nil, + }, + { + args: &api.InvocationsArgs{ + FunctionName: FunctionName01, + InvocationType: api.InvocationTypeEvent, + Payload: `[{"a":1},{"a":2}]`, + }, + respPayload: "", + err: nil, + }, + { + args: &api.InvocationsArgs{ + FunctionName: FunctionName01, + InvocationType: api.InvocationTypeRequestResponse, + Payload: `[{"a":,{"a":2}]`, + }, + respPayload: "", + err: fmt.Errorf("could not parse payload into json"), + }, + { + args: &api.InvocationsArgs{ + FunctionName: FunctionName01, + InvocationType: api.InvocationTypeEvent, + Payload: []byte(`{"a":1}`), + }, + respPayload: "", + err: nil, + }, + { + args: &api.InvocationsArgs{ + FunctionName: FunctionName01, + InvocationType: api.InvocationTypeRequestResponse, + Payload: []*PayloadDemo{&PayloadDemo{A: "1", B: 2}, &PayloadDemo{A: "3", B: 4}}, + }, + respPayload: invokeTestReturnPayload, + err: nil, + }, + } + for _, tc := range cases { + t.Run("invoke", func(t *testing.T) { + res, err := CfcClient.Invocations(tc.args) + if err == nil && tc.err != nil { + t.Errorf("Expected err to be %v, but got nil", tc.err) + } else if err != nil && tc.err == nil { + t.Errorf("Expected err to be nil, but got %v", err) + } else if err != nil && tc.err != nil && err.Error() != tc.err.Error() { + t.Errorf("Expected err to be %v, but got %v", tc.err, err) + } else if res != nil && res.Payload != tc.respPayload { + t.Errorf("Expected Payload to be %s, but got %s", tc.respPayload, res.Payload) + } + }) + } +} + +func TestUpdateFunctionCode(t *testing.T) { + codeFile, err := ioutil.ReadFile(zipFileNodejs02) + if err != nil { + t.Fatalf("err (%v)", err) + } + res, err := CfcClient.UpdateFunctionCode(&api.UpdateFunctionCodeArgs{ + FunctionName: FunctionName02, + ZipFile: codeFile, + Publish: false, + DryRun: false, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestUpdateFunctionConfiguration(t *testing.T) { + res, err := CfcClient.UpdateFunctionConfiguration(&api.UpdateFunctionConfigurationArgs{ + FunctionName: FunctionName02, + Timeout: 5, + Description: "wooo cool", + Handler: "index.handler", + Runtime: "nodejs8.5", + Environment: &api.Environment{ + Variables: map[string]string{ + "name": "Test", + }, + }, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestGetFunctionConfiguration(t *testing.T) { + res, err := CfcClient.GetFunctionConfiguration(&api.GetFunctionConfigurationArgs{ + FunctionName: FunctionName02, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestPublishVersion(t *testing.T) { + res, err := CfcClient.GetFunction(&api.GetFunctionArgs{ + FunctionName: FunctionName02, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + CodeSha256 = res.Configuration.CodeSha256 + fmt.Printf(FunctionBRN) + result, err := CfcClient.PublishVersion(&api.PublishVersionArgs{ + FunctionName: FunctionName02, + Description: "test", + CodeSha256: CodeSha256, + }) + if logSuccess && err == nil { + t.Logf("res %v ", result) + } +} + +func TestListVersionsByFunction(t *testing.T) { + res, err := CfcClient.ListVersionsByFunction(&api.ListVersionsByFunctionArgs{ + FunctionName: "testHelloWorld", + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestCreateAlias(t *testing.T) { + cases := []api.CreateAliasArgs{ + { + FunctionName: FunctionName02, + FunctionVersion: "$LATEST", + Name: AliasName01, + Description: "test alias", + }, + { + FunctionName: FunctionName02, + FunctionVersion: "$LATEST", + Name: AliasName02, + Description: "test alias", + }, + } + for _, args := range cases { + res, err := CfcClient.CreateAlias(&args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } + } +} + +func TestGetAlias(t *testing.T) { + args := &api.GetAliasArgs{ + FunctionName: FunctionName02, + AliasName: AliasName01, + } + res, err := CfcClient.GetAlias(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestUpdateAlias(t *testing.T) { + args := &api.UpdateAliasArgs{ + FunctionName: FunctionName02, + AliasName: AliasName01, + FunctionVersion: "$LATEST", + Description: "test alias " + AliasName01, + } + res, err := CfcClient.UpdateAlias(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestListAliases(t *testing.T) { + args := &api.ListAliasesArgs{ + FunctionName: FunctionName02, + FunctionVersion: "$LATEST", + Marker: 0, + MaxItems: 2, + } + res, err := CfcClient.ListAliases(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestDeleteAlias(t *testing.T) { + args := &api.DeleteAliasArgs{ + FunctionName: FunctionName02, + AliasName: AliasName02, + } + err := CfcClient.DeleteAlias(args) + if err != nil { + t.Fatalf("err (%v)", err) + } +} + +func TestCreateTrigger(t *testing.T) { + cases := []api.CreateTriggerArgs{ + { + Target: FunctionBRN, + Source: api.SourceTypeHTTP, + Data: struct { + ResourcePath string + Method string + AuthType string + }{ + ResourcePath: fmt.Sprintf("tr01-%s", time.Now().Format("2006-01-02T150405")), + Method: "GET", + AuthType: "anonymous", + }, + }, { + Target: FunctionBRN, + Source: api.SourceTypeHTTP, + Data: struct { + ResourcePath string + Method string + AuthType string + }{ + ResourcePath: fmt.Sprintf("tr02-%s", time.Now().Format("2006-01-02T150405")), + Method: "GET", + AuthType: "anonymous", + }, + }, + } + for _, args := range cases { + res, err := CfcClient.CreateTrigger(&args) + if err != nil { + t.Fatalf("err (%v)", err) + } + + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } + } +} + +func TestListTriggers(t *testing.T) { + args := &api.ListTriggersArgs{ + FunctionBrn: FunctionBRN, + } + res, err := CfcClient.ListTriggers(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + if len(res.Relation) > 0 { + RelationId = res.Relation[0].RelationId + } + t.Logf("res %v", res) + resStr, err := json.Marshal(res) + if err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestUpdateTrigger(t *testing.T) { + args := &api.UpdateTriggerArgs{ + RelationId: RelationId, + Target: FunctionBRN, + Source: api.SourceTypeHTTP, + Data: struct { + ResourcePath string + Method string + AuthType string + }{ + ResourcePath: fmt.Sprintf("tr99-%s", time.Now().Format("2006-01-02T150405")), + Method: "GET", + AuthType: "anonymous", + }, + } + res, err := CfcClient.UpdateTrigger(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + if res.Relation != nil { + RelationId = res.Relation.RelationId + } + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestDeleteTrigger(t *testing.T) { + listArgs := &api.ListTriggersArgs{ + FunctionBrn: FunctionBRN, + } + res, err := CfcClient.ListTriggers(listArgs) + if err != nil { + t.Fatalf("err (%v)", err) + } + if len(res.Relation) > 0 { + RelationId = res.Relation[0].RelationId + } + args := &api.DeleteTriggerArgs{ + RelationId: RelationId, + Target: FunctionBRN, + Source: api.SourceTypeHTTP, + } + t.Logf("args (%+v)", args) + err = CfcClient.DeleteTrigger(args) + if err != nil { + t.Errorf("err (%v)", err) + } +} + +func TestDeleteFunction(t *testing.T) { + args := &api.DeleteFunctionArgs{ + FunctionName: FunctionName01, + } + err := CfcClient.DeleteFunction(args) + if err != nil { + t.Logf("res (%v)", err) + } +} + +// TODO test fail +func TestListEventSource(t *testing.T) { + FunctionBRN = "" + args := &api.ListEventSourceArgs{ + FunctionName: FunctionBRN, + Marker: 0, + MaxItems: 100, + } + res, err := CfcClient.ListEventSource(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) + resStr, err := json.Marshal(res) + if err == nil { + t.Logf("res %s ", resStr) + } +} + +// test pass +func TestGetEventSource(t *testing.T) { + args := &api.GetEventSourceArgs{ + UUID: "uuid", + } + res, err := CfcClient.GetEventSource(args) + if err != nil { + t.Logf("res (%v)", err) + } + t.Logf("res %+v", res) + resStr, err := json.Marshal(res) + if err == nil { + t.Logf("res %s ", resStr) + } +} + +// test pass +func TestCreateEventSource(t *testing.T) { + unEnabled := false + FunctionBRN = "" + args := &api.CreateEventSourceArgs{ + Enabled: &unEnabled, + BatchSize: 3, + Type: api.TypeEventSourceDatahubTopic, + FunctionName: FunctionBRN, + DatahubConfig: api.DatahubConfig{ + MetaHostEndpoint: "endpoint", + MetaHostPort: 2181, + ClusterName: "clusterName", + PipeName: "pipeName", + PipeletNum: 1, + StartPoint: -1, + AclName: "aclName", + AclPassword: "aclPassword", + }, + } + res, err := CfcClient.CreateEventSource(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} +func TestUpdateEventSource(t *testing.T) { + FunctionBRN = "" + unEnabled := false + args := &api.UpdateEventSourceArgs{ + UUID: "uuid", + FuncEventSource: api.FuncEventSource{ + Enabled: &unEnabled, + BatchSize: 3, + Type: api.TypeEventSourceDatahubTopic, + FunctionName: FunctionBRN, + DatahubConfig: api.DatahubConfig{ + MetaHostEndpoint: "10.155.195.11", + MetaHostPort: 2181, + ClusterName: "clusterName", + PipeName: "pipeName", + PipeletNum: 1, + StartPoint: -1, + AclName: "aclName", + AclPassword: "aclPassword", + }, + }, + } + res, err := CfcClient.UpdateEventSource(args) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) + resStr, err := json.MarshalIndent(res, "", " ") + if logSuccess && err == nil { + t.Logf("res %s ", resStr) + } +} + +func TestDeleteEventSource(t *testing.T) { + args := &api.DeleteEventSourceArgs{ + UUID: "uuid", + } + err := CfcClient.DeleteEventSource(args) + if err != nil { + t.Fatalf("err (%v)", err) + } +} + +func TestListFlow(t *testing.T) { + res, err := CfcClient.ListFlow() + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res.Flows[0]) +} + +func TestCreateFlow(t *testing.T) { + res, err := CfcClient.CreateFlow(&api.CreateUpdateFlowArgs{ + Name: "demo-x2", + Type: "FDL", + Definition: "name: demo\nstart: initData\nstates:\n - type: pass\n name: initData\n data:\n hello: world\n end: true", + Description: "ut test demo", + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestUpdateFlow(t *testing.T) { + res, err := CfcClient.UpdateFlow(&api.CreateUpdateFlowArgs{ + Name: "demo-x2", + Type: "FDL", + Definition: "name: demo\nstart: initData2\nstates:\n - type: pass\n name: initData2\n data:\n hello: world\n end: true", + Description: "ut test demo2", + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestDescribeFlow(t *testing.T) { + res, err := CfcClient.DescribeFlow("demo-x2") + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestDeleteFlow(t *testing.T) { + err := CfcClient.DeleteFlow("demo-x2") + if err != nil { + t.Fatalf("err (%v)", err) + } +} + +func TestStartExecution(t *testing.T) { + res, err := CfcClient.StartExecution(&api.StartExecutionArgs{ + FlowName: "demo-x2", + ExecutionName: "s3", + Input: "{\"fruits\":[\"apple\", \"banana\"]}", + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestStopExecution(t *testing.T) { + res, err := CfcClient.StopExecution("demo-x2", "s3") + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestDescribeExecution(t *testing.T) { + res, err := CfcClient.DescribeExecution("demo-x2", "s3") + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestListExecutions(t *testing.T) { + res, err := CfcClient.ListExecutions("demo-x2") + if err != nil { + t.Fatalf("err (%v)", err) + } + t.Logf("res %+v", res) +} + +func TestGetExecutionHistory(t *testing.T) { + res, err := CfcClient.GetExecutionHistory(&api.GetExecutionHistoryArgs{ + FlowName: "demo-x2", + ExecutionName: "s3", + Limit: 40, + }) + if err != nil { + t.Fatalf("err (%v)", err) + } + for _, e := range res.Events { + t.Logf("event %+v", e) + } +} diff --git a/bce-sdk-go/services/cfc/config.json b/bce-sdk-go/services/cfc/config.json new file mode 100644 index 0000000..7d314b8 --- /dev/null +++ b/bce-sdk-go/services/cfc/config.json @@ -0,0 +1,5 @@ +{ + "AK":"ak", + "SK":"sk", + "Endpoint":"endpoint" +} \ No newline at end of file diff --git a/bce-sdk-go/services/cfc/nodejs.zip b/bce-sdk-go/services/cfc/nodejs.zip new file mode 100644 index 0000000000000000000000000000000000000000..80145cd2d6647aa9e7dbd4e47a7ff0984fa4be14 GIT binary patch literal 610 zcmWIWW@Zs#-~d9N#qqujNPvSufFUz4CAC5?t2i`*hhf9~o6$e!-HiT~zH6TMT8$I> zTAJ5Rdz|q;>8TsEA@qWWuJ>8(i-8{J&TD(FWiS*fE}A4Xd9$f#&RdaHJ4G58)Rs&P zpEY^$;ssA8&YC=VeZaiz-^8ak0BC;%5C;HV#RYat0h+5g zfv$>=_jPpk5020WIcp2hSs)B@7DyX*&j=%H(~HODujB;LQhv73{dkp&ZHpb5PYaZV7k}2^H;rfG){P4J?*KZr>X1b z>*=ZE8Oq1OrdebCKFy@#o#&?u>=(g)u8+t zyn5PGM^97N&)3sa$1{|VgH5x>`hA*7$2-qY7uYX00NwllKgjKToF{eCfV>SrjBq=U zg91QaggMx2t;Pv`EzN7EJ}5+%qU21N-JI4o)OK{ks(1c6=10CFKD_K{tWk{%d1 Y7!g4i;LXYgvX2=EZvg3uKsg2m0L&Soga7~l literal 0 HcmV?d00001 diff --git a/bce-sdk-go/services/cfc/python.zip b/bce-sdk-go/services/cfc/python.zip new file mode 100644 index 0000000000000000000000000000000000000000..0ebb961918f8e6de974d278848201a2d13bda454 GIT binary patch literal 201 zcmWIWW@Zs#-~d8&020Z}OG&NJE2#AH^bJ1YamM?kr|u;WUGKBn7Xv-co!9nUt8sBl z(1W0WfB+4RQ)jdrPh>W+b_IERb$=3>ViQzc^hxy4=kB7QDLWV#85sWm5AbH^DBzfz o>kc&91Be5>8JR>F5Y{8hVLAxrm;i59HjrZ(fzS*{*Mc|<0Kx1oM*si- literal 0 HcmV?d00001 diff --git a/bce-sdk-go/services/cfs/cfs.go b/bce-sdk-go/services/cfs/cfs.go new file mode 100644 index 0000000..6614d91 --- /dev/null +++ b/bce-sdk-go/services/cfs/cfs.go @@ -0,0 +1,195 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// cfs.go - the Normal CFS APIs definition supported by the CFS service + +package cfs + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateFS - create a FS Instance +// +// PARAMS: +// - args: parameters to create FS +// +// RETURNS: +// - *CreateFSResult: the result of create fs, contains new FS Instance's ID +// - error: nil if ok otherwise the specific error +func (c *Client) CreateFS(args *CreateFSArgs) (*CreateFSResult, error) { + if args == nil || len(args.Name) == 0 { + return nil, fmt.Errorf("unset fs name") + } + + if len(args.Zone) == 0 { + return nil, fmt.Errorf("unset zone") + } + + result := &CreateFSResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getCFSUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateFS - update name of a FS Instance +// +// PARAMS: +// - args: parameters to create FS +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateFS(args *UpdateFSArgs) error { + if args == nil || len(args.FSName) == 0 { + return fmt.Errorf("unset fs name") + } + if len(args.FSID) == 0 { + return fmt.Errorf("unset fs id") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getFSInstanceUri(args.FSID)). + WithBody(args). + Do() + + return err +} + +// DescribeFS - describe all FS Instances +// +// PARAMS: +// - args: parameters describe all FS Instances +// +// RETURNS: +// - *DescribeFSResult: the result FS Instances's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeFS(args *DescribeFSArgs) (*DescribeFSResult, error) { + if args == nil { + args = &DescribeFSArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeFSResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getCFSUri()). + WithQueryParamFilter("fsId", args.FSID). + WithQueryParamFilter("userId", args.UserId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + err := request.Do() + return result, err +} + +// CreateMountTarget - create a mount target for FS Instances +// +// PARAMS: +// - args: parameters to create mount target +// +// RETURNS: +// - *CreateMountTargetResult: the result mount target's detail +// - error: nil if ok otherwise the specific error +func (c *Client) CreateMountTarget(args *CreateMountTargetArgs) (*CreateMountTargetResult, error) { + if args == nil || len(args.FSID) == 0 { + return nil, fmt.Errorf("unset fs id") + } + + if len(args.SubnetId) == 0 { + return nil, fmt.Errorf("unset subnetid") + } + + result := &CreateMountTargetResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getFSInstanceUri(args.FSID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DescribeMountTarget - describe all mount targets +// +// PARAMS: +// - args: parameters describe all mount targets +// +// RETURNS: +// - *DescribeMountTargetResult: the result Mount target's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeMountTarget(args *DescribeMountTargetArgs) (*DescribeMountTargetResult, error) { + if args == nil { + args = &DescribeMountTargetArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeMountTargetResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getFSInstanceUri(args.FSID)). + WithQueryParamFilter("mountId", args.MountID). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + err := request.Do() + return result, err +} + +// DropMountTarget - drop a MountTarget +// +// PARAMS: +// - args: parameters to drop mount target +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DropMountTarget(args *DropMountTargetArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getMountTargetUri(args.FSID, args.MountId)). + Do() +} + +// DropFS - drop a fs instance +// +// PARAMS: +// - args: parameters to drop fs +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DropFS(args *DropFSArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getFSInstanceUri(args.FSID)). + Do() +} diff --git a/bce-sdk-go/services/cfs/client.go b/bce-sdk-go/services/cfs/client.go new file mode 100644 index 0000000..c83ece8 --- /dev/null +++ b/bce-sdk-go/services/cfs/client.go @@ -0,0 +1,54 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Application LoadBalance service + +// Package cfs defines the Normal CFS services of BCE. The supported APIs are all defined in sub-package +package cfs + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + DEFAULT_SERVICE_DOMAIN = "cfs." + bce.DEFAULT_REGION + ".baidubce.com" + URI_PREFIX = bce.URI_PREFIX + "v1" + REQUEST_CFS_URL = "/cfs" +) + +// Client of CFS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getCFSUri() string { + return URI_PREFIX + REQUEST_CFS_URL +} + +func getFSInstanceUri(id string) string { + return URI_PREFIX + REQUEST_CFS_URL + "/" + id +} + +func getMountTargetUri(fs_id string, mount_id string) string { + return URI_PREFIX + REQUEST_CFS_URL + "/" + fs_id + "/" + mount_id +} diff --git a/bce-sdk-go/services/cfs/client_test.go b/bce-sdk-go/services/cfs/client_test.go new file mode 100644 index 0000000..eefc178 --- /dev/null +++ b/bce-sdk-go/services/cfs/client_test.go @@ -0,0 +1,188 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 cfs + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CFS_CLIENT *Client + CFS_ID string + MOUNT_ID string + USER_ID string + + // set these values before start test + VPC_TEST_ID = "" + SUBNET_TEST_ID = "" + TEST_PROTOCOL = "nfs" + TEST_ZONE = "zoneA" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + USER_ID string + VPC_ID string + SUBNET_ID string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CFS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + VPC_TEST_ID = confObj.VPC_ID + SUBNET_TEST_ID = confObj.SUBNET_ID + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateFS(t *testing.T) { + createArgs := &CreateFSArgs{ + ClientToken: getClientToken(), + Name: "sdkCFS", + VpcID: VPC_TEST_ID, + Protocol: TEST_PROTOCOL, + Zone: TEST_ZONE, + } + + createResult, err := CFS_CLIENT.CreateFS(createArgs) + ExpectEqual(t.Errorf, nil, err) + + CFS_ID = createResult.FSID +} + +func TestClient_UpdateFS(t *testing.T) { + updateArgs := &UpdateFSArgs{ + FSID: CFS_ID, + FSName: "sdCFS_newname", + } + + err := CFS_CLIENT.UpdateFS(updateArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DescribeFS(t *testing.T) { + describeArgs := &DescribeFSArgs{ + UserId: USER_ID, + } + res, err := CFS_CLIENT.DescribeFS(describeArgs) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(1) * time.Second) +} + +func TestClient_DescribeFSWithFS(t *testing.T) { + describeArgs := &DescribeFSArgs{ + FSID: CFS_ID, + } + res, err := CFS_CLIENT.DescribeFS(describeArgs) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(1) * time.Second) +} + +func TestClient_CreateMountTarget(t *testing.T) { + createArgs := &CreateMountTargetArgs{ + FSID: CFS_ID, + SubnetId: SUBNET_TEST_ID, + VpcID: VPC_TEST_ID, + } + + createResult, err := CFS_CLIENT.CreateMountTarget(createArgs) + fmt.Print(createResult) + ExpectEqual(t.Errorf, nil, err) + MOUNT_ID = createResult.MountID +} + +func TestClient_DescribeMountTarget(t *testing.T) { + describeArgs := &DescribeMountTargetArgs{ + FSID: CFS_ID, + } + res, err := CFS_CLIENT.DescribeMountTarget(describeArgs) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(1) * time.Second) +} + +func TestClient_DropMountTarget(t *testing.T) { + dropArgs := &DropMountTargetArgs{ + FSID: CFS_ID, + MountId: MOUNT_ID, + } + err := CFS_CLIENT.DropMountTarget(dropArgs) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(5) * time.Second) +} + +func TestClient_DropFS(t *testing.T) { + dropArgs := &DropFSArgs{ + FSID: CFS_ID, + } + err := CFS_CLIENT.DropFS(dropArgs) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(time.Duration(1) * time.Second) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/cfs/model.go b/bce-sdk-go/services/cfs/model.go new file mode 100644 index 0000000..56a7d33 --- /dev/null +++ b/bce-sdk-go/services/cfs/model.go @@ -0,0 +1,116 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package cfs + +import () + +type FSStatus string + +const ( + FSStatusAvailable FSStatus = "available" + FSStatusPaused FSStatus = "paused" + FSStatusUnavailable FSStatus = "unavailable" +) + +type CreateFSArgs struct { + ClientToken string `json:"-"` + Name string `json:"fsName"` + Type string `json:"type"` + Protocol string `json:"protocol"` + VpcID string `json:"vpcId,omitempty"` + Zone string `json:"zone,omitempty"` +} + +type CreateFSResult struct { + FSID string `json:"fsId"` +} + +type UpdateFSArgs struct { + FSID string `json:"-"` + FSName string `json:"fsName"` +} + +type DescribeResultMeta struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type DescribeFSArgs struct { + FSID string + UserId string + Marker string + MaxKeys int +} + +type MoutTargetModel struct { + AccessGroupName string `json:"accessGroupName"` + MountID string `json:"mountId"` + Ovip string `json:"ovip"` + Domain string `json:"domain"` + SubnetID string `json:"subnetId"` +} + +type FSModel struct { + FSID string `json:"fsId"` + Name string `json:"fsName"` + VpcID string `json:"vpcId"` + Type string `json:"type"` + Protocol string `json:"protocol"` + Usage string `json:"fsUsage"` + Status FSStatus `json:"status"` + MoutTargets []MoutTargetModel `json:"mountTargetList"` +} + +type DescribeFSResult struct { + FSList []FSModel `json:"fileSystemList"` + DescribeResultMeta +} + +type DropFSArgs struct { + FSID string +} + +type CreateMountTargetArgs struct { + FSID string `json:"-"` + VpcID string `json:"vpcId"` + SubnetId string `json:"subnetId"` + AccessGroupName string `json:"accessGroupName"` +} + +type CreateMountTargetResult struct { + MountID string `json:"mountId"` + Domain string `json:"domain"` +} + +type DropMountTargetArgs struct { + FSID string + MountId string +} + +type DescribeMountTargetArgs struct { + FSID string + MountID string + Marker string + MaxKeys int +} + +type DescribeMountTargetResult struct { + MountTargetList []MoutTargetModel `json:"mountTargetList"` + DescribeResultMeta +} diff --git a/bce-sdk-go/services/cfw/cfw.go b/bce-sdk-go/services/cfw/cfw.go new file mode 100644 index 0000000..68955d5 --- /dev/null +++ b/bce-sdk-go/services/cfw/cfw.go @@ -0,0 +1,489 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 cfw + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" + "strings" +) + +// BindCfw - 批量实例绑定CFW策略。 - 没有规则的CFW不能绑定到实例 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func BindCfw(cli *Client, cfwId string, body *BindCfwRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + req.SetParam("bind", "") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateCfw - 创建CFW策略。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - body: +// +// RETURNS: +// - *api.CreateCfwResponse: +// - error: the return error if any occurs +func CreateCfw(cli *Client, body *CreateCfwRequest) (*CreateCfwResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/cfw" + req.SetUri(path) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &CreateCfwResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// CreateCfwRule - 批量创建CFW中防护规则。 - 五元组(protocol/sourceAddress/destAddress/sourcePort/destPort) + 方向(direction)不能全部相同。 - 一次最多创建100条规则。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateCfwRule(cli *Client, cfwId string, body *CreateCfwRuleRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/cfw/[cfwId]/rule" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteCfw - 删除指定CFW策略。 - CFW存在绑定关系时不允许删除 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// +// RETURNS: +// - error: the return error if any occurs +func DeleteCfw(cli *Client, cfwId string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteCfwRule - 批量删除指定CFW中某些规则。 - CFW已绑定到实例时,至少保留一条规则。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeleteCfwRule(cli *Client, cfwId string, body *DeleteCfwRuleRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]/delete/rule" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DisableCfw - 已绑定CFW的实例,使用该接口临时关闭CFW的防护功能。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DisableCfw(cli *Client, cfwId string, body *DisableCfwRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + req.SetParam("off", "") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// EnableCfw - 已绑定CFW并且临时关闭了防护功能的实例,使用该接口恢复CFW的防护功能。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func EnableCfw(cli *Client, cfwId string, body *EnableCfwRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + req.SetParam("on", "") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// GetCfw - 查询指定CFW策略的详情信息。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// +// RETURNS: +// - *api.GetCfwResponse: +// - error: the return error if any occurs +func GetCfw(cli *Client, cfwId string) (*GetCfwResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &GetCfwResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListCfw - 查询CFW策略列表信息。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - listCfwArgs: +// +// RETURNS: +// - *api.ListCfwResponse: +// - error: the return error if any occurs +func ListCfw(cli *Client, listCfwArgs *ListCfwArgs) ( + *ListCfwResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/cfw" + req.SetUri(path) + if "" != listCfwArgs.Marker { + req.SetParam("marker", listCfwArgs.Marker) + } + if 0 != listCfwArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listCfwArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListCfwResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListInstance - 查询防护边界实例的列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - listInstanceRequest: +// +// RETURNS: +// - *api.ListInstanceResponse: +// - error: the return error if any occurs +func ListInstance(cli *Client, listInstanceRequest *ListInstanceRequest) (*ListInstanceResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/cfw/instance" + req.SetUri(path) + req.SetParam("instanceType", listInstanceRequest.InstanceType) + if "" != listInstanceRequest.Marker { + req.SetParam("marker", listInstanceRequest.Marker) + } + if 0 != listInstanceRequest.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listInstanceRequest.MaxKeys)) + } + if "" != listInstanceRequest.Status { + req.SetParam("status", listInstanceRequest.Status) + } + if "" != listInstanceRequest.Region { + req.SetParam("region", listInstanceRequest.Region) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListInstanceResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// UnbindCfw - 实例批量解绑CFW。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UnbindCfw(cli *Client, cfwId string, body *UnbindCfwRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + req.SetParam("unbind", "") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateCfw - 更新CFW策略的基本信息。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateCfw(cli *Client, cfwId string, body *UpdateCfwRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + req.SetUri(path) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateCfwRule - 修改指定CFW规则。 - 五元组(protocol/sourceAddress/destAddress/sourcePort/destPort) + 方向(direction)不能全部相同。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - cfwId: CFW策略的id +// - cfwRuleId: CFW规则的id +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateCfwRule(cli *Client, cfwId string, cfwRuleId string, + body *UpdateCfwRuleRequest) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/cfw/[cfwId]/rule/[cfwRuleId]" + path = strings.Replace(path, "[cfwId]", cfwId, -1) + path = strings.Replace(path, "[cfwRuleId]", cfwRuleId, -1) + req.SetUri(path) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/cfw/client.go b/bce-sdk-go/services/cfw/client.go new file mode 100644 index 0000000..1982dea --- /dev/null +++ b/bce-sdk-go/services/cfw/client.go @@ -0,0 +1,236 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 cfw + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "http://cfw.baidubce.com" + DEFAULT_MAX_PARALLEL = 10 + MULTIPART_ALIGN = 1 << 20 // 1MB + MIN_MULTIPART_SIZE = 1 << 20 // 1MB + DEFAULT_MULTIPART_SIZE = 12 * (1 << 20) // 12MB + MAX_PART_NUMBER = 10000 +) + +// Client of bcd service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient + + // Fileds that used in parallel operation for BOS service + MaxParallel int64 + MultipartSize int64 +} + +// NewClient make the bcd service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer), + DEFAULT_MAX_PARALLEL, DEFAULT_MULTIPART_SIZE} + return client, nil +} + +// BindCfw - 批量实例绑定CFW策略。 - 没有规则的CFW不能绑定到实例 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) BindCfw(cfwId string, body *BindCfwRequest) error { + return BindCfw(c, cfwId, body) +} + +// CreateCfw - 创建CFW策略。 +// +// PARAMS: +// - body: body参数 +// +// RETURNS: +// - *CreateCfwResponse: +// - error: the return error if any occurs +func (c *Client) CreateCfw(body *CreateCfwRequest) ( + *CreateCfwResponse, error) { + return CreateCfw(c, body) +} + +// CreateCfwRule - 批量创建CFW中防护规则。 - 五元组(protocol/sourceAddress/destAddress/sourcePort/destPort) + 方向(direction)不能全部相同。 - 一次最多创建100条规则。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateCfwRule(cfwId string, body *CreateCfwRuleRequest) error { + return CreateCfwRule(c, cfwId, body) +} + +// DeleteCfw - 删除指定CFW策略。 - CFW存在绑定关系时不允许删除 +// +// PARAMS: +// - cfwId: CFW的id +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteCfw(cfwId string) error { + return DeleteCfw(c, cfwId) +} + +// DeleteCfwRule - 批量删除指定CFW中某些规则。 - CFW已绑定到实例时,至少保留一条规则。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteCfwRule(cfwId string, body *DeleteCfwRuleRequest) error { + return DeleteCfwRule(c, cfwId, body) +} + +// DisableCfw - 已绑定CFW的实例,使用该接口临时关闭CFW的防护功能。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DisableCfw(cfwId string, body *DisableCfwRequest) error { + return DisableCfw(c, cfwId, body) +} + +// EnableCfw - 已绑定CFW并且临时关闭了防护功能的实例,使用该接口恢复CFW的防护功能。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) EnableCfw(cfwId string, body *EnableCfwRequest) error { + return EnableCfw(c, cfwId, body) +} + +// GetCfw - 查询指定CFW策略的详情信息。 +// +// PARAMS: +// - cfwId: CFW的id +// +// RETURNS: +// - *GetCfwResponse: +// - error: the return error if any occurs +func (c *Client) GetCfw(cfwId string) (*GetCfwResponse, error) { + return GetCfw(c, cfwId) +} + +// ListCfw - 查询CFW策略列表信息。 +// +// PARAMS: +// - marker: 批量获取列表查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListCfwResponse: +// - error: the return error if any occurs +func (c *Client) ListCfw(listCfwArgs *ListCfwArgs) ( + *ListCfwResponse, error) { + return ListCfw(c, listCfwArgs) +} + +// ListInstance - 查询防护边界实例的列表。 +// +// PARAMS: +// - instanceType: 实例类型,取值[ eip | nat | etGateway | peerconn | csn | ipv6Gateway ] +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000,缺省值为1000 +// - status: 防护状态,取值 [ unbound | protected | unprotected ] +// - region: 地域信息,取值 [ bj | gz | su | fsh | hkg | bd | fwh | sin ] +// - body: body参数 +// +// RETURNS: +// - *ListInstanceResponse: +// - error: the return error if any occurs +func (c *Client) ListInstance(body *ListInstanceRequest) (*ListInstanceResponse, error) { + return ListInstance(c, body) +} + +// UnbindCfw - 实例批量解绑CFW。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UnbindCfw(cfwId string, body *UnbindCfwRequest) error { + return UnbindCfw(c, cfwId, body) +} + +// UpdateCfw - 更新CFW策略的基本信息。 +// +// PARAMS: +// - cfwId: CFW的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateCfw(cfwId string, body *UpdateCfwRequest) error { + return UpdateCfw(c, cfwId, body) +} + +// UpdateCfwRule - 修改指定CFW规则。 - 五元组(protocol/sourceAddress/destAddress/sourcePort/destPort) + 方向(direction)不能全部相同。 +// +// PARAMS: +// - cfwId: CFW策略的id +// - cfwRuleId: CFW规则的id +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateCfwRule(cfwId string, cfwRuleId string, + body *UpdateCfwRuleRequest) error { + return UpdateCfwRule(c, cfwId, cfwRuleId, body) +} diff --git a/bce-sdk-go/services/cfw/client_test.go b/bce-sdk-go/services/cfw/client_test.go new file mode 100644 index 0000000..14fd3b4 --- /dev/null +++ b/bce-sdk-go/services/cfw/client_test.go @@ -0,0 +1,236 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 cfw + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + CfwClient *Client +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + CfwClient, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) +} + +func TestClient_CreateCfw(t *testing.T) { + args := &CreateCfwRequest{ + Name: "cfw_1", + Description: "desc", + CfwRules: []CreateRule{ + { + IpVersion: 4, + Priority: 4, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.4", + DestAddress: "192.168.0.5", + SourcePort: "80", + DestPort: "88", + Action: "allow", + }, + }, + } + result, err := CfwClient.CreateCfw(args) + ExpectEqual(t.Errorf, nil, err) + CfwId := result.CfwId + log.Debug(CfwId) +} + +func TestClient_ListCfw(t *testing.T) { + args := &ListCfwArgs{} + result, err := CfwClient.ListCfw(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_GetCfw(t *testing.T) { + result, err := CfwClient.GetCfw("cfw-xxxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_UpdateCfw(t *testing.T) { + args := &UpdateCfwRequest{ + Name: "cfw_2", + Description: "desc", + } + err := CfwClient.UpdateCfw("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCfw(t *testing.T) { + err := CfwClient.DeleteCfw("cfw-xxxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateCfwRule(t *testing.T) { + args := &CreateCfwRuleRequest{ + CfwRules: []CreateRule{ + { + IpVersion: 4, + Priority: 5, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.3", + DestAddress: "192.168.0.4", + SourcePort: "80", + DestPort: "88", + Action: "allow", + }, + }, + } + err := CfwClient.CreateCfwRule("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateCfwRule(t *testing.T) { + args := &UpdateCfwRuleRequest{ + IpVersion: 4, + Priority: 2, + Protocol: "TCP", + Direction: "in", + SourceAddress: "192.168.0.1", + DestAddress: "192.168.0.2", + SourcePort: "80", + DestPort: "88", + Action: "allow", + } + err := CfwClient.UpdateCfwRule("cfw-xxxxxxxxxxxx", "cfwr-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCfwRule(t *testing.T) { + args := &DeleteCfwRuleRequest{ + CfwRuleIds: []string{ + "cfwr-xxxxxxxxxxxx", + }, + } + err := CfwClient.DeleteCfwRule("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListInstance(t *testing.T) { + args := &ListInstanceRequest{ + InstanceType: "csn", + } + result, err := CfwClient.ListInstance(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_BindCfw(t *testing.T) { + args := &BindCfwRequest{ + InstanceType: "csn", + Instances: []CfwBind{ + { + Region: "bj", + InstanceId: "csn-xxxxxxxxxxxx", + MemberId: "vpc-xxxxxxxxxxxx", + }, + }, + } + err := CfwClient.BindCfw("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindCfw(t *testing.T) { + args := &UnbindCfwRequest{ + InstanceType: "csn", + Instances: []CfwBind{ + { + Region: "bj", + InstanceId: "csn-xxxxxxxxxxxx", + MemberId: "vpc-xxxxxxxxxxxx", + }, + }, + } + err := CfwClient.UnbindCfw("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EnableCfw(t *testing.T) { + args := &EnableCfwRequest{ + InstanceId: "csn-xxxxxxxxxxxx", + MemberId: "vpc-xxxxxxxxxxxx", + } + err := CfwClient.EnableCfw("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DisableCfw(t *testing.T) { + args := &DisableCfwRequest{ + InstanceId: "csn-xxxxxxxxxxxx", + MemberId: "vpc-xxxxxxxxxxxx", + } + err := CfwClient.DisableCfw("cfw-xxxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/cfw/model.go b/bce-sdk-go/services/cfw/model.go new file mode 100644 index 0000000..2471170 --- /dev/null +++ b/bce-sdk-go/services/cfw/model.go @@ -0,0 +1,203 @@ +/* + * Copyright Baidu, Inc. + * + * 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 cfw + +type BindCfwRequest struct { + InstanceType string `json:"instanceType"` + Instances []CfwBind `json:"instances"` +} + +type BindCfwRequestInstances struct { +} + +type Cfw struct { + CfwId string `json:"cfwId"` + Name string `json:"name"` + Description string `json:"description"` + CreatedTime string `json:"createdTime"` + BindInstanceNum int32 `json:"bindInstanceNum"` + CfwRules []CfwRule `json:"cfwRules"` +} + +type CfwBind struct { + Region string `json:"region"` + InstanceId string `json:"instanceId"` + Role string `json:"role"` + MemberId string `json:"memberId"` +} + +type CfwRule struct { + IpVersion int32 `json:"ipVersion"` + Priority int32 `json:"priority"` + Protocol string `json:"protocol"` + Direction string `json:"direction"` + SourceAddress string `json:"sourceAddress"` + DestAddress string `json:"destAddress"` + SourcePort string `json:"sourcePort"` + DestPort string `json:"destPort"` + Action string `json:"action"` + Description string `json:"description"` + CfwId string `json:"cfwId"` + CfwRuleId string `json:"cfwRuleId"` +} + +type CreateCfwRequest struct { + Name string `json:"name"` + Description string `json:"description"` + CfwRules []CreateRule `json:"cfwRules"` +} + +type CreateCfwRequestCfwRules struct { +} + +type CreateCfwResponse struct { + CfwId string `json:"cfwId"` +} + +type CreateCfwRuleRequest struct { + CfwRules []CreateRule `json:"cfwRules"` +} + +type CreateCfwRuleRequestCfwRules struct { +} + +type CreateRule struct { + IpVersion int32 `json:"ipVersion"` + Priority int32 `json:"priority"` + Protocol string `json:"protocol"` + Direction string `json:"direction"` + SourceAddress string `json:"sourceAddress"` + DestAddress string `json:"destAddress"` + SourcePort string `json:"sourcePort"` + DestPort string `json:"destPort"` + Action string `json:"action"` + Description string `json:"description"` +} + +type DeleteCfwRuleRequest struct { + CfwRuleIds []string `json:"cfwRuleIds"` +} + +type DeleteCfwRuleRequestCfwRuleIds struct { +} + +type DisableCfwRequest struct { + InstanceId string `json:"instanceId"` + Role string `json:"role"` + MemberId string `json:"memberId"` +} + +type EnableCfwRequest struct { + InstanceId string `json:"instanceId"` + Role string `json:"role"` + MemberId string `json:"memberId"` +} + +type GetCfwResponse struct { + CfwId string `json:"cfwId"` + Name string `json:"name"` + Description string `json:"description"` + CreatedTime string `json:"createdTime"` + BindInstanceNum int32 `json:"bindInstanceNum"` + CfwRules []CfwRule `json:"cfwRules"` +} + +type GetCfwResponseCfwRules struct { +} + +type Instance struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Status string `json:"status"` + Region string `json:"region"` + CfwId string `json:"cfwId"` + CfwName string `json:"cfwName"` + VpcId string `json:"vpcId"` + VpcName string `json:"vpcName"` + PublicIp string `json:"publicIp"` + Role string `json:"role"` + LocalIfId string `json:"localIfId"` + LocalIfName string `json:"localIfName"` + PeerRegion string `json:"peerRegion"` + PeerVpcId string `json:"peerVpcId"` + PeerVpcName string `json:"peerVpcName"` + MemberId string `json:"memberId"` + MemberName string `json:"memberName"` + MemberAccountId string `json:"memberAccountId"` +} + +type ListCfwResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int32 `json:"maxKeys"` + Cfws []Cfw `json:"cfws"` +} + +type ListCfwResponseCfws struct { +} + +type ListInstanceRequest struct { + InstanceType string `json:"instanceType"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + Status string `json:"status"` + Region string `json:"region"` +} + +type ListInstanceRequestCfwRuleIds struct { +} + +type ListInstanceResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []Instance `json:"instances"` +} + +type ListInstanceResponseInstances struct { +} + +type UnbindCfwRequest struct { + InstanceType string `json:"instanceType"` + Instances []CfwBind `json:"instances"` +} + +type UnbindCfwRequestInstances struct { +} + +type UpdateCfwRequest struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type UpdateCfwRuleRequest struct { + IpVersion int32 `json:"ipVersion"` + Priority int32 `json:"priority"` + Protocol string `json:"protocol"` + Direction string `json:"direction"` + SourceAddress string `json:"sourceAddress"` + DestAddress string `json:"destAddress"` + SourcePort string `json:"sourcePort"` + DestPort string `json:"destPort"` + Action string `json:"action"` + Description string `json:"description"` +} + +type ListCfwArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} diff --git a/bce-sdk-go/services/csn/client.go b/bce-sdk-go/services/csn/client.go new file mode 100644 index 0000000..7be44cb --- /dev/null +++ b/bce-sdk-go/services/csn/client.go @@ -0,0 +1,525 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 csn + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "http://csn.baidubce.com" + DEFAULT_MAX_PARALLEL = 10 + MULTIPART_ALIGN = 1 << 20 // 1MB + MIN_MULTIPART_SIZE = 1 << 20 // 1MB + DEFAULT_MULTIPART_SIZE = 12 * (1 << 20) // 12MB + MAX_PART_NUMBER = 10000 +) + +// Client of bcd service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient + + // Fileds that used in parallel operation for BOS service + MaxParallel int64 + MultipartSize int64 +} + +// NewClient make the bcd service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer), + DEFAULT_MAX_PARALLEL, DEFAULT_MULTIPART_SIZE} + return client, nil +} + +// AttachInstance - 将网络实例加载进云智能网。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) AttachInstance(csnId string, body *AttachInstanceRequest, clientToken string) error { + return AttachInstance(c, csnId, body, clientToken) +} + +// BindCsnBp - 带宽包绑定云智能网。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) BindCsnBp(csnBpId string, body *BindCsnBpRequest, clientToken string) error { + return BindCsnBp(c, csnBpId, body, clientToken) +} + +// CreateAssociation - 创建路由表的关联关系。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateAssociation(csnRtId string, body *CreateAssociationRequest, + clientToken string) error { + return CreateAssociation(c, csnRtId, body, clientToken) +} + +// CreateCsn - 创建云智能网。 +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - *CreateCsnResponse: +// - error: the return error if any occurs +func (c *Client) CreateCsn(body *CreateCsnRequest, clientToken string) ( + *CreateCsnResponse, error) { + return CreateCsn(c, body, clientToken) +} + +// CreateCsnBp - 创建云智能网共享带宽包。 +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - *CreateCsnBpResponse: +// - error: the return error if any occurs +func (c *Client) CreateCsnBp(body *CreateCsnBpRequest, clientToken string) ( + *CreateCsnBpResponse, error) { + return CreateCsnBp(c, body, clientToken) +} + +// CreateCsnBpLimit - 创建带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateCsnBpLimit(csnBpId string, body *CreateCsnBpLimitRequest, clientToken string) error { + return CreateCsnBpLimit(c, csnBpId, body, clientToken) +} + +// CreatePropagation - 创建路由表的学习关系。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreatePropagation(csnRtId string, body *CreatePropagationRequest, + clientToken string) error { + return CreatePropagation(c, csnRtId, body, clientToken) +} + +// CreateRouteRule - 添加云智能网路由表的路由条目。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateRouteRule(csnRtId string, body *CreateRouteRuleRequest, + clientToken string) error { + return CreateRouteRule(c, csnRtId, body, clientToken) +} + +// DeleteAssociation - 删除云智能网路由表的关联关系。 +// +// PARAMS: +// - csnRtId: 路由表的ID +// - attachId: 网络实例在云智能网中的身份ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteAssociation(csnRtId string, attachId string, clientToken string) error { + return DeleteAssociation(c, csnRtId, attachId, clientToken) +} + +// DeleteCsn - 删除云智能网。 已经加载了网络实例的云智能网不能直接删除,必须先卸载实例。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteCsn(csnId string, clientToken string) error { + return DeleteCsn(c, csnId, clientToken) +} + +// DeleteCsnBp - 删除带宽包。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteCsnBp(csnBpId string, clientToken string) error { + return DeleteCsnBp(c, csnBpId, clientToken) +} + +// DeleteCsnBpLimit - 删除带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteCsnBpLimit(csnBpId string, body *DeleteCsnBpLimitRequest, + clientToken string) error { + return DeleteCsnBpLimit(c, csnBpId, body, clientToken) +} + +// DeletePropagation - 删除云智能网路由表的学习关系。 +// +// PARAMS: +// - csnRtId: 路由表的ID +// - attachId: 网络实例在云智能网中的身份ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeletePropagation(csnRtId string, attachId string, clientToken string) error { + return DeletePropagation(c, csnRtId, attachId, clientToken) +} + +// DeleteRouteRule - 删除云智能网路由表的指定路由条目。 +// +// PARAMS: +// - csnRtId: 路由表的ID +// - csnRtRuleId: 路由条目的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteRouteRule(csnRtId string, csnRtRuleId string, clientToken string) error { + return DeleteRouteRule(c, csnRtId, csnRtRuleId, clientToken) +} + +// DetachInstance - 从云智能网中移出指定的网络实例。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DetachInstance(csnId string, body *DetachInstanceRequest, clientToken string) error { + return DetachInstance(c, csnId, body, clientToken) +} + +// GetCsn - 查询云智能网详情。 +// +// PARAMS: +// - csnId: csnId +// +// RETURNS: +// - *api.GetCsnResponse: +// - error: the return error if any occurs +func (c *Client) GetCsn(csnId string) (*GetCsnResponse, error) { + return GetCsn(c, csnId) +} + +// GetCsnBp - 查询指定云智能网带宽包详情。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// +// RETURNS: +// - *GetCsnBpResponse: +// - error: the return error if any occurs +func (c *Client) GetCsnBp(csnBpId string) (*GetCsnBpResponse, error) { + return GetCsnBp(c, csnBpId) +} + +// ListAssociation - 查询指定云智能网路由表的关联关系。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// +// RETURNS: +// - *ListAssociationResponse: +// - error: the return error if any occurs +func (c *Client) ListAssociation(csnRtId string) (*ListAssociationResponse, error) { + return ListAssociation(c, csnRtId) +} + +// ListCsn - 查询云智能网列表。 +// +// PARAMS: +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListCsnResponse: +// - error: the return error if any occurs +func (c *Client) ListCsn(listCsnArgs *ListCsnArgs) (*ListCsnResponse, error) { + return ListCsn(c, listCsnArgs) +} + +// ListCsnBp - 查询云智能网带宽包列表。 +// +// PARAMS: +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListCsnBpResponse: +// - error: the return error if any occurs +func (c *Client) ListCsnBp(listCsnBpArgs *ListCsnBpArgs) (*ListCsnBpResponse, error) { + return ListCsnBp(c, listCsnBpArgs) +} + +// ListCsnBpLimit - 查询带宽包的地域带宽列表。 +// +// PARAMS: +// - csnBpId: +// +// RETURNS: +// - *ListCsnBpLimitResponse: +// - error: the return error if any occurs +func (c *Client) ListCsnBpLimit(csnBpId string) (*ListCsnBpLimitResponse, error) { + return ListCsnBpLimit(c, csnBpId) +} + +// ListCsnBpLimitByCsnId - 查询云智能网的地域带宽列表。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - body: body参数 +// +// RETURNS: +// - *ListCsnBpLimitByCsnIdResponse: +// - error: the return error if any occurs +func (c *Client) ListCsnBpLimitByCsnId(csnId string) ( + *ListCsnBpLimitByCsnIdResponse, error) { + return ListCsnBpLimitByCsnId(c, csnId) +} + +// ListInstance - 查询指定云智能网下加载的网络实例信息。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListInstanceResponse: +// - error: the return error if any occurs +func (c *Client) ListInstance(csnId string, listInstanceArgs *ListInstanceArgs) ( + *ListInstanceResponse, error) { + return ListInstance(c, csnId, listInstanceArgs) +} + +// ListPropagation - 查询指定云智能网路由表的学习关系。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// +// RETURNS: +// - *ListPropagationResponse: +// - error: the return error if any occurs +func (c *Client) ListPropagation(csnRtId string) (*ListPropagationResponse, error) { + return ListPropagation(c, csnRtId) +} + +// ListRouteRule - 查询指定云智能网路由表的路由条目。 +// +// PARAMS: +// - csnRtId: 云智能网路由表的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000。缺省值为1000 +// +// RETURNS: +// - *ListRouteRuleResponse: +// - error: the return error if any occurs +func (c *Client) ListRouteRule(csnRtId string, listRouteRuleArgs *ListRouteRuleArgs) ( + *ListRouteRuleResponse, error) { + return ListRouteRule(c, csnRtId, listRouteRuleArgs) +} + +// ListRouteTable - 查询云智能网的路由表列表。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListRouteTableResponse: +// - error: the return error if any occurs +func (c *Client) ListRouteTable(csnId string, listRouteTableArgs *ListRouteTableArgs) ( + *ListRouteTableResponse, error) { + return ListRouteTable(c, csnId, listRouteTableArgs) +} + +// ListTgw - 查询云智能网TGW列表。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListTgwResponse: +// - error: the return error if any occurs +func (c *Client) ListTgw(csnId string, listTgwArgs *ListTgwArgs) ( + *ListTgwResponse, error) { + return ListTgw(c, csnId, listTgwArgs) +} + +// ListTgwRule - 查询指定TGW的路由条目。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - tgwId: TGW的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *ListTgwRuleResponse: +// - error: the return error if any occurs +func (c *Client) ListTgwRule(csnId string, tgwId string, listTgwRuleArgs *ListTgwRuleArgs, +) (*ListTgwRuleResponse, error) { + return ListTgwRule(c, csnId, tgwId, listTgwRuleArgs) +} + +// ResizeCsnBp - 带宽包的带宽升降级。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) ResizeCsnBp(csnBpId string, body *ResizeCsnBpRequest, clientToken string) error { + return ResizeCsnBp(c, csnBpId, body, clientToken) +} + +// UnbindCsnBp - 带宽包解绑云智能网。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UnbindCsnBp(csnBpId string, body *UnbindCsnBpRequest, clientToken string) error { + return UnbindCsnBp(c, csnBpId, body, clientToken) +} + +// UpdateCsn - 更新云智能网。 更新云智能网的名称和描述。 +// +// PARAMS: +// - csnId: 云智能网ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateCsn(csnId string, body *UpdateCsnRequest, clientToken string) error { + return UpdateCsn(c, csnId, body, clientToken) +} + +// UpdateCsnBp - 更新带宽包的名称信息。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateCsnBp(csnBpId string, body *UpdateCsnBpRequest, clientToken string) error { + return UpdateCsnBp(c, csnBpId, body, clientToken) +} + +// UpdateCsnBpLimit - 更新带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateCsnBpLimit(csnBpId string, body *UpdateCsnBpLimitRequest, + clientToken string) error { + return UpdateCsnBpLimit(c, csnBpId, body, clientToken) +} + +// UpdateTgw - 更新TGW的名称、描述。 +// +// PARAMS: +// - csnId: 云智能网的ID +// - tgwId: TGW实例的ID +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateTgw(csnId string, tgwId string, body *UpdateTgwRequest, + clientToken string) error { + return UpdateTgw(c, csnId, tgwId, body, clientToken) +} diff --git a/bce-sdk-go/services/csn/client_test.go b/bce-sdk-go/services/csn/client_test.go new file mode 100644 index 0000000..373278e --- /dev/null +++ b/bce-sdk-go/services/csn/client_test.go @@ -0,0 +1,374 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 csn + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + CsnClient *Client +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + CsnClient, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) +} + +func TestClient_CreateCsn(t *testing.T) { + desc := "desc" + args := &CreateCsnRequest{ + Name: "csn_api_1", + Description: &desc, + } + + result, err := CsnClient.CreateCsn(args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) + CsnId := result.CsnId + log.Debug(CsnId) +} + +func TestClient_UpdateCsn(t *testing.T) { + name := "csn_api_2" + args := &UpdateCsnRequest{ + Name: &name, + } + + err := CsnClient.UpdateCsn("csn-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCsn(t *testing.T) { + err := CsnClient.DeleteCsn("csn-xxxxxxxxxxx", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListCsn(t *testing.T) { + args := &ListCsnArgs{} + result, err := CsnClient.ListCsn(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_GetCsn(t *testing.T) { + result, err := CsnClient.GetCsn("csn-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_ListInstance(t *testing.T) { + args := &ListInstanceArgs{} + result, err := CsnClient.ListInstance("csn-xxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_AttachInstance(t *testing.T) { + args := &AttachInstanceRequest{ + InstanceId: "vpc-xxxxxxxxxxx", + InstanceType: "vpc", + InstanceRegion: "gz", + } + err := CsnClient.AttachInstance("csn-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DetachInstance(t *testing.T) { + args := &DetachInstanceRequest{ + InstanceId: "vpc-xxxxxxxxxxx", + InstanceType: "vpc", + InstanceRegion: "gz", + } + err := CsnClient.DetachInstance("csn-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListRouteTable(t *testing.T) { + args := &ListRouteTableArgs{} + result, err := CsnClient.ListRouteTable("csn-xxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_CreateRouteRule(t *testing.T) { + args := &CreateRouteRuleRequest{ + AttachId: "tgwAttach-xxxxxxxxxxx", + DestAddress: "0.0.0.0/0", + RouteType: "custom", + } + err := CsnClient.CreateRouteRule("csnRt-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListRouteRuleArgs(t *testing.T) { + args := &ListRouteRuleArgs{} + result, err := CsnClient.ListRouteRule("csnRt-xxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_DeleteRouteRule(t *testing.T) { + err := CsnClient.DeleteRouteRule("csnRt-xxxxxxxxxxx", + "xxxxxxxxxxx-32ac-4949-a3ca-xxxxxxxxxxx", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreatePropagation(t *testing.T) { + desc := "desc" + args := &CreatePropagationRequest{ + AttachId: "tgwAttach-xxxxxxxxxxx", + Description: &desc, + } + err := CsnClient.CreatePropagation("csnRt-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListPropagation(t *testing.T) { + result, err := CsnClient.ListPropagation("csnRt-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_DeletePropagation(t *testing.T) { + err := CsnClient.DeletePropagation("csnRt-xxxxxxxxxxx", + "tgwAttach-xxxxxxxxxxx", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAssociation(t *testing.T) { + desc := "desc" + args := &CreateAssociationRequest{ + AttachId: "tgwAttach-xxxxxxxxxxx", + Description: &desc, + } + err := CsnClient.CreateAssociation("csnRt-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListAssociation(t *testing.T) { + result, err := CsnClient.ListAssociation("csnRt-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_DeleteAssociation(t *testing.T) { + err := CsnClient.DeleteAssociation("csnRt-xxxxxxxxxxx", + "tgwAttach-xxxxxxxxxxx", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateCsnBp(t *testing.T) { + instanceType := "center" + args := &CreateCsnBpRequest{ + Name: "csnBp_api_1", + Bandwidth: 100, + InterworkType: &instanceType, + GeographicA: "China", + GeographicB: "China", + Billing: Billing{ + PaymentTiming: "Postpaid", + }, + } + result, err := CsnClient.CreateCsnBp(args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_UpdateCsnBp(t *testing.T) { + args := &UpdateCsnBpRequest{ + Name: "csnBp_api_2", + } + err := CsnClient.UpdateCsnBp("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteCsnBp(t *testing.T) { + err := CsnClient.DeleteCsnBp("csnBp-xxxxxxxxxxx", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListCsnBp(t *testing.T) { + args := &ListCsnBpArgs{} + result, err := CsnClient.ListCsnBp(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_GetCsnBp(t *testing.T) { + result, err := CsnClient.GetCsnBp("csnBp-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_ResizeCsnBp(t *testing.T) { + args := &ResizeCsnBpRequest{ + Bandwidth: 50, + } + err := CsnClient.ResizeCsnBp("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindCsnBp(t *testing.T) { + args := &BindCsnBpRequest{ + CsnId: "csn-xxxxxxxxxxx", + } + err := CsnClient.BindCsnBp("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindCsnBpRequest(t *testing.T) { + args := &UnbindCsnBpRequest{ + CsnId: "csn-xxxxxxxxxxx", + } + err := CsnClient.UnbindCsnBp("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateCsnBpLimit(t *testing.T) { + args := &CreateCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "gz", + Bandwidth: 10, + } + err := CsnClient.CreateCsnBpLimit("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListCsnBpLimit(t *testing.T) { + result, err := CsnClient.ListCsnBpLimit("csnBp-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_DeleteCsnBpLimit(t *testing.T) { + args := &DeleteCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "gz", + } + err := CsnClient.DeleteCsnBpLimit("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateCsnBpLimit(t *testing.T) { + args := &UpdateCsnBpLimitRequest{ + LocalRegion: "bj", + PeerRegion: "gz", + Bandwidth: 20, + } + err := CsnClient.UpdateCsnBpLimit("csnBp-xxxxxxxxxxx", args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListCsnBpLimitByCsnId(t *testing.T) { + result, err := CsnClient.ListCsnBpLimitByCsnId("csn-xxxxxxxxxxx") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_ListTgw(t *testing.T) { + args := &ListTgwArgs{} + result, err := CsnClient.ListTgw("csn-xxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func TestClient_UpdateTgw(t *testing.T) { + name := "tgw_1" + desc := "desc" + args := &UpdateTgwRequest{ + Name: &name, + Description: &desc, + } + err := CsnClient.UpdateTgw("csn-xxxxxxxxxxx", "tgw-xxxxxxxxxxx", + args, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListTgwRuleArgs(t *testing.T) { + args := &ListTgwRuleArgs{} + result, err := CsnClient.ListTgwRule("csn-xxxxxxxxxxx", "tgw-xxxxxxxxxxx", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/csn/csn.go b/bce-sdk-go/services/csn/csn.go new file mode 100644 index 0000000..3842dc6 --- /dev/null +++ b/bce-sdk-go/services/csn/csn.go @@ -0,0 +1,1258 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 csn + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" + "strings" +) + +// AttachInstance - 将网络实例加载进云智能网。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func AttachInstance(cli bce.Client, csnId string, body *AttachInstanceRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/[csnId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + req.SetParam("attach", "") + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// BindCsnBp - 带宽包绑定云智能网。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func BindCsnBp(cli bce.Client, csnBpId string, body *BindCsnBpRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetParam("bind", "") + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateAssociation - 创建路由表的关联关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateAssociation(cli bce.Client, csnRtId string, body *CreateAssociationRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/routeTable/[csnRtId]/association" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateCsn - 创建云智能网。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - *api.CreateCsnResponse: +// - error: the return error if any occurs +func CreateCsn(cli bce.Client, body *CreateCsnRequest, clientToken string) ( + *CreateCsnResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &CreateCsnResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// CreateCsnBp - 创建云智能网共享带宽包。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - *api.CreateCsnBpResponse: +// - error: the return error if any occurs +func CreateCsnBp(cli bce.Client, body *CreateCsnBpRequest, clientToken string) ( + *CreateCsnBpResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/bp" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &CreateCsnBpResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// CreateCsnBpLimit - 创建带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateCsnBpLimit(cli bce.Client, csnBpId string, body *CreateCsnBpLimitRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/bp/[csnBpId]/limit" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreatePropagation - 创建路由表的学习关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreatePropagation(cli bce.Client, csnRtId string, body *CreatePropagationRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/routeTable/[csnRtId]/propagation" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateRouteRule - 添加云智能网路由表的路由条目。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateRouteRule(cli bce.Client, csnRtId string, body *CreateRouteRuleRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/routeTable/[csnRtId]/rule" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteAssociation - 删除云智能网路由表的关联关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 路由表的ID +// - attachId: 网络实例在云智能网中的身份ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func DeleteAssociation(cli bce.Client, csnRtId string, attachId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/csn/routeTable/[csnRtId]/association/[attachId]" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + path = strings.Replace(path, "[attachId]", attachId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteCsn - 删除云智能网。 已经加载了网络实例的云智能网不能直接删除,必须先卸载实例。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func DeleteCsn(cli bce.Client, csnId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/csn/[csnId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteCsnBp - 删除带宽包。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func DeleteCsnBp(cli bce.Client, csnBpId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteCsnBpLimit - 删除带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeleteCsnBpLimit(cli bce.Client, csnBpId string, body *DeleteCsnBpLimitRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/csn/bp/[csnBpId]/limit/delete" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeletePropagation - 删除云智能网路由表的学习关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 路由表的ID +// - attachId: 网络实例在云智能网中的身份ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func DeletePropagation(cli bce.Client, csnRtId string, attachId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/csn/routeTable/[csnRtId]/propagation/[attachId]" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + path = strings.Replace(path, "[attachId]", attachId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteRouteRule - 删除云智能网路由表的指定路由条目。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 路由表的ID +// - csnRtRuleId: 路由条目的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// +// RETURNS: +// - error: the return error if any occurs +func DeleteRouteRule(cli bce.Client, csnRtId string, csnRtRuleId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/csn/routeTable/[csnRtId]/rule/[csnRtRuleId]" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + path = strings.Replace(path, "[csnRtRuleId]", csnRtRuleId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DetachInstance - 从云智能网中移出指定的网络实例。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DetachInstance(cli bce.Client, csnId string, body *DetachInstanceRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/[csnId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + req.SetParam("detach", "") + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// GetCsn - 查询云智能网详情。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: csnId +// +// RETURNS: +// - *api.GetCsnResponse: +// - error: the return error if any occurs +func GetCsn(cli bce.Client, csnId string) (*GetCsnResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &GetCsnResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// GetCsnBp - 查询指定云智能网带宽包详情。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// +// RETURNS: +// - *api.GetCsnBpResponse: +// - error: the return error if any occurs +func GetCsnBp(cli bce.Client, csnBpId string) (*GetCsnBpResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &GetCsnBpResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListAssociation - 查询指定云智能网路由表的关联关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// +// RETURNS: +// - *api.ListAssociationResponse: +// - error: the return error if any occurs +func ListAssociation(cli bce.Client, csnRtId string) (*ListAssociationResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/routeTable/[csnRtId]/association" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListAssociationResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListCsn - 查询云智能网列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *api.ListCsnResponse: +// - error: the return error if any occurs +func ListCsn(cli bce.Client, listCsnArgs *ListCsnArgs) (*ListCsnResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn" + req.SetUri(path) + if "" != listCsnArgs.Marker { + req.SetParam("marker", listCsnArgs.Marker) + } + if 0 != listCsnArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listCsnArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListCsnResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListCsnBp - 查询云智能网带宽包列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *api.ListCsnBpResponse: +// - error: the return error if any occurs +func ListCsnBp(cli bce.Client, listCsnBpArgs *ListCsnBpArgs) (*ListCsnBpResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/bp" + req.SetUri(path) + if "" != listCsnBpArgs.Marker { + req.SetParam("marker", listCsnBpArgs.Marker) + } + if 0 != listCsnBpArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listCsnBpArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListCsnBpResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListCsnBpLimit - 查询带宽包的地域带宽列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: +// +// RETURNS: +// - *api.ListCsnBpLimitResponse: +// - error: the return error if any occurs +func ListCsnBpLimit(cli bce.Client, csnBpId string) (*ListCsnBpLimitResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/bp/[csnBpId]/limit" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListCsnBpLimitResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListCsnBpLimitByCsnId - 查询云智能网的地域带宽列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - body: +// +// RETURNS: +// - *api.ListCsnBpLimitByCsnIdResponse: +// - error: the return error if any occurs +func ListCsnBpLimitByCsnId(cli bce.Client, csnId string) ( + *ListCsnBpLimitByCsnIdResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]/bp/limit" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListCsnBpLimitByCsnIdResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListInstance - 查询指定云智能网下加载的网络实例信息。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *api.ListInstanceResponse: +// - error: the return error if any occurs +func ListInstance(cli bce.Client, csnId string, listInstanceArgs *ListInstanceArgs) ( + *ListInstanceResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]/instance" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + if "" != listInstanceArgs.Marker { + req.SetParam("marker", listInstanceArgs.Marker) + } + if 0 != listInstanceArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listInstanceArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListInstanceResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListPropagation - 查询指定云智能网路由表的学习关系。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// +// RETURNS: +// - *api.ListPropagationResponse: +// - error: the return error if any occurs +func ListPropagation(cli bce.Client, csnRtId string) (*ListPropagationResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/routeTable/[csnRtId]/propagation" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListPropagationResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListRouteRule - 查询指定云智能网路由表的路由条目。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnRtId: 云智能网路由表的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000。缺省值为1000 +// +// RETURNS: +// - *api.ListRouteRuleResponse: +// - error: the return error if any occurs +func ListRouteRule(cli bce.Client, csnRtId string, listRouteRuleArgs *ListRouteRuleArgs) ( + *ListRouteRuleResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/routeTable/[csnRtId]/rule" + path = strings.Replace(path, "[csnRtId]", csnRtId, -1) + req.SetUri(path) + if "" != listRouteRuleArgs.Marker { + req.SetParam("marker", listRouteRuleArgs.Marker) + } + if 0 != listRouteRuleArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listRouteRuleArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListRouteRuleResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListRouteTable - 查询云智能网的路由表列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *api.ListRouteTableResponse: +// - error: the return error if any occurs +func ListRouteTable(cli bce.Client, csnId string, listRouteTableArgs *ListRouteTableArgs) ( + *ListRouteTableResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]/routeTable" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + if "" != listRouteTableArgs.Marker { + req.SetParam("marker", listRouteTableArgs.Marker) + } + if 0 != listRouteTableArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listRouteTableArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListRouteTableResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListTgw - 查询云智能网TGW列表。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// +// RETURNS: +// - *api.ListTgwResponse: +// - error: the return error if any occurs +func ListTgw(cli bce.Client, csnId string, listTgwArgs *ListTgwArgs) (*ListTgwResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]/tgw" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + if "" != listTgwArgs.Marker { + req.SetParam("marker", listTgwArgs.Marker) + } + if 0 != listTgwArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listTgwArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListTgwResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListTgwRule - 查询指定TGW的路由条目。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - tgwId: TGW的ID +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量不超过1000,缺省值为1000 +// - body: +// +// RETURNS: +// - *api.ListTgwRuleResponse: +// - error: the return error if any occurs +func ListTgwRule(cli bce.Client, csnId string, tgwId string, listTgwRuleArgs *ListTgwRuleArgs, +) (*ListTgwRuleResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/csn/[csnId]/tgw/[tgwId]/rule" + path = strings.Replace(path, "[csnId]", csnId, -1) + path = strings.Replace(path, "[tgwId]", tgwId, -1) + req.SetUri(path) + if "" != listTgwRuleArgs.Marker { + req.SetParam("marker", listTgwRuleArgs.Marker) + } + if 0 != listTgwRuleArgs.MaxKeys { + req.SetParam("maxKeys", strconv.Itoa(listTgwRuleArgs.MaxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListTgwRuleResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ResizeCsnBp - 带宽包的带宽升降级。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func ResizeCsnBp(cli bce.Client, csnBpId string, body *ResizeCsnBpRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetParam("resize", "") + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UnbindCsnBp - 带宽包解绑云智能网。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UnbindCsnBp(cli bce.Client, csnBpId string, body *UnbindCsnBpRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetParam("unbind", "") + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateCsn - 更新云智能网。 更新云智能网的名称和描述。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串,详见ClientToken幂等性 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateCsn(cli bce.Client, csnId string, body *UpdateCsnRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/[csnId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateCsnBp - 更新带宽包的名称信息。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateCsnBp(cli bce.Client, csnBpId string, body *UpdateCsnBpRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/bp/[csnBpId]" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateCsnBpLimit - 更新带宽包中两个地域间的地域带宽。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnBpId: 带宽包的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateCsnBpLimit(cli bce.Client, csnBpId string, body *UpdateCsnBpLimitRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/bp/[csnBpId]/limit" + path = strings.Replace(path, "[csnBpId]", csnBpId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateTgw - 更新TGW的名称、描述。 +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - csnId: 云智能网的ID +// - tgwId: TGW实例的ID +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateTgw(cli bce.Client, csnId string, tgwId string, body *UpdateTgwRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/csn/[csnId]/tgw/[tgwId]" + path = strings.Replace(path, "[csnId]", csnId, -1) + path = strings.Replace(path, "[tgwId]", tgwId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetHeader("Content-Type", "application/json;charset=UTF-8") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/csn/model.go b/bce-sdk-go/services/csn/model.go new file mode 100644 index 0000000..c3423d4 --- /dev/null +++ b/bce-sdk-go/services/csn/model.go @@ -0,0 +1,397 @@ +/* + * Copyright Baidu, Inc. + * + * 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 csn + +type AttachInstanceRequest struct { + InstanceType string `json:"instanceType"` + InstanceId string `json:"instanceId"` + InstanceRegion string `json:"instanceRegion"` + InstanceAccountId *string `json:"instanceAccountId,omitempty"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type BindCsnBpRequest struct { + CsnId string `json:"csnId"` +} + +type CreateAssociationRequest struct { + AttachId string `json:"attachId"` + Description *string `json:"description,omitempty"` +} + +type CreateCsnBpLimitRequest struct { + LocalRegion string `json:"localRegion"` + PeerRegion string `json:"peerRegion"` + Bandwidth int32 `json:"bandwidth"` +} + +type CreateCsnBpRequest struct { + Name string `json:"name"` + InterworkType *string `json:"interworkType,omitempty"` + Bandwidth int32 `json:"bandwidth"` + GeographicA string `json:"geographicA"` + GeographicB string `json:"geographicB"` + Billing Billing `json:"billing"` +} + +type CreateCsnBpResponse struct { + CsnBpId string `json:"csnBpId"` +} + +type CreateCsnRequest struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` +} + +type CreateCsnResponse struct { + CsnId string `json:"csnId"` +} + +type CreatePropagationRequest struct { + AttachId string `json:"attachId"` + Description *string `json:"description,omitempty"` +} + +type CreateRouteRuleRequest struct { + AttachId string `json:"attachId"` + DestAddress string `json:"destAddress"` + RouteType string `json:"routeType"` +} + +type Csn struct { + CsnId string `json:"csnId"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` + InstanceNum int32 `json:"instanceNum"` + CsnBpNum int32 `json:"csnBpNum"` +} + +type CsnBp struct { + CsnBpId string `json:"csnBpId"` + Name string `json:"name"` + Bandwidth int32 `json:"bandwidth"` + UsedBandwidth int32 `json:"usedBandwidth"` + CsnId string `json:"csnId"` + InterworkType string `json:"interworkType"` + InterworkRegion string `json:"interworkRegion"` + Status string `json:"status"` + PaymentTiming string `json:"paymentTiming"` + ExpireTime string `json:"expireTime"` + CreatedTime string `json:"createdTime"` +} + +type CsnBpLimit struct { + CsnBpId string `json:"csnBpId"` + CsnId string `json:"csnId"` + LocalRegion string `json:"localRegion"` + PeerRegion string `json:"peerRegion"` + Bandwidth int32 `json:"bandwidth"` +} + +type CsnRouteTable struct { + CsnRtId string `json:"csnRtId"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` +} + +type CsnRtAssociation struct { + AttachId string `json:"attachId"` + Description string `json:"description"` + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + InstanceRegion string `json:"instanceRegion"` + InstanceType string `json:"instanceType"` + Status string `json:"status"` +} + +type CsnRtPropagation struct { + AttachId string `json:"attachId"` + Description string `json:"description"` + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + InstanceRegion string `json:"instanceRegion"` + InstanceType string `json:"instanceType"` + Status string `json:"status"` +} + +type CsnRtRule struct { + RuleId string `json:"ruleId"` + RouteType string `json:"routeType"` + CsnId string `json:"csnId"` + CsnRtId string `json:"csnRtId"` + Description string `json:"description"` + FromAttachId string `json:"fromAttachId"` + Status string `json:"status"` + SourceAddress string `json:"sourceAddress"` + DestAddress string `json:"destAddress"` + NextHopId string `json:"nextHopId"` + NextHopName string `json:"nextHopName"` + NextHopRegion string `json:"nextHopRegion"` + NextHopType string `json:"nextHopType"` + AsPath string `json:"asPath"` + Community string `json:"community"` + BlackHole bool `json:"blackHole"` +} + +type DeleteCsnBpLimitRequest struct { + LocalRegion string `json:"localRegion"` + PeerRegion string `json:"peerRegion"` +} + +type DetachInstanceRequest struct { + InstanceType string `json:"instanceType"` + InstanceId string `json:"instanceId"` + InstanceRegion string `json:"instanceRegion"` + InstanceAccountId *string `json:"instanceAccountId,omitempty"` +} + +type GetCsnResponse struct { + Name string `json:"name"` + Description string `json:"description"` + CsnId string `json:"csnId"` + Status string `json:"status"` + InstanceNum int32 `json:"instanceNum"` + CsnBpNum int32 `json:"csnBpNum"` +} + +type GetCsnBpResponse struct { + CsnBpId string `json:"csnBpId"` + Name string `json:"name"` + Bandwidth int32 `json:"bandwidth"` + UsedBandwidth int32 `json:"usedBandwidth"` + CsnId string `json:"csnId"` + InterworkType string `json:"interworkType"` + InterworkRegion string `json:"interworkRegion"` + Status string `json:"status"` + PaymentTiming string `json:"paymentTiming"` + ExpireTime string `json:"expireTime"` + CreatedTime string `json:"createdTime"` +} + +type Instance struct { + AttachId string `json:"attachId"` + InstanceType string `json:"instanceType"` + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + InstanceRegion string `json:"instanceRegion"` + InstanceAccountId string `json:"instanceAccountId"` + Status string `json:"status"` +} + +type ListAssociationResponse struct { + Associations []CsnRtAssociation `json:"associations"` +} + +type ListAssociationResponseAssociations struct { +} + +type ListCsnBpLimitByCsnIdRequest struct { + LocalRegion string `json:"localRegion"` + PeerRegion string `json:"peerRegion"` +} + +type ListCsnBpLimitByCsnIdResponse struct { + BpLimits []CsnBpLimit `json:"bpLimits"` +} + +type ListCsnBpLimitByCsnIdResponseBpLimits struct { +} + +type ListCsnBpLimitResponse struct { + BpLimits []CsnBpLimit `json:"bpLimits"` +} + +type ListCsnBpLimitResponseBpLimits struct { +} + +type ListCsnBpResponse struct { + CsnBps []CsnBp `json:"csnBps"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListCsnBpResponseCsnBps struct { +} + +type ListCsnResponse struct { + Csns []Csn `json:"csns"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListCsnResponseCsns struct { +} + +type ListInstanceResponse struct { + Instances []Instance `json:"instances"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListInstanceResponseInstances struct { +} + +type ListPropagationResponse struct { + Propagations []CsnRtPropagation `json:"propagations"` +} + +type ListPropagationResponsePropagations struct { +} + +type ListRouteRuleResponse struct { + CsnRtRules []CsnRtRule `json:"csnRtRules"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListRouteRuleResponseCsnRtRules struct { +} + +type ListRouteTableResponse struct { + CsnRts []CsnRouteTable `json:"csnRts"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListRouteTableResponseCsnRts struct { +} + +type ListTgwResponse struct { + Tgws []Tgw `json:"tgws"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListTgwRuleResponse struct { + TgwRtRules []TgwRtRule `json:"tgwRtRules"` + Marker *string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker,omitempty"` + MaxKeys int32 `json:"maxKeys"` +} + +type ListTgwRuleResponseTgwRtRules struct { +} + +type Reservation struct { + ReservationLength int32 `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type ResizeCsnBpRequest struct { + Bandwidth int32 `json:"bandwidth"` +} + +type Tgw struct { + TgwId string `json:"tgwId"` + CsnId string `json:"csnId"` + Name string `json:"name"` + Region string `json:"region"` + Description string `json:"description"` +} + +type TgwRtRule struct { + RuleId string `json:"ruleId"` + RouteType string `json:"routeType"` + CsnId string `json:"csnId"` + CsnRtId string `json:"csnRtId"` + FromAttachId string `json:"fromAttachId"` + Status string `json:"status"` + DestAddress string `json:"destAddress"` + NextHopId string `json:"nextHopId"` + NextHopName string `json:"nextHopName"` + NextHopRegion string `json:"nextHopRegion"` + NextHopType string `json:"nextHopType"` + AsPath string `json:"asPath"` + Community string `json:"community"` + BlackHole bool `json:"blackHole"` +} + +type UnbindCsnBpRequest struct { + CsnId string `json:"csnId"` +} + +type UpdateCsnBpLimitRequest struct { + LocalRegion string `json:"localRegion"` + PeerRegion string `json:"peerRegion"` + Bandwidth int32 `json:"bandwidth"` +} + +type UpdateCsnBpRequest struct { + Name string `json:"name"` +} + +type UpdateCsnRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +type UpdateTgwRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +type ListCsnArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListCsnBpArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListInstanceArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListRouteRuleArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListRouteTableArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListTgwArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListTgwRuleArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} diff --git a/bce-sdk-go/services/dbsc/client.go b/bce-sdk-go/services/dbsc/client.go new file mode 100644 index 0000000..86fb955 --- /dev/null +++ b/bce-sdk-go/services/dbsc/client.go @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 dbsc + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" +) + +const DEFAULT_SERVICE_DOMAIN = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + +// Client of BCC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the BCC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endPoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endPoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func (c *Client) CreateVolumeCluster(args *CreateVolumeClusterArgs) (*CreateVolumeClusterResult, error) { + return CreateVolumeCluster(c, args) +} + +func (c *Client) ListVolumeCluster(queryArgs *ListVolumeClusterArgs) (*ListVolumeClusterResult, error) { + return ListVolumeCluster(c, queryArgs) +} + +func (c *Client) GetVolumeClusterDetail(clusterId string) (*VolumeClusterDetail, error) { + return GetVolumeClusterDetail(c, clusterId) +} + +func (c *Client) ResizeVolumeCluster(clusterId string, args *ResizeVolumeClusterArgs) error { + return ResizeVolumeCluster(c, clusterId, args) +} + +func (c *Client) PurchaseReservedVolumeCluster(clusterId string, args *PurchaseReservedVolumeClusterArgs) error { + return PurchaseReservedVolumeCluster(c, clusterId, args) +} + +func (c *Client) AutoRenewVolumeCluster(args *AutoRenewVolumeClusterArgs) error { + return AutoRenewVolumeCluster(c, args) +} + +func (c *Client) CancelAutoRenewVolumeCluster(args *CancelAutoRenewVolumeClusterArgs) error { + return CancelAutoRenewVolumeCluster(c, args) +} diff --git a/bce-sdk-go/services/dbsc/client_test.go b/bce-sdk-go/services/dbsc/client_test.go new file mode 100644 index 0000000..0c157a6 --- /dev/null +++ b/bce-sdk-go/services/dbsc/client_test.go @@ -0,0 +1,135 @@ +package dbsc + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "runtime" + "testing" +) + +var ( + DBSC_CLIENT *Client +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 6; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "../config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DBSC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +func TestCreateVolumeCluster(t *testing.T) { + args := &CreateVolumeClusterArgs{ + PurchaseCount: 1, + ClusterSizeInGB: 97280, + ClusterName: "dbsc", + StorageType: StorageTypeHdd, + Billing: &Billing{ + PaymentTiming: PaymentTimingPrePaid, + Reservation: &Reservation{ + ReservationLength: 6, + ReservationTimeUnit: "MONTH", + }, + }, + RenewTimeUnit: "month", + RenewTime: 6, + } + result, err := DBSC_CLIENT.CreateVolumeCluster(args) + if err != nil { + fmt.Println(err) + } + clusterId := result.ClusterIds[0] + fmt.Println(clusterId) + if result.ClusterUuids != nil { + clusterUuid := result.ClusterUuids[0] + fmt.Println(clusterUuid) + } +} + +func TestListVolumeCluster(t *testing.T) { + args := &ListVolumeClusterArgs{} + result, err := DBSC_CLIENT.ListVolumeCluster(args) + if err != nil { + fmt.Println(err) + } + fmt.Println(result) +} + +func TestGetVolumeClusterDetail(t *testing.T) { + result, err := DBSC_CLIENT.GetVolumeClusterDetail("DC-xxxxxx") + if err != nil { + fmt.Println(err) + } + fmt.Println(result) +} + +func TestResizeVolumeCluster(t *testing.T) { + args := &ResizeVolumeClusterArgs{ + NewClusterSizeInGB: 107520, + } + err := DBSC_CLIENT.ResizeVolumeCluster("DC-yWfhpUbN", args) + if err != nil { + fmt.Println(err) + } +} + +func TestPurchaseReservedVolumeCluster(t *testing.T) { + args := &PurchaseReservedVolumeClusterArgs{ + Billing: &Billing{ + PaymentTiming: PaymentTimingPrePaid, + Reservation: &Reservation{ + ReservationLength: 6, + ReservationTimeUnit: "month", + }, + }, + } + err := DBSC_CLIENT.PurchaseReservedVolumeCluster("DC-yWfhpUbN", args) + if err != nil { + fmt.Println(err) + } +} + +func TestAutoRenewVolumeCluster(t *testing.T) { + args := &AutoRenewVolumeClusterArgs{ + ClusterId: "DC-yWfhpUbN", + RenewTime: 6, + RenewTimeUnit: "month", + } + err := DBSC_CLIENT.AutoRenewVolumeCluster(args) + if err != nil { + fmt.Println(err) + } +} + +func TestCancelAutoRenewVolumeCluster(t *testing.T) { + args := &CancelAutoRenewVolumeClusterArgs{ + ClusterId: "DC-yWfhpUbN", + } + err := DBSC_CLIENT.CancelAutoRenewVolumeCluster(args) + if err != nil { + fmt.Println(err) + } +} diff --git a/bce-sdk-go/services/dbsc/dedicatedVolumeCluster.go b/bce-sdk-go/services/dbsc/dedicatedVolumeCluster.go new file mode 100644 index 0000000..689a33d --- /dev/null +++ b/bce-sdk-go/services/dbsc/dedicatedVolumeCluster.go @@ -0,0 +1,235 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 dbsc + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +func CreateVolumeCluster(cli bce.Client, args *CreateVolumeClusterArgs) (*CreateVolumeClusterResult, error) { + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CREATE_URI) + req.SetMethod(http.POST) + req.SetParam("uuidFlag", args.UuidFlag) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + err = cli.SendRequest(req, resp) + if err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CreateVolumeClusterResult{} + err = resp.ParseJsonBody(jsonBody) + if err != nil { + return nil, err + } + return jsonBody, nil +} + +func ListVolumeCluster(cli bce.Client, queryArgs *ListVolumeClusterArgs) (*ListVolumeClusterResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CREATE_URI) + req.SetMethod(http.GET) + + if queryArgs != nil { + if len(queryArgs.ZoneName) != 0 { + req.SetParam("zoneName", queryArgs.ZoneName) + } + if len(queryArgs.ClusterName) != 0 { + req.SetParam("clusterName", queryArgs.ClusterName) + } + if len(queryArgs.Marker) != 0 { + req.SetParam("marker", queryArgs.Marker) + } + if queryArgs.MaxKeys != 0 { + req.SetParam("maxKeys", strconv.Itoa(queryArgs.MaxKeys)) + } + } + + if queryArgs == nil || queryArgs.MaxKeys == 0 { + req.SetParam("maxKeys", "1000") + } + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListVolumeClusterResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetVolumeClusterDetail(cli bce.Client, clusterId string) (*VolumeClusterDetail, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CREATE_URI + "/" + clusterId) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &VolumeClusterDetail{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ResizeVolumeCluster(cli bce.Client, clusterId string, args *ResizeVolumeClusterArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CREATE_URI + "/" + clusterId) + req.SetMethod(http.PUT) + req.SetParam("resize", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { _ = resp.Body().Close() }() + return nil +} + +func PurchaseReservedVolumeCluster(cli bce.Client, clusterId string, args *PurchaseReservedVolumeClusterArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CREATE_URI + "/" + clusterId) + req.SetMethod(http.PUT) + + req.SetParam("purchaseReserved", "") + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { _ = resp.Body().Close() }() + return nil +} + +func AutoRenewVolumeCluster(cli bce.Client, args *AutoRenewVolumeClusterArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_AUTO_RENEW_URI) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { _ = resp.Body().Close() }() + + return nil +} + +func CancelAutoRenewVolumeCluster(cli bce.Client, args *CancelAutoRenewVolumeClusterArgs) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getVolumeClusterUri() + REQUEST_CANCEL_AUTO_RENEW_URI) + req.SetMethod(http.POST) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { _ = resp.Body().Close() }() + + return nil +} diff --git a/bce-sdk-go/services/dbsc/model.go b/bce-sdk-go/services/dbsc/model.go new file mode 100644 index 0000000..b02a31f --- /dev/null +++ b/bce-sdk-go/services/dbsc/model.go @@ -0,0 +1,114 @@ +package dbsc + +type CreateVolumeClusterArgs struct { + ZoneName string `json:"zoneName,omitempty"` + ClusterName string `json:"clusterName,omitempty"` + StorageType StorageType `json:"storageType,omitempty"` + ClusterSizeInGB int `json:"clusterSizeInGB,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Billing *Billing `json:"billing"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` + UuidFlag string `json:"uuidFlag"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type PaymentTimingType string + +const ( + PaymentTimingPrePaid PaymentTimingType = "Prepaid" + PaymentTimingPostPaid PaymentTimingType = "Postpaid" +) + +type StorageType string + +const ( + StorageTypeHP1 StorageType = "hp1" + StorageTypeHdd StorageType = "hdd" +) + +type CreateVolumeClusterResult struct { + ClusterIds []string `json:"clusterIds"` + ClusterUuids []string `json:"clusterUuids"` +} + +type ListVolumeClusterArgs struct { + Marker string + MaxKeys int + ClusterName string + ZoneName string +} + +type ListVolumeClusterResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Result []VolumeClusterModel `json:"result"` +} + +type VolumeClusterModel struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + CreatedTime string `json:"createdTime"` + ExpiredTime string `json:"expiredTime"` + Status string `json:"status"` + ZoneName string `json:"logicalZone"` + ProductType string `json:"productType"` + ClusterType string `json:"clusterType"` + TotalCapacity int `json:"totalCapacity"` + UsedCapacity int `json:"usedCapacity"` + AvailableCapacity int `json:"availableCapacity"` + ExpandingCapacity int `json:"expandingCapacity"` + CreatedVolumeNum int `json:"createdVolumeNum"` + EnableAutoRenew bool `json:"enableAutoRenew"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` +} + +type VolumeClusterDetail struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + CreatedTime string `json:"createdTime"` + ExpiredTime string `json:"expiredTime"` + Status string `json:"status"` + ZoneName string `json:"logicalZone"` + ProductType string `json:"productType"` + ClusterType string `json:"clusterType"` + TotalCapacity int `json:"totalCapacity"` + UsedCapacity int `json:"usedCapacity"` + AvailableCapacity int `json:"availableCapacity"` + ExpandingCapacity int `json:"expandingCapacity"` + CreatedVolumeNum int `json:"createdVolumeNum"` + AffiliatedCDSNumber []string `json:"affiliatedCDSNumber"` + EnableAutoRenew bool `json:"enableAutoRenew"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` +} + +type ResizeVolumeClusterArgs struct { + NewClusterSizeInGB int `json:"newClusterSizeInGB"` +} + +type PurchaseReservedVolumeClusterArgs struct { + Billing *Billing `json:"billing"` +} + +type AutoRenewVolumeClusterArgs struct { + ClusterId string `json:"clusterId"` + RenewTimeUnit string `json:"renewTimeUnit"` + RenewTime int `json:"renewTime"` +} + +type CancelAutoRenewVolumeClusterArgs struct { + ClusterId string `json:"clusterId"` +} diff --git a/bce-sdk-go/services/dbsc/util.go b/bce-sdk-go/services/dbsc/util.go new file mode 100644 index 0000000..a03c0e9 --- /dev/null +++ b/bce-sdk-go/services/dbsc/util.go @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of BBC service +package dbsc + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX_V2 = bce.URI_PREFIX + "v2" + REQUEST_VOLUME_URI = "/volume" + + REQUEST_CREATE_URI = "/cluster" + REQUEST_AUTO_RENEW_URI = "/cluster/autoRenew" + REQUEST_CANCEL_AUTO_RENEW_URI = "/cluster/cancelAutoRenew" +) + +func getVolumeClusterUri() string { + return URI_PREFIX_V2 + REQUEST_VOLUME_URI +} diff --git a/bce-sdk-go/services/dcc/client.go b/bce-sdk-go/services/dcc/client.go new file mode 100644 index 0000000..62dc5e0 --- /dev/null +++ b/bce-sdk-go/services/dcc/client.go @@ -0,0 +1,27 @@ +package dcc + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + // DefaultEndpoint -- xx + DefaultEndpoint = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" +) + +// Client used for client +type Client struct { + *bce.BceClient +} + +// NewClient return a client +func NewClient(ak, sk, endPoint string) (ret *Client, err error) { + if len(endPoint) == 0 { + endPoint = DefaultEndpoint + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} diff --git a/bce-sdk-go/services/dcc/dedicatedHost.go b/bce-sdk-go/services/dcc/dedicatedHost.go new file mode 100644 index 0000000..58b98f2 --- /dev/null +++ b/bce-sdk-go/services/dcc/dedicatedHost.go @@ -0,0 +1,134 @@ +package dcc + +import ( + "fmt" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListDedicatedHosts -- xx +func (c *Client) ListDedicatedHosts(args *ListDedicatedHostArgs) (list *ListDedicatedHostResult, err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithBody(args). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost"). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", fmt.Sprintf("%d", args.MaxKeys)). + WithQueryParamFilter("zoneName", args.ZoneName). + WithResult(&list). + Do() + return +} + +// GetDedicatedHostDetail -- xx +func (c *Client) GetDedicatedHostDetail(hostID string) (ret *GetDedicatedHostDetailResult, err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(bce.URI_PREFIX + "v1/dedicatedHost/" + hostID). + WithBody(&ret). + WithResult(&ret). + Do() + return + +} + +// PurchaseReserved -- xx +func (c *Client) PurchaseReserved(hostID string, args *PurchaseReservedArgs) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/"+hostID). + WithQueryParamFilter("purchaseReserved", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(&args). + Do() + return +} + +// Create -- xx +func (c *Client) Create(args *CreateArgs) (ret *CreateResult, err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(&args). + WithResult(&ret). + Do() + return + +} +func (c *Client) tag(action, uri string, args interface{}) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(uri). + WithQueryParamFilter(action, ""). + WithBody(&args). + Do() + return + +} + +// BindTag -- xx +func (c *Client) BindTag(dccID string, args *BindTagArgs) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/"+dccID+"/tag"). + WithQueryParamFilter("bind", ""). + WithBody(&args). + Do() + return + +} + +// UnbindTag -- xx +func (c *Client) UnbindTag(dccID string, args *BindTagArgs) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/"+dccID+"/tag"). + WithQueryParamFilter("unbind", ""). + WithBody(&args). + Do() + return +} + +// CreateInstance -- xx +func (c *Client) CreateInstance(args *CreateInstanceArgs) (ret *CreateInstanceResult, err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/instance"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(&args). + WithResult(&ret). + Do() + return +} + +// ModityInstance -- xx +func (c *Client) ModityInstance(instanceID string, args *ModityInstanceArgs) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/instance/"+instanceID). + WithQueryParamFilter("modifyAttribute", ""). + WithBody(&args). + Do() + return +} +func (c *Client) tagforInstance(action, instanceID string, args *BindTagArgs) (err error) { + err = bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(bce.URI_PREFIX+"v1/dedicatedHost/instance/"+instanceID+"/tag"). + WithQueryParamFilter(action, ""). + WithBody(&args). + Do() + return +} + +// BindTagforInstance -- xx +func (c *Client) BindTagforInstance(instanceID string, args *BindTagArgs) error { + return c.tagforInstance("bind", instanceID, args) +} + +// UnbindTagforInstance -- xx +func (c *Client) UnbindTagforInstance(instanceID string, args *BindTagArgs) error { + return c.tagforInstance("unbind", instanceID, args) +} diff --git a/bce-sdk-go/services/dcc/model.go b/bce-sdk-go/services/dcc/model.go new file mode 100644 index 0000000..7eea78f --- /dev/null +++ b/bce-sdk-go/services/dcc/model.go @@ -0,0 +1,162 @@ +package dcc + +import bcc "github.com/baidubce/bce-sdk-go/services/bcc/api" + +// ListDedicatedHostArgs struct +type ListDedicatedHostArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxkeys"` + ZoneName string `json:"zoneName"` +} + +// ListDedicatedHostResult struct +type ListDedicatedHostResult struct { + Marker string + IsTruncated bool + NextMarker string + MaxKeys int + DedicatedHosts []*DedicatedHostModel +} + +// DedicatedHostModel -- xx +type DedicatedHostModel struct { + ID string // 专属服务器ID,符合BCE规范,是一个定长字符串,且只允许包含大小写字母、数字、连字号(-)和下划线(_)。 + Name string // 专属服务器名称 + Status DedicatedHostStatus // 专属服务器状态 + FlavorName string // 套餐名称 + ResourceUsage ResourceUsage // 套餐明细 + PaymentTiming string // 付费方式,包括Postpaid(后付费),Prepaid(预付费)两种。 + CreateTime string // 创建时间 + ExpireTime string // 过期时间,只有Prepaid计费资源存在 + Desc string // 描述信息 + ZoneName string // 可用区名称 + Tags []TagModel // 实例当前配置的标签 +} + +// DedicatedHostStatus string +type DedicatedHostStatus string + +const ( + //DedicatedHostStatusStarting DedicatedHostStatus = "Starting" + DedicatedHostStatusStarting DedicatedHostStatus = "Starting" + //DedicatedHostStatusRunning DedicatedHostStatus = "Running" + DedicatedHostStatusRunning DedicatedHostStatus = "Running" + //DedicatedHostStatusExpired DedicatedHostStatus = "Expired" + DedicatedHostStatusExpired DedicatedHostStatus = "Expired" + //DedicatedHostStatusError DedicatedHostStatus = "Error" + DedicatedHostStatusError DedicatedHostStatus = "Error" +) + +// ResourceUsage struct { +type ResourceUsage struct { + CPUCount int `json:"cpuCount"` + FreeCPUCount int `json:"FreeCpuCount"` + MemoryCapacityInGB int + FreeMemoryCapacityInGB int + EphemeralDisks []EphemeralDisk +} + +// EphemeralDisk struct { for go-lint +type EphemeralDisk struct { + StorageType StorageType `json:"storageType,omitempty"` + SizeInGB int `json:"sizeInGB,omitempty"` + FreeSizeInGB int `json:"freesizeInGB,omitempty"` +} + +// StorageType string for go-lint +type StorageType string + +const ( + //StorageTypeSata StorageType = "sata" for go-lint + StorageTypeSata StorageType = "sata" + //StorageTypeSSD StorageType = "ssd" for go-lint + StorageTypeSSD StorageType = "ssd" +) + +// TagModel struct { for go-lint +type TagModel struct { + TagKey string + TagValue string +} + +// GetDedicatedHostDetailResult struct { +type GetDedicatedHostDetailResult struct { + DedicatedHost DedicatedHostModel +} + +// PurchaseReservedArgs struct { +type PurchaseReservedArgs struct { + ClientToken string // 是 Query参数 幂等性Token,是一个长度不超过64位的ASCII字符串)。 + Billing Billing // 是 RequestBody参数 订单信息 +} + +// Billing struct { +type Billing struct { + PaymentTiming string `json:"paymentTiming"` // 付款时间,预支付(Prepaid)和后支付(Postpaid) + Reservation Reservation `json:"reservation"` // 保留信息,支付方式为后支付时不需要设置,预支付时必须设置 +} + +// Reservation struct { +type Reservation struct { + Length int `json:"reservationLength"` // 时长,[1,2,3,4,5,6,7,8,9,12,24,36] + TimeUnit string `json:"reservationTimeUnit,omitempty"` // 时间单位,month,当前仅支持按月 +} + +// CreateArgs -- xx +type CreateArgs struct { + Version string // 是 URL参数 API版本号 + ClientToken string // 是 Query参数 幂等性Token,是一个长度不超过64位的ASCII字符串)。 + VCPU int `json:"vcpu"` // 否 RequestBody参数 待创建专属服务器虚拟CPU核数,数量不能超过物理CPU核数的两倍 + FlavorName string // 是 RequestBody参数 套餐类型,可选计算型(calculation)C01/C02,可选大数据机型(storage)S01/S02 + PurchaseCount int // 否 RequestBody参数 批量创建(购买)的虚专属服务器个数,必须为大于0的整数,可选参数,缺省为1 + Name string // 否 RequestBody参数 专属服务器名字(可选)。默认都不指定name。如果指定name:批量时name作为名字的前缀。后端将加上后缀,后缀生成方式:name{ -序号}。如果没有指定name,则自动生成,方式:{instance-八位随机串-序号}。注:随机串从0~9a~z生成;序号按照count的数量级,依次递增,如果count为100,则序号从000~100递增,如果为10,则从00~10递增 + Billing Billing // 是 RequestBody参数 订单、计费相关参数 + ZoneName string // 否 RequestBody参数 指定zone信息,默认为空,由系统自动选择 +} + +// CreateResult struct { +type CreateResult struct { + DedicatedHostIds []string +} + +// BindTagArgs struct { +type BindTagArgs struct { + ChangeTags []TagModel // 是 Request Body参数 标签数组,每个标签由tagKey和tagValue组成 +} + +// ModityInstanceArgs struct { +type ModityInstanceArgs struct { + Name string // 是 Request Body参数 实例名称 +} + +// CreateInstanceArgs struct { +type CreateInstanceArgs struct { + ClientToken string `json:"clientToken"` + ImageID string `json:"imageId"` + Billing Billing `json:"billing"` + CPUCount int `json:"cpuCount"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` + InstanceType string `json:"instanceType,omitempty"` + RootDiskSizeInGb int `json:"rootDiskSizeInGb,omitempty"` + RootDiskStorageType string `json:"rootDiskStorageType,omitempty"` + LocalDiskSizeInGB int `json:"localDiskSizeInGB,omitempty"` + EphemeralDisks []EphemeralDisk `json:"ephemeralDisks,omitempty"` + CreateCdsList []bcc.CreateCdsModel `json:"createCdsList,omitempty"` + NetworkCapacityInMbps int `json:"networkCapacityInMbps"` + InternetChargeType string `json:"internetChargeType,omitempty"` + DedicatedHostID string `json:"dedicatedHostId,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + Name string `json:"name,omitempty"` + AdminPass string `json:"adminPass,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + SubnetID string `json:"subnetId,omitempty"` + SecurityGroupID string `json:"securityGroupId,omitempty"` + GpuCard string `json:"gpuCard,omitempty"` + FpgaCard string `json:"fpgaCard,omitempty"` + CardCount string `json:"cardCount,omitempty"` +} + +// CreateInstanceResult struct +type CreateInstanceResult struct { + InstanceIds []string // 虚机实例ID的集合,其中ID符合BCE规范,必须是一个定长字符串,且只允许包含大小写字母、数字、连字号(-)和下划线(_)。 +} diff --git a/bce-sdk-go/services/ddc/client.go b/bce-sdk-go/services/ddc/client.go new file mode 100644 index 0000000..6774962 --- /dev/null +++ b/bce-sdk-go/services/ddc/client.go @@ -0,0 +1,207 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for DDC service + +// Package ddc defines the DDC services of BCE. The supported APIs are all defined in sub-package +package ddc + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1/ddc" + DEFAULT_ENDPOINT = "ddc.su.baidubce.com" + REQUEST_DDC_INSTANCE_URL = "/instance" + REQUEST_DDC_POOL_URL = "/pool" + REQUEST_DDC_HOST_URL = "/host" + REQUEST_DDC_DEPLOY_URL = "/deploy" + REQUEST_DDC_DATABASE_URL = "/database" + REQUEST_DDC_TABLE_URL = "/table" + REQUEST_DDC_HARDLINK_URL = "/link" + REQUEST_DDC_ACCOUNT_URL = "/account" + REQUEST_DDC_ROGROUP_URL = "/roGroup" + REQUEST_DDC_RECYCLER_URL = "/recycler" + REQUEST_DDC_SECURITYGROUP_URL = "/security" + REQUEST_DDC_LOG_URL = "/logs" + REQUEST_DDC_UPDATE_ACTION = "/update" + REQUEST_DDC_MAINTAINTIME_URL = "/maintenTimeInfo" + REQUEST_UPDATE_MAINTAINTIME_URL = "/updateMaintenTime" +) + +// Client of DDC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getDdcUri() string { + return URI_PREFIX +} + +func getDdcInstanceUri() string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL +} + +// Pool URL +func getPoolUri() string { + return URI_PREFIX + REQUEST_DDC_POOL_URL +} + +func getPoolUriWithId(poolId string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId +} + +// Host URL +func getHostUri() string { + return URI_PREFIX + REQUEST_DDC_HOST_URL +} + +func getHostUriWithPnetIp(poolId, hostPnetIP string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_HOST_URL + "/" + hostPnetIP +} + +// DeploySet URL +func getDeploySetUri(poolId string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_DEPLOY_URL +} + +func getDeploySetUriWithId(poolId, id string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_DEPLOY_URL + "/" + id +} + +func getDdcUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId +} + +// Database URL +func getDatabaseUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL +} + +func getDatabaseUriWithDbName(instanceId string, dbName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL + "/" + dbName +} + +// Account URL +func getAccountUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ACCOUNT_URL +} + +func getAccountUriWithAccountName(instanceId string, accountName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ACCOUNT_URL + "/" + accountName +} + +// RoGroup URL +func getRoGroupUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ROGROUP_URL +} + +// MaintainTime URL +func getMaintainTimeUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_MAINTAINTIME_URL +} + +// MaintainTime URL +func getUpdateMaintainTimeUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_UPDATE_MAINTAINTIME_URL +} + +// RoGroup URL +func getUpdateRoGroupUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + REQUEST_DDC_UPDATE_ACTION +} + +// RoGroupWeight URL +func getUpdateRoGroupWeightUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + "/updateWeight" +} + +// ReBalance RoGroup URL +func getReBalanceRoGroupUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + "/balanceRoLoad" +} + +// Recycler URL +func getRecyclerUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL +} + +// Recycler Recover URL +func getRecyclerRecoverUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL + "/batchRecover" +} + +// Recycler Recover URL +func getRecyclerDeleteUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL + "/batchDelete" +} + +// List Security Group By Vpc URL +func getSecurityGroupWithVpcIdUrl(vpcId string) string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/" + vpcId + "/listByVpc" +} + +// List Security Group By Instance URL +func getSecurityGroupWithInstanceIdUrl(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/" + instanceId + "/list" +} + +// Bind Security Group To Instance URL +func getBindSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/bind" +} + +// UnBind Security Group To Instance URL +func getUnBindSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/unbind" +} + +// Batch Replace Security Group URL +func getReplaceSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/updateSecurityGroup" +} + +func getLogsUrlWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL +} + +func getLogsUrlWithLogId(instanceId, logId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL + "/" + logId +} + +func getCreateTableHardLinkUrl(instanceId, dbName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + + REQUEST_DDC_DATABASE_URL + "/" + dbName + + REQUEST_DDC_TABLE_URL + REQUEST_DDC_HARDLINK_URL +} + +func getTableHardLinkUrl(instanceId, dbName, tableName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + + REQUEST_DDC_DATABASE_URL + "/" + dbName + + REQUEST_DDC_TABLE_URL + "/" + tableName + REQUEST_DDC_HARDLINK_URL +} + +func getChangeSemiSyncStatusUrlWithId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/semisync" +} diff --git a/bce-sdk-go/services/ddc/client_test.go b/bce-sdk-go/services/ddc/client_test.go new file mode 100644 index 0000000..143272a --- /dev/null +++ b/bce-sdk-go/services/ddc/client_test.go @@ -0,0 +1,530 @@ +package ddc + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + DDC_CLIENT *Client + DDC_ID string = "ddc-m8rs4yjz" + ACCOUNT_NAME string = "go_sdk_account_2" + ACCOUNT_PASSWORD string = "go_sdk_password_1" + ACCOUNT_REMARK string = "go-sdk-remark-1" + DB_NAME string = "go_sdk_db_1" + DB_CHARACTER_SET_NAME string = "utf8" + DB_REMARK string = "go_sdk_db_remark" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +const ( + SDK_NAME_PREFIX = "sdk_rds_" + POOL = "xdb_gaiabase_pool" + PNETIP = "100.88.65.121" + DEPLOY_ID = "ab89d829-9068-d88e-75bc-64bb6367d036" + INSTANCE_ID = "rdsmcb38t7b8atx" +) + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 1; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DDC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateInstance(t *testing.T) { + args := &CreateRdsArgs{ + PurchaseCount: 1, + InstanceName: "mysql_5.7", + //SourceInstanceId: "ddc-mmqptugx", + Engine: "mysql", + EngineVersion: "5.7", + CpuCount: 1, + MemoryCapacity: 1, + VolumeCapacity: 5, + Billing: Billing{ + PaymentTiming: "Postpaid", + Reservation: Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + VpcId: "vpc-80m2ksi6sv0f", + ZoneNames: []string{ + "cn-su-c", + }, + Subnets: []SubnetMap{ + { + ZoneName: "cn-su-c", + SubnetId: "sbn-8v3p33vhyhq5", + }, + }, + DeployId: "", + PoolId: "xdb_gaiabase_pool", + } + rds, err := DDC_CLIENT.CreateRds(args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(rds) +} + +func TestClient_ListDeploySets(t *testing.T) { + deploys, err := DDC_CLIENT.ListDeploySets(POOL, &Marker{MaxKeys: 1}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, deploys.MaxKeys, 1) + fmt.Println(deploys.Result[0].DeployName) +} + +func TestClient_GetDeploySet(t *testing.T) { + deploy, err := DDC_CLIENT.GetDeploySet(POOL, DEPLOY_ID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, DEPLOY_ID, deploy.DeployID) +} + +func TestClient_DeleteDeploySet(t *testing.T) { + err := DDC_CLIENT.DeleteDeploySet(POOL, "b444142c-69ed-87a6-396b-fc4a76a9754f") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateDeploySet(t *testing.T) { + err := DDC_CLIENT.CreateDeploySet(POOL, &CreateDeployRequest{ + DeployName: "api-from-go", + Strategy: "distributed", + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListParameters(t *testing.T) { + parameters, err := DDC_CLIENT.ListParameters("rdsmcb38t7b8atx") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, parameters.Items, parameters.Items) + fmt.Println(parameters.Items) +} + +func TestClient_UpdateParameter(t *testing.T) { + DDC_CLIENT.UpdateParameter(INSTANCE_ID, &UpdateParameterArgs{Parameters: []KVParameter{ + { + Name: "auto_increment_increment", + Value: "2", + }, + }}) +} + +func TestClient_GetSecurityIps(t *testing.T) { + ips, err := DDC_CLIENT.GetSecurityIps(INSTANCE_ID) + ExpectEqual(t.Errorf, ips, ips) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(ips.SecurityIps) +} +func TestClient_ListRoGroup(t *testing.T) { + ips, err := DDC_CLIENT.ListRoGroup("rdsm2bpbozmj59z") + ExpectEqual(t.Errorf, ips, ips) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(ips) +} + +func TestClient_ListVpc(t *testing.T) { + vpc, err := DDC_CLIENT.ListVpc() + ExpectEqual(t.Errorf, vpc, vpc) + ExpectEqual(t.Errorf, err, nil) + fmt.Println(vpc) +} + +func TestClient_GetDetail(t *testing.T) { + detail, err := DDC_CLIENT.GetDetail("ddc-mmqptugx") + ExpectEqual(t.Errorf, detail, detail) + ExpectEqual(t.Errorf, err, nil) + res, _ := json.Marshal(detail) + fmt.Println(string(res)) +} + +func TestClient_UpdateSecurityIps(t *testing.T) { + DDC_CLIENT.UpdateSecurityIps(INSTANCE_ID, &UpdateSecurityIpsArgs{ + SecurityIps: []string{ + "10.10.0.0/16", + }, + }) +} + +func TestClient_GetBackupList(t *testing.T) { + list, err := DDC_CLIENT.GetBackupList(INSTANCE_ID) + ExpectEqual(t.Errorf, list.Snapshots, list.Snapshots) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(list.Snapshots) +} + +func TestClient_GetBackupDetail(t *testing.T) { + list, err := DDC_CLIENT.GetBackupDetail(INSTANCE_ID, "1611156600150859846") + ExpectEqual(t.Errorf, list.Snapshot, list.Snapshot) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(list.Snapshot) +} + +func TestClient_CreateBackup(t *testing.T) { + DDC_CLIENT.CreateBackup(INSTANCE_ID) +} + +func TestClient_ModifyBackupPolicy(t *testing.T) { + DDC_CLIENT.ModifyBackupPolicy(INSTANCE_ID, &BackupPolicy{ + BackupDays: "0,1,2,3,5", + ExpireInDays: 100, + BackupTime: "17:00:00Z", + }) +} + +func TestClient_GetZoneList(t *testing.T) { + list, err := DDC_CLIENT.GetZoneList() + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, list, list) + fmt.Println(list) +} + +func TestClient_ListSubnets(t *testing.T) { + args := &ListSubnetsArgs{ZoneName: "zoneA"} + subnets, err := DDC_CLIENT.ListSubnets(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, subnets, subnets) + result, _ := json.Marshal(subnets.Subnets) + fmt.Println(string(result)) +} + +func TestClient_GetBinlogList(t *testing.T) { + list, err := DDC_CLIENT.GetBinlogList(INSTANCE_ID, "") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, list, list) + fmt.Println(list.Binlogs) +} + +func TestClient_GetBinlogDetail(t *testing.T) { + detail, err := DDC_CLIENT.GetBinlogDetail(INSTANCE_ID, "1611072328934047748") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, detail.Binlog.BinlogId, "1611072328934047748") + fmt.Println(detail) +} + +func TestClient_DeleteDdcInstance(t *testing.T) { + instanceId := "ddc-m8rs4yjz" + DDC_CLIENT.DeleteRds(instanceId) +} + +func TestClient_SwitchInstance(t *testing.T) { + args := &SwitchArgs{IsSwitchNow: false} + DDC_CLIENT.SwitchInstance("rdsma7fcyu2anvi", args) +} + +// Database +func TestClient_CreateDatabase(t *testing.T) { + args := &CreateDatabaseArgs{ + ClientToken: getClientToken(), + DbName: DB_NAME, + CharacterSetName: DB_CHARACTER_SET_NAME, + Remark: DB_REMARK, + } + + err := DDC_CLIENT.CreateDatabase(DDC_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetDatabase(t *testing.T) { + result, err := DDC_CLIENT.GetDatabase(DDC_ID, DB_NAME) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "available", result.DbStatus) +} + +func TestClient_ListDatabase(t *testing.T) { + result, err := DDC_CLIENT.ListDatabase(DDC_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Databases { + if e.DbName == DB_NAME { + ExpectEqual(t.Errorf, "available", e.DbStatus) + } + } +} + +func TestClient_UpdateDatabaseRemark(t *testing.T) { + args := &UpdateDatabaseRemarkArgs{ + Remark: DB_REMARK + "_update", + } + err := DDC_CLIENT.UpdateDatabaseRemark(DDC_ID, DB_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteDatabase(t *testing.T) { + err := DDC_CLIENT.DeleteDatabase(DDC_ID, DB_NAME) + ExpectEqual(t.Errorf, nil, err) +} + +// Account +func TestClient_CreateAccount(t *testing.T) { + args := &CreateAccountArgs{ + ClientToken: getClientToken(), + AccountName: ACCOUNT_NAME, + Password: ACCOUNT_PASSWORD, + AccountType: "Common", + Desc: ACCOUNT_REMARK, + DatabasePrivileges: []DatabasePrivilege{ + { + DbName: "hello", + AuthType: "ReadOnly", + }, + }, + } + + err := DDC_CLIENT.CreateAccount(DDC_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAccount(t *testing.T) { + result, err := DDC_CLIENT.GetAccount(DDC_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Available", result.Status) +} + +func TestClient_ListAccount(t *testing.T) { + result, err := DDC_CLIENT.ListAccount(DDC_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Accounts { + if e.AccountName == ACCOUNT_NAME { + ExpectEqual(t.Errorf, "Available", e.Status) + } + } +} + +func TestClient_UpdateAccountPassword(t *testing.T) { + args := &UpdateAccountPasswordArgs{ + Password: ACCOUNT_PASSWORD + "_update", + } + err := DDC_CLIENT.UpdateAccountPassword(DDC_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountDesc(t *testing.T) { + args := &UpdateAccountDescArgs{ + Desc: ACCOUNT_REMARK + "_update", + } + err := DDC_CLIENT.UpdateAccountDesc(DDC_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountPrivileges(t *testing.T) { + databasePrivileges := []DatabasePrivilege{ + { + DbName: "hello", + AuthType: "ReadWrite", + }, + } + args := &UpdateAccountPrivilegesArgs{ + DatabasePrivileges: databasePrivileges, + } + err := DDC_CLIENT.UpdateAccountPrivileges(DDC_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAccount(t *testing.T) { + err := DDC_CLIENT.DeleteAccount(DDC_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) +} + +// util +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateRds(t *testing.T) { + args := &CreateRdsArgs{ + PurchaseCount: 1, + InstanceName: "mysql_5.7", + //SourceInstanceId: "ddc-mmqptugx", + Engine: "mysql", + EngineVersion: "5.7", + CpuCount: 1, + MemoryCapacity: 1, + VolumeCapacity: 5, + Billing: Billing{ + PaymentTiming: "Postpaid", + Reservation: Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + VpcId: "vpc-80m2ksi6sv0f", + ZoneNames: []string{ + "cn-su-c", + }, + Subnets: []SubnetMap{ + { + ZoneName: "cn-su-c", + SubnetId: "sbn-8v3p33vhyhq5", + }, + }, + DeployId: "", + PoolId: "xdb_gaiabase_pool", + } + rds, err := DDC_CLIENT.CreateRds(args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(rds) +} + +func TestClient_ListDdcInstance(t *testing.T) { + args := &ListRdsArgs{ + // 批量获取列表的查询的起始位置,实例列表中Marker需要指定实例Id,可选 + // Marker: "marker", + // 指定每页包含的最大数量(主实例),最大数量不超过1000,缺省值为1000,可选 + MaxKeys: 100, + } + resp, err := DDC_CLIENT.ListRds(args) + + if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return + } + + // 返回标记查询的起始位置 + fmt.Println("ddc list marker: ", resp.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("ddc list isTruncated: ", resp.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("ddc list nextMarker: ", resp.NextMarker) + // 每页包含的最大数量 + fmt.Println("ddc list maxKeys: ", resp.MaxKeys) + + // 获取instance的列表信息 + for _, e := range resp.Instances { + fmt.Println("ddc instanceId: ", e.InstanceId) + fmt.Println("ddc instanceName: ", e.InstanceName) + fmt.Println("ddc engine: ", e.Engine) + fmt.Println("ddc engineVersion: ", e.EngineVersion) + fmt.Println("ddc instanceStatus: ", e.InstanceStatus) + fmt.Println("ddc cpuCount: ", e.CpuCount) + fmt.Println("ddc memoryCapacity: ", e.MemoryCapacity) + fmt.Println("ddc volumeCapacity: ", e.VolumeCapacity) + fmt.Println("ddc usedStorage: ", e.UsedStorage) + fmt.Println("ddc paymentTiming: ", e.PaymentTiming) + fmt.Println("ddc instanceType: ", e.InstanceType) + fmt.Println("ddc instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("ddc instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("ddc publicAccessStatus: ", e.PublicAccessStatus) + fmt.Println("ddc vpcId: ", e.VpcId) + } + +} +func TestClient_GetDetail2(t *testing.T) { + result, err := DDC_CLIENT.GetDetail("ddc-mrjnghx8") + if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return + } + // 获取实例详情信息 + fmt.Println("ddc instanceId: ", result.InstanceId) + fmt.Println("ddc instanceName: ", result.InstanceName) + fmt.Println("ddc engine: ", result.Engine) + fmt.Println("ddc engineVersion: ", result.EngineVersion) + fmt.Println("ddc instanceStatus: ", result.InstanceStatus) + fmt.Println("ddc cpuCount: ", result.CpuCount) + fmt.Println("ddc memoryCapacity: ", result.MemoryCapacity) + fmt.Println("ddc volumeCapacity: ", result.VolumeCapacity) + fmt.Println("ddc usedStorage: ", result.UsedStorage) + fmt.Println("ddc paymentTiming: ", result.PaymentTiming) + fmt.Println("ddc instanceType: ", result.InstanceType) + fmt.Println("ddc instanceCreateTime: ", result.InstanceCreateTime) + fmt.Println("ddc instanceExpireTime: ", result.InstanceExpireTime) + fmt.Println("ddc publicAccessStatus: ", result.PublicAccessStatus) + fmt.Println("ddc vpcId: ", result.VpcId) + fmt.Println("ddc Subnets: ", result.Subnets) + fmt.Println("ddc BackupPolicy: ", result.BackupPolicy) + fmt.Println("ddc RoGroupList: ", result.RoGroupList) + fmt.Println("ddc NodeMaster: ", result.NodeMaster) + fmt.Println("ddc NodeSlave: ", result.NodeSlave) + fmt.Println("ddc NodeReadReplica: ", result.NodeReadReplica) + fmt.Println("ddc DeployId: ", result.DeployId) +} + +func TestClient_UpdateInstanceName(t *testing.T) { + args := &UpdateInstanceNameArgs{ + // DDC实例名称,允许小写字母、数字,中文,长度限制为1~64 + InstanceName: "ssss", + } + err := DDC_CLIENT.UpdateInstanceName("ddc-m67du0mh", args) + if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return + } + fmt.Printf("update instance name success\n") +} + +func TestClient_BinlogAccessDetail(t *testing.T) { + args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, + } + result, err := DDC_CLIENT.BinlogAccessDetail(args) + if err != nil { + fmt.Printf("get binlog access detail error: %+v\n", err) + return + } + fmt.Printf("get binlog access detail success\n") + fmt.Println("result: ", result) +} + +func TestClient_SnapshotAccessDetail(t *testing.T) { + args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, + } + result, err := DDC_CLIENT.SnapshotAccessDetail(args) + if err != nil { + fmt.Printf("get snapshot access detail error: %+v\n", err) + return + } + fmt.Printf("get snapshot access detail success\n %+v", result) +} diff --git a/bce-sdk-go/services/ddc/ddc.go b/bce-sdk-go/services/ddc/ddc.go new file mode 100644 index 0000000..1191f15 --- /dev/null +++ b/bce-sdk-go/services/ddc/ddc.go @@ -0,0 +1,1868 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// ddc.go - the ddc APIs definition supported by the DDC service +package ddc + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + KEY_CLIENT_TOKEN = "clientToken" + KEY_MARKER = "marker" + KEY_MAX_KEYS = "maxKeys" + COMMA = "," +) + +// Convert marker to request params +func getMarkerParams(marker *Marker) map[string]string { + if marker == nil { + marker = &Marker{Marker: "-1"} + } + params := make(map[string]string, 2) + params[KEY_MARKER] = marker.Marker + if marker.MaxKeys > 0 { + params[KEY_MAX_KEYS] = strconv.Itoa(marker.MaxKeys) + } + return params +} + +// CreateInstance - create a Instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create a instance +// +// RETURNS: +// - *InstanceIds: the result of create RDS, contains new RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *CreateInstanceArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDdcInstanceUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateRds - create a DDC with the specific parameters +// +// PARAMS: +// - args: the arguments to create a ddc +// +// RETURNS: +// - *InstanceIds: the result of create DDC, contains new DDC's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateRds(args *CreateRdsArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Engine == "" { + return nil, fmt.Errorf("unset Engine") + } + + if args.EngineVersion == "" { + return nil, fmt.Errorf("unset EngineVersion") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + + newArgs := &CreateInstanceArgs{ + InstanceType: "RDS", + Number: args.PurchaseCount, + ClientToken: args.ClientToken, + Instance: CreateInstance{ + InstanceName: args.InstanceName, + Engine: args.Engine, + EngineVersion: args.EngineVersion, + CpuCount: args.CpuCount, + AllocatedMemoryInGB: args.MemoryCapacity, + AllocatedStorageInGB: args.VolumeCapacity, + DiskIoType: "ssd", + DeployId: args.DeployId, + PoolId: args.PoolId, + IsDirectPay: args.IsDirectPay, + Billing: args.Billing, + AutoRenewTime: args.AutoRenewTime, + AutoRenewTimeUnit: args.AutoRenewTimeUnit, + Tags: args.Tags, + Category: args.Category, + SyncMode: args.SyncMode, + }, + } + + info, err := c.SupplyVpcInfo(newArgs, args) + if err != nil { + return nil, err + } + result, err2 := c.CreateInstance(info) + if err2 != nil { + return nil, err2 + } + //list, err4 := c.GetZoneList() + //if err4 != nil { + // fmt.Printf("get zone list error: %+v\n", err4) + // return nil, err4 + //} + //if args.ZoneNames != nil { + // newZoneName := "" + // for _, e1 := range list.Zones { + // if strings.Join(args.ZoneNames, ",") == strings.Join(e1.ZoneNames, ",") { + // newZoneName = strings.Join(e1.ZoneNames, ",") + // } + // } + // newArgs.Instance.AZone = newZoneName + //} + return result, nil +} + +func (c *Client) SupplyVpcInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + + info := newArgs + vpc, err := c.ListVpc() + if err != nil { + fmt.Printf("list vpc error: %+v\n", err) + return nil, err + } + defaultVpcId := "" + if args.VpcId == "" { + for _, e := range *vpc { + defaultVpcId = e.VpcId + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + if args.VpcId == defaultVpcId { + for _, e := range *vpc { + if e.VpcId == args.VpcId { + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + info, err = c.UnDefaultVpcInfo(info, args) + if err != nil { + fmt.Printf("set vpc error: %+v\n", err) + return nil, err + } + } else { + for _, e := range *vpc { + if args.VpcId == e.ShortId { + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + info, err = c.UnDefaultVpcInfo(info, args) + if err != nil { + fmt.Printf("supply zoneAndSubnet info error: %+v\n", err) + return nil, err + } + } + return info, nil +} + +func (c *Client) SupplyZoneAndSubnetInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + newZoneName := "" + if args.Subnets != nil { + for _, e := range args.Subnets { + newZoneName += e.ZoneName + "," + } + if newZoneName != "" && newZoneName[0:len(newZoneName)-1] != strings.Join(args.ZoneNames, ",") { + fmt.Printf("subnets and zoneNames not matcher: %+v\n", nil) + return nil, errors.New("subnets and zoneNames not matcher") + } else { + subnets, err1 := c.ListSubnets(nil) + if err1 != nil { + fmt.Printf("list subnets error: %+v\n", err1) + return nil, err1 + } + subnetId := "" + if args.Subnets != nil { + for _, e := range subnets.Subnets { + for _, e1 := range args.Subnets { + if e.ShortId == e1.SubnetId { + subnetId += e.Az + ":" + e.SubnetId + "," + } + } + } + if subnetId == "" { + return nil, errors.New("subnetId no match") + } + newArgs.Instance.SubnetId = subnetId[0 : len(subnetId)-1] + } + } + } else { + var subnetStr string + for _, e := range args.ZoneNames { + if !strings.Contains(e, ",") { + listSubnetsArgs := &ListSubnetsArgs{ + VpcId: args.VpcId, + ZoneName: e, + } + subnets, err := c.ListSubnets(listSubnetsArgs) + if err != nil { + fmt.Printf("list subnets error: %+v\n", err) + return nil, err + } + if subnets != nil && len(subnets.Subnets) > 0 { + subnetId := subnets.Subnets[0].SubnetId + subnetStr += e + ":" + subnetId + "," + } + } + } + if len(subnetStr) < 1 { + return nil, errors.New("Have no available subnet") + } + newArgs.Instance.SubnetId = subnetStr[:len(subnetStr)-1] + } + return newArgs, nil +} + +func (c *Client) UnDefaultVpcInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + info := newArgs + list, err2 := c.GetZoneList() + if err2 != nil { + fmt.Printf("get zone list error: %+v\n", err2) + return nil, err2 + } + newZoneName := "" + if args.ZoneNames == nil { + if args.Subnets == nil { + subnets, _ := c.ListSubnets(nil) + if subnets == nil || len(subnets.Subnets) == 0 { + return nil, errors.New("Have no available subnet or zone") + } + + for _, e := range subnets.Subnets { + info.Instance.AZone = e.Az + args.ZoneNames = append(args.ZoneNames, e.Az) + break + } + //for _, e := range list.Zones { + // info.Instance.AZone = e.ApiZoneNames[0] + // args.ZoneNames = append(args.ZoneNames, e.ApiZoneNames[0]) + // break + //} + } else { + for _, e := range args.Subnets { + newZoneName += e.ZoneName + "," + args.ZoneNames = append(args.ZoneNames, e.ZoneName) + } + for _, e := range list.Zones { + if newZoneName == strings.Join(e.ApiZoneNames, ",") { + info.Instance.AZone = strings.Join(e.ApiZoneNames, ",") + } + } + } + if args.ZoneNames == nil { + return nil, errors.New("Have no available zone for your operation.") + } + } else { + newZoneName = "" + for _, e1 := range list.Zones { + if strings.Join(args.ZoneNames, ",") == strings.Join(e1.ZoneNames, ",") { + newZoneName = strings.Join(e1.ApiZoneNames, ",") + } + } + info.Instance.AZone = newZoneName + } + + info, err2 = c.SupplyZoneAndSubnetInfo(info, args) + if err2 != nil { + return nil, err2 + } + return info, nil +} + +// CreateReadReplica - create a readReplica ddc with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica ddc +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica ddc, contains the readReplica DDC's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateReadReplica(args *CreateReadReplicaArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.SourceInstanceId == "" { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + detail, err2 := c.GetDdcDetail(args.SourceInstanceId) + if err2 != nil { + return nil, err2 + } + newArgs := &CreateInstanceArgs{ + InstanceType: "RDS", + Number: args.PurchaseCount, + ClientToken: args.ClientToken, + Instance: CreateInstance{ + InstanceName: args.InstanceName, + Engine: detail.Instance.Engine, + EngineVersion: detail.Instance.EngineVersion, + CpuCount: args.CpuCount, + AllocatedMemoryInGB: args.MemoryCapacity, + AllocatedStorageInGB: args.VolumeCapacity, + DiskIoType: "ssd", + DeployId: args.DeployId, + PoolId: args.PoolId, + RoGroupId: args.RoGroupId, + RoGroupWeight: args.RoGroupWeight, + EnableDelayOff: args.EnableDelayOff, + DelayThreshold: args.DelayThreshold, + LeastInstanceAmount: args.LeastInstanceAmount, + Billing: args.Billing, + IsDirectPay: args.IsDirectPay, + Tags: args.Tags, + }, + } + + createRdsArgs := &CreateRdsArgs{ + VpcId: args.VpcId, + Subnets: args.Subnets, + ZoneNames: args.ZoneNames, + } + + info, err := c.SupplyVpcInfo(newArgs, createRdsArgs) + if err != nil { + return nil, err + } + result, err2 := c.CreateInstance(info) + if err2 != nil { + return nil, err2 + } + + return result, err +} + +// UpdateRoGroup - update a roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateRoGroup(roGroupId string, args *UpdateRoGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateRoGroupUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// UpdateRoGroupReplicaWeight- update repica weight in roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateRoGroupReplicaWeight(roGroupId string, args *UpdateRoGroupWeightArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateRoGroupWeightUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ReBalanceRoGroup- Initiate a rebalance for foGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReBalanceRoGroup(roGroupId string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getReBalanceRoGroupUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) CreateDeploySet(poolId string, args *CreateDeployRequest) error { + if args == nil { + return fmt.Errorf("unset args") + } + if !(args.Strategy == "distributed" || args.Strategy == "centralized") { + return fmt.Errorf("Only support strategy distributed/centralized, current strategy: %v", args.Strategy) + } + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(cli). + WithMethod(http.POST). + WithURL(getDeploySetUri(poolId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return err +} + +// UpdateDeploySet - update a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDeploySet(poolId string, deployId string, args *UpdateDeployRequest) error { + if args == nil { + return fmt.Errorf("unset args") + } + if !(args.Strategy == "distributed" || args.Strategy == "centralized") { + return fmt.Errorf("Only support strategy distributed/centralized, current strategy: %v", args.Strategy) + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDeploySetUriWithId(poolId, deployId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListRds - list all instances +// RETURNS: +// - *ListRdsResult: the result of list instances with marker +// - error: nil if success otherwise the specific error +func (c *Client) ListRds(marker *ListRdsArgs) (*ListRdsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getDdcInstanceUri() + "/list") + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRdsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// GetDdcDetail - get details of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - *InstanceModelResult: the detail of the instance +// - error: nil if success otherwise the specific error +func (c *Client) GetDdcDetail(instanceId string) (*InstanceModelResult, error) { + result := &InstanceModelResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)). + WithResult(result). + Do() + + return result, err +} + +// GetDetail - get a specific ddc Instance's detail +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *Instance: the specific ddc Instance's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetDetail(instanceId string) (*Instance, error) { + detail, err := c.GetDdcDetail(instanceId) + instanceJson, _ := json.Marshal(detail.Instance) + args := &Instance{ + InstanceName: detail.Instance.InstanceName, + InstanceId: detail.Instance.InstanceId, + SourceInstanceId: detail.Instance.SourceInstanceId, + Endpoint: detail.Instance.Endpoint, + Engine: detail.Instance.Engine, + EngineVersion: detail.Instance.EngineVersion, + InstanceStatus: detail.Instance.InstanceStatus, + CpuCount: detail.Instance.CpuCount, + MemoryCapacity: detail.Instance.AllocatedMemoryInGB, + VolumeCapacity: detail.Instance.AllocatedStorageInGB, + UsedStorage: detail.Instance.UsedStorageInGB, + InstanceType: detail.Instance.Type, + InstanceCreateTime: detail.Instance.InstanceCreateTime, + InstanceExpireTime: detail.Instance.InstanceExpireTime, + PublicAccessStatus: detail.Instance.PublicAccessStatus, + PaymentTiming: detail.Instance.PaymentTiming, + SyncMode: detail.Instance.SyncMode, + Region: detail.Instance.Region, + VpcId: detail.Instance.VpcId, + BackupPolicy: detail.Instance.BackupPolicy, + RoGroupList: detail.Instance.RoGroupList, + NodeMaster: detail.Instance.NodeMaster, + NodeSlave: detail.Instance.NodeSlave, + NodeReadReplica: detail.Instance.NodeReadReplica, + Subnets: detail.Instance.Subnets, + DeployId: detail.Instance.DeployId, + ZoneNames: detail.Instance.ZoneNames, + } + err = json.Unmarshal(instanceJson, &args) + return args, err +} + +// DeleteRds - delete instances +// +// PARAMS: +// - instanceIds: id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRds(instanceIds string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getDdcInstanceUri()+"/delete"). + WithQueryParam("instanceIds", instanceIds). + Do() +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// - args: reboot args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(instanceId string, args *RebootArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)). + WithQueryParam("reboot", ""). + WithBody(args). + Do() +} + +// UpdateInstanceName - update name of a specified instance +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceName(instanceId string, args *UpdateInstanceNameArgs) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/updateName"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return err +} + +// GetBackupList - get backup list of the instance +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *GetBackupListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupList(instanceId string) (*GetBackupListResult, error) { + result := &GetBackupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/snapshot"). + WithResult(result). + Do() + + return result, err +} + +// GetZoneList - list all zone +// +// PARAMS: +// - c: the client agent which can perform sending request +// +// RETURNS: +// - *GetZoneListResult: result of the zone list +// - error: nil if success otherwise the specific error +func (c *Client) GetZoneList() (*GetZoneListResult, error) { + result := &GetZoneListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX + "/zone"). + WithResult(result). + Do() + + return result, err +} + +// ListsSubnet - list all Subnets +// +// PARAMS: +// - c: the client agent which can perform sending request +// - args: the arguments to list all subnets, not necessary +// +// RETURNS: +// - *ListSubnetsResult: result of the subnet list +// - error: nil if success otherwise the specific error +func (c *Client) ListSubnets(args *ListSubnetsArgs) (*ListSubnetsResult, error) { + result := &ListSubnetsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX + "/subnet"). + WithResult(result). + Do() + if args == nil { + return result, err + } + if args.ZoneName == "" { + return result, err + } + // to compat rds api, filter by zone and vpcId + if result.Subnets != nil && len(result.Subnets) > 0 { + var filterd = []Subnet{} + for _, subnet := range result.Subnets { + // subnet az is logical zone + if subnet.Az == args.ZoneName { + if args.VpcId == "" || args.VpcId == subnet.VpcId { + filterd = append(filterd, subnet) + } + } + } + result.Subnets = filterd + } + return result, err +} + +// ListPool - list current pools +// RETURNS: +// - *ListResultWithMarker: the result of list hosts with marker +// - error: nil if success otherwise the specific error +func (cli *Client) ListPool(marker *Marker) (*ListPoolResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getPoolUri()) + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPoolResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListDeploySets - list all deploy sets +// RETURNS: +// - *ListResultWithMarker: the result of list deploy sets with marker +// - error: nil if success otherwise the specific error +func (cli *Client) ListDeploySets(poolId string, marker *Marker) (*ListDeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUri(poolId)) + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListDeploySetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - deploySetId: the id of the deploy set +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (cli *Client) DeleteDeploySet(poolId string, deploySetId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(poolId, deploySetId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + + return nil +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *DeploySet: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (cli *Client) GetDeploySet(poolId string, deploySetId string) (*DeploySet, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(poolId, deploySetId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeploySet{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetSecurityIps - get all SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *GetSecurityIpsResult: all security IP +// - error: nil if success otherwise the specific error +func (c *Client) GetSecurityIps(instanceId string) (*GetSecurityIpsResult, error) { + rowResult := &SecurityIpsRawResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/authIp"). + WithResult(rowResult). + Do() + // to compat rds api,json annotations for SecurityIps are different + result := &GetSecurityIpsResult{ + SecurityIps: rowResult.SecurityIps, + } + return result, err +} + +// UpdateSecurityIps - update SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: all SecurityIps +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSecurityIps(instacneId string, args *UpdateSecurityIpsArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instacneId)+"/updateAuthIp"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ListParameters - list all parameters of a RDS instance +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListParametersResult: the result of list all parameters +// - error: nil if success otherwise the specific error +func (c *Client) ListParameters(instanceId string) (*ListParametersResult, error) { + result := &ListParametersResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/parameter" + "/list"). + WithResult(result). + Do() + + return result, err +} + +// UpdateParameter - update Parameter +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: *UpdateParameterArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateParameter(instanceId string, args *UpdateParameterArgs) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/parameter"+"/modify"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return err +} + +// CreateBackup - create backup of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateBackup(instanceId string) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDdcUriWithInstanceId(instanceId)+"/snapshot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return err +} + +// GetBackupDetail - get details of the instance'Backup +// +// PARAMS: +// - instanceId: the id of the instance +// - snapshotId: the id of the backup +// +// RETURNS: +// - *BackupDetailResult: the detail of the backup +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupDetail(instanceId string, snapshotId string) (*BackupDetailResult, error) { + result := &BackupDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/snapshot" + "/" + snapshotId). + WithResult(result). + Do() + + return result, err +} + +// ModifyBackupPolicy - update backupPolicy +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: the specific rds Instance's BackupPolicy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyBackupPolicy(instanceId string, args *BackupPolicy) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/snapshot"+"/update"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return err +} + +// GetBinlogList - get backup list of the instance +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *BinlogListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogList(instanceId string, datetime string) (*BinlogListResult, error) { + + result := &BinlogListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)+"/binlog"). + WithQueryParam("datetime", datetime). + WithResult(result). + Do() + + return result, err +} + +// GetBinlogDetail - get details of the instance'Binlog +// +// PARAMS: +// - instanceId: the id of the instance +// - binlog: the id of the binlog +// +// RETURNS: +// - *BinlogDetailResult: the detail of the binlog +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogDetail(instanceId string, binlog string) (*BinlogDetailResult, error) { + result := &BinlogDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/binlog" + "/" + binlog). + WithResult(result). + Do() + + return result, err +} + +// SwitchInstance - main standby switching of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// - args: switch now or wait to the maintain time +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SwitchInstance(instanceId string, args *SwitchArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/switchMaster"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return err +} + +// CreateDatabase - create a database with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateDatabase(instanceId string, args *CreateDatabaseArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.DbName == "" { + return fmt.Errorf("unset DbName") + } + + if args.CharacterSetName == "" { + return fmt.Errorf("unset CharacterSetName") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDatabaseUriWithInstanceId(instanceId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteDatabase - delete an database of a DDC instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - dbName: the specific database's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDatabase(instanceId, dbName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + Do() +} + +// UpdateDatabaseRemark - update a database remark with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - dbName: the specific accountName +// - args: the arguments to update a database remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDatabaseRemark(instanceId string, dbName string, args *UpdateDatabaseRemarkArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + //if args.Remark == "" { + // return fmt.Errorf("unset Remark") + //} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + WithQueryParam("remark", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetDatabase - get an database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - dbName: the specific database's name +// +// RETURNS: +// - *Database: the database's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetDatabase(instanceId, dbName string) (*Database, error) { + result := &DatabaseResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + WithResult(result). + Do() + + return &result.Database, err +} + +// ListDatabase - list all database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *ListDatabaseResult: the result of list all database, contains all databases' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListDatabase(instanceId string) (*ListDatabaseResult, error) { + result := &ListDatabaseResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDatabaseUriWithInstanceId(instanceId)). + WithResult(result). + Do() + + return result, err +} + +// CreateAccount - create a account with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateAccount(instanceId string, args *CreateAccountArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.AccountName == "" { + return fmt.Errorf("unset AccountName") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + if args.AccountType == "" { + args.AccountType = "common" + } + if args.AccountType == "Super" { + args.AccountType = "rdssuper" + } + if args.AccountType == "Common" { + args.AccountType = "common" + } + if args.DatabasePrivileges != nil { + for idx, _ := range args.DatabasePrivileges { + if args.DatabasePrivileges[idx].AuthType == "ReadOnly" { + args.DatabasePrivileges[idx].AuthType = "readOnly" + } else if args.DatabasePrivileges[idx].AuthType == "ReadWrite" { + args.DatabasePrivileges[idx].AuthType = "readWrite" + } + } + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAccountUriWithInstanceId(instanceId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteAccount - delete an account of a RDS instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - accountName: the specific account's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAccount(instanceId, accountName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + Do() +} + +// UpdateAccountPassword - update a account password with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPassword(instanceId string, accountName string, args *UpdateAccountPasswordArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("password", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountDesc - update a account desc with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountDesc(instanceId string, accountName string, args *UpdateAccountDescArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + //if args.Remark == "" { + // return fmt.Errorf("unset Remark") + //} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("remark", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountPrivileges - update a account privileges with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account privileges +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPrivileges(instanceId string, accountName string, args *UpdateAccountPrivilegesArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + for idx, _ := range args.DatabasePrivileges { + if args.DatabasePrivileges[idx].AuthType == "ReadOnly" { + args.DatabasePrivileges[idx].AuthType = "readOnly" + } else if args.DatabasePrivileges[idx].AuthType == "ReadWrite" { + args.DatabasePrivileges[idx].AuthType = "readWrite" + } + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("privileges", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetAccount - get an account of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - accountName: the specific account's name +// +// RETURNS: +// - *Account: the account's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetAccount(instanceId, accountName string) (*Account, error) { + result := &AccountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithResult(result). + Do() + + if result.Account.AccountType == "common" { + result.Account.AccountType = "Common" + } else if result.Account.AccountType == "rdssuper" { + result.Account.AccountType = "Super" + } + + for idx, _ := range result.Account.DatabasePrivileges { + if result.Account.DatabasePrivileges[idx].AuthType == "readOnly" { + result.Account.DatabasePrivileges[idx].AuthType = "ReadOnly" + } else if result.Account.DatabasePrivileges[idx].AuthType == "readWrite" { + result.Account.DatabasePrivileges[idx].AuthType = "ReadWrite" + } + } + result.Account.Status = strings.Title(result.Account.Status) + return &result.Account, err +} + +// ListAccount - list all account of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListAccountResult: the result of list all account, contains all accounts' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListAccount(instanceId string) (*ListAccountResult, error) { + result := &ListAccountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAccountUriWithInstanceId(instanceId)). + WithResult(result). + Do() + for idx, _ := range result.Accounts { + if result.Accounts[idx].AccountType == "common" { + result.Accounts[idx].AccountType = "Common" + } else if result.Accounts[idx].AccountType == "rdssuper" { + result.Accounts[idx].AccountType = "Super" + } + result.Accounts[idx].Status = strings.Title(result.Accounts[idx].Status) + + for iidx, _ := range result.Accounts[idx].DatabasePrivileges { + if result.Accounts[idx].DatabasePrivileges[iidx].AuthType == "readOnly" { + result.Accounts[idx].DatabasePrivileges[iidx].AuthType = "ReadOnly" + } else if result.Accounts[idx].DatabasePrivileges[iidx].AuthType == "readWrite" { + result.Accounts[idx].DatabasePrivileges[iidx].AuthType = "ReadWrite" + } + } + } + return result, err +} + +// ListRoGroup - list all roGroups of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListRoGroupResult: All roGroups of the current instance +// - error: nil if success otherwise the specific error +func (c *Client) ListRoGroup(instanceId string) (*ListRoGroupResult, error) { + result := &ListRoGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRoGroupUriWithInstanceId(instanceId) + "/list"). + WithResult(result). + Do() + + return result, err +} + +// ListVpc - list all Vpc +// +// PARAMS: +// RETURNS: +// - *ListVpc: All vpc of +// - error: nil if success otherwise the specific error +func (c *Client) ListVpc() (*[]VpcVo, error) { + result := &[]VpcVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri() + "/vpcList"). + WithResult(result). + Do() + + return result, err +} + +// GetMaintenTime - get details of the maintenTime +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *DeploySet: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) GetMaintainTime(instanceId string) (*MaintainTime, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getMaintainTimeUriWithInstanceId(instanceId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &MaintainWindow{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return &jsonBody.MaintainTime, nil +} + +// UpdateMaintenTime - update UpdateMaintenTime of instance +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateMaintainTime(instanceId string, args *MaintainTime) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateMaintainTimeUriWithInstanceId(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListRecycleInstances - list all instances in recycler with marker +// +// PARAMS: +// - marker: marker page +// +// RETURNS: +// - *RecyclerInstanceList: the result of instances in recycler +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycleInstances(marker *Marker) (*RecyclerInstanceList, error) { + result := &RecyclerInstanceList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParams(getMarkerParams(marker)). + WithURL(getRecyclerUrl()). + WithResult(result). + Do() + + return result, err +} + +// RecoverRecyclerInstances - batch recover instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to recover +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoverRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + args := &BatchInstanceIds{ + InstanceIds: strings.Join(instanceIds, COMMA), + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRecyclerRecoverUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// DeleteRecyclerInstances - batch delete instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + // delete use query params + instanceIdsParam := strings.Join(instanceIds, COMMA) + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRecyclerDeleteUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("instanceIds", instanceIdsParam). + Do() + return err +} + +// ListSecurityGroupByVpcId - list security groups by vpc id +// +// PARAMS: +// - vpcId: id of vpc +// +// RETURNS: +// - *[]SecurityGroup:security groups of vpc +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByVpcId(vpcId string) (*[]SecurityGroup, error) { + if len(vpcId) < 1 { + return nil, fmt.Errorf("unset vpcId") + } + result := &[]SecurityGroup{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithVpcIdUrl(vpcId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// ListSecurityGroupByInstanceId - list security groups by instance id +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *ListSecurityGroupResult: list secrity groups result of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByInstanceId(instanceId string) (*ListSecurityGroupResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + result := &ListSecurityGroupResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithInstanceIdUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// BindSecurityGroups - bind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// UnBindSecurityGroups - unbind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUnBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ReplaceSecurityGroups - replace SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReplaceSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getReplaceSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListLogByInstanceId - list error or slow logs of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *[]Log:logs of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListLogByInstanceId(instanceId string, args *ListLogArgs) (*[]Log, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if args == nil { + return nil, fmt.Errorf("unset list args") + } + if "error" != args.LogType && "slow" != args.LogType { + return nil, fmt.Errorf("invalid logType, should be 'error' or 'slow'") + } + result := &[]Log{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithInstanceId(instanceId)). + WithQueryParam("logType", strings.ToLower(args.LogType)). + WithQueryParam("datetime", args.Datetime). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetLogById - get log's detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Log:log's detail of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetLogById(instanceId, logId string, args *GetLogArgs) (*LogDetail, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(logId) < 1 { + return nil, fmt.Errorf("unset logId") + } + if args == nil { + return nil, fmt.Errorf("unset get log args") + } + + result := &LogDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithLogId(instanceId, logId)). + WithQueryParam("downloadValidTimeInSec", strconv.Itoa(args.ValidSeconds)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// LazyDropCreateHardLink - create a hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) LazyDropCreateHardLink(instanceId, dbName, tableName string) error { + if len(instanceId) < 1 { + return fmt.Errorf("unset instanceId") + } + if len(dbName) < 1 { + return fmt.Errorf("unset dbName") + } + if len(tableName) < 1 { + return fmt.Errorf("unset tableName") + } + + args := &CreateTableHardLinkArgs{ + TableName: tableName, + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getCreateTableHardLinkUrl(instanceId, dbName)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// LazyDropDeleteHardLink - delete the hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) LazyDropDeleteHardLink(instanceId, dbName, tableName string) error { + if len(instanceId) < 1 { + return fmt.Errorf("unset instanceId") + } + if len(dbName) < 1 { + return fmt.Errorf("unset dbName") + } + if len(tableName) < 1 { + return fmt.Errorf("unset tableName") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getTableHardLinkUrl(instanceId, dbName, tableName)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// ResizeRds - resize an RDS with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to resize an RDS +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeRds(instanceId string, args *ResizeRdsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId) + "/resize"). + WithBody(args). + Do() +} + +// UpdateSyncMode - update sync mode of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update syncMode +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifySyncMode(instanceId string, args *ModifySyncModeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getChangeSemiSyncStatusUrlWithId(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetDisk - get disk detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Disk:disk of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetDisk(instanceId string) (*Disk, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &Disk{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)+"/disk"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// SnapshotAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *Client) SnapshotAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + result := &BackupAccessDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri()+"/snapshot/accessdetail"). + WithQueryParam("startDateTime", args.StartDateTime). + WithQueryParam("endDateTime", args.EndDateTime). + WithQueryParam("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("dataType", args.DataType). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// BinlogAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *Client) BinlogAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + result := &BackupAccessDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri()+"/binlog/accessdetail"). + WithQueryParam("startDateTime", args.StartDateTime). + WithQueryParam("endDateTime", args.EndDateTime). + WithQueryParam("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("dataType", args.DataType). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/ddc/ddc_util/util.go b/bce-sdk-go/services/ddc/ddc_util/util.go new file mode 100644 index 0000000..e24ecb7 --- /dev/null +++ b/bce-sdk-go/services/ddc/ddc_util/util.go @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of RDS service +package ddc_util + +import ( + "bytes" + "encoding/gob" + "encoding/hex" + "errors" + "fmt" + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} + +// 拷贝字段内容工具 +func SimpleCopyProperties(dst, src interface{}) (err error) { + // 防止意外panic + defer func() { + if e := recover(); e != nil { + err = errors.New(fmt.Sprintf("%v", e)) + } + }() + + var buf bytes.Buffer + if err := gob.NewEncoder(&buf).Encode(src); err != nil { + return err + } + return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst) +} diff --git a/bce-sdk-go/services/ddc/model.go b/bce-sdk-go/services/ddc/model.go new file mode 100644 index 0000000..4405df5 --- /dev/null +++ b/bce-sdk-go/services/ddc/model.go @@ -0,0 +1,817 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package ddc + +import "github.com/baidubce/bce-sdk-go/model" + +type CreateInstanceArgs struct { + ClientToken string `json:"-"` + InstanceType string `json:"instanceType"` + Number int `json:"number"` + Instance CreateInstance `json:"instance"` +} + +type CreateRdsArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + PurchaseCount int `json:"purchaseCount,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + Category string `json:"category,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacity int `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + DeployId string `json:"deployId"` + PoolId string `json:"poolId"` + SyncMode string `json:"syncMode"` +} + +type CreateReadReplicaArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + PurchaseCount int `json:"purchaseCount,omitempty"` + SourceInstanceId string `json:"sourceInstanceId"` + InstanceName string `json:"instanceName,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacity int `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + DeployId string `json:"deployId"` + PoolId string `json:"poolId"` + RoGroupId string `json:"roGroupId"` + EnableDelayOff bool `json:"enableDelayOff"` + DelayThreshold int `json:"delayThreshold"` + LeastInstanceAmount int `json:"leastInstanceAmount"` + RoGroupWeight int `json:"roGroupWeight"` +} + +type Instance struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + Category string `json:"category"` + InstanceStatus string `json:"instanceStatus"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"allocatedMemoryInGB"` + VolumeCapacity int `json:"allocatedStorageInGB"` + NodeAmount int `json:"nodeAmount"` + UsedStorage float64 `json:"usedStorageInGB"` + PublicAccessStatus bool `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Endpoint Endpoint `json:"endpoint"` + SyncMode string `json:"syncMode"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + Region string `json:"region"` + InstanceType string `json:"type"` + SourceInstanceId string `json:"sourceInstanceId"` + SourceRegion string `json:"sourceRegion"` + ZoneNames []string `json:"zoneNames"` + VpcId string `json:"vpcId"` + Subnets []Subnet `json:"subnets"` + Topology Topology `json:"topology"` + PaymentTiming string `json:"paymentTiming"` + RoGroupList []RoGroup `json:"roGroupList"` + NodeMaster NodeInfo `json:"nodeMaster"` + NodeSlave NodeInfo `json:"nodeSlave"` + NodeReadReplica NodeInfo `json:"nodeReadReplica"` + DeployId string `json:"deployId"` +} + +type SubnetMap struct { + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation Reservation `json:"reservation,omitempty"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength,omitempty"` + ReservationTimeUnit string `json:"reservationTimeUnit,omitempty"` +} + +type CreateResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type InstanceModelResult struct { + Instance InstanceModel `json:"instance"` +} + +type CreateInstance struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + SourceInstanceId string `json:"sourceInstanceId"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + CpuCount int `json:"cpuCount"` + AllocatedMemoryInGB int `json:"allocatedMemoryInGB"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + AZone string `json:"azone"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + DiskIoType string `json:"diskIoType"` + DeployId string `json:"deployId"` + PoolId string `json:"poolId"` + RoGroupId string `json:"roGroupId"` + EnableDelayOff bool `json:"enableDelayOff"` + DelayThreshold int `json:"delayThreshold"` + LeastInstanceAmount int `json:"leastInstanceAmount"` + RoGroupWeight int `json:"roGroupWeight"` + IsDirectPay bool `json:"IsDirectPay"` + Billing Billing `json:"billing"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Category string `json:"category,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + SyncMode string `json:"syncMode"` +} + +type Pool struct { + CPUQuotaTotal int `json:"cpuQuotaTotal"` + CPUQuotaUsed int `json:"cpuQuotaUsed"` + CreateTime string `json:"createTime"` + DeployMethod string `json:"deployMethod"` + DiskQuotaTotal int `json:"diskQuotaTotal"` + DiskQuotaUsed int `json:"diskQuotaUsed"` + Engine string `json:"engine"` + Hosts []Host `json:"hosts"` + MaxMemoryUsedRatio string `json:"maxMemoryUsedRatio"` + MemoryQuotaTotal int `json:"memoryQuotaTotal"` + MemoryQuotaUsed int `json:"memoryQuotaUsed"` + PoolID string `json:"poolId"` + PoolName string `json:"poolName"` + VpcID string `json:"vpcId"` +} + +type Host struct { + Containers []Container `json:"containers"` + Flavor Flavor `json:"flavor"` + CPUQuotaTotal int `json:"cpuQuotaTotal"` + CPUQuotaUsed int `json:"cpuQuotaUsed"` + DeploymentStatus string `json:"deploymentStatus"` + DiskQuotaTotal int `json:"diskQuotaTotal"` + DiskQuotaUsed int `json:"diskQuotaUsed"` + HostID string `json:"hostId"` + HostName string `json:"hostName"` + ImageType string `json:"imageType"` + MemoryQuotaTotal int64 `json:"memoryQuotaTotal"` + MemoryQuotaUsed int64 `json:"memoryQuotaUsed"` + PnetIP string `json:"pnetIp"` + Role string `json:"role"` + Status string `json:"status"` + SubnetID string `json:"subnetId"` + VnetIP string `json:"vnetIp"` + VpcID string `json:"vpcId"` + Zone string `json:"zone"` +} + +type OperateHostRequest struct { + Action string `json:"action"` +} + +type Flavor struct { + CPUCount int `json:"cpuCount"` + CPUType string `json:"cpuType"` + Disk int `json:"disk"` + FlavorID string `json:"flavorId"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` +} + +type Container struct { + ContainerID string `json:"containerId"` + DeployID string `json:"deployId"` + DeployName string `json:"deployName"` + Engine string `json:"engine"` + HostID string `json:"hostId"` + HostName string `json:"hostName"` + PoolName string `json:"poolName"` + Role string `json:"role"` + Zone string `json:"zone"` +} + +type DeploySet struct { + CreateTime string `json:"createTime"` + DeployID string `json:"deployId"` + DeployName string `json:"deployName"` + Instances []string `json:"instances"` + PoolID string `json:"poolId"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type CreateDeployRequest struct { + ClientToken string `json:"-"` + DeployName string `json:"deployName"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type UpdateDeployRequest struct { + ClientToken string `json:"-"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type Marker struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListResultWithMarker struct { + IsTruncated bool `json:"isTruncated"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` +} + +type ListPoolResult struct { + ListResultWithMarker + Result []Pool `json:"result"` +} + +type ListHostResult struct { + ListResultWithMarker + Result []Host `json:"result"` +} + +type ListDeploySetResult struct { + ListResultWithMarker + Result []DeploySet `json:"result"` +} + +type InstanceModel struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + InstanceStatus string `json:"instanceStatus"` + CpuCount int `json:"cpuCount"` + AllocatedMemoryInGB float64 `json:"allocatedMemoryInGB"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + NodeAmount int `json:"nodeAmount"` + UsedStorageInGB float64 `json:"usedStorageInGB"` + PublicAccessStatus bool `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Endpoint Endpoint `json:"endpoint"` + SyncMode string `json:"syncMode"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + Region string `json:"region"` + InstanceType string `json:"instanceType"` + SourceInstanceId string `json:"sourceInstanceId"` + SourceRegion string `json:"sourceRegion"` + ZoneNames []string `json:"zoneNames"` + VpcId string `json:"vpcId"` + Subnets []Subnet `json:"subnets"` + NodeMaster NodeInfo `json:"nodeMaster"` + NodeSlave NodeInfo `json:"nodeSlave"` + NodeReadReplica NodeInfo `json:"nodeReadReplica"` + DeployId string `json:"deployId"` + Topology Topology `json:"topology"` + DiskType string `json:"diskType"` + Type string `json:"type"` + ApplicationType string `json:"applicationType"` + RoGroupList []RoGroup `json:"roGroupList"` + PaymentTiming string `json:"paymentTiming"` +} + +type SubnetVo struct { + Name string `json:"name"` + SubnetId string `json:"subnetId"` + Az string `json:"az"` + Cidr string `json:"cidr"` + ShortId string `json:"shortId"` +} + +type RoGroup struct { + RoGroupID string `json:"roGroupId"` + RoGroupName string `json:"roGroupName"` + VnetIP string `json:"vnetIp"` + IsBalanceRoLoad int `json:"isBalanceRoLoad"` + EnableDelayOff int `json:"enableDelayOff"` + DelayThreshold int `json:"delayThreshold"` + LeastInstanceAmount int `json:"leastInstanceAmount"` + ReplicaList []Replica `json:"replicaList"` +} + +type UpdateRoGroupArgs struct { + RoGroupName string `json:"roGroupName"` + IsBalanceRoLoad int `json:"isBalanceRoLoad"` + EnableDelayOff int `json:"enableDelayOff"` + DelayThreshold int `json:"delayThreshold"` + LeastInstanceAmount int `json:"leastInstanceAmount"` +} + +type UpdateRoGroupWeightArgs struct { + IsBalanceRoLoad int `json:"isBalanceRoLoad"` + ReplicaList []ReplicaWeight `json:"replicaList"` +} +type ReplicaWeight struct { + InstanceId string `json:"instanceId"` + Weight int `json:"weight"` +} + +type Replica struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Status string `json:"status"` + RoGroupWeight int `json:"roGroupWeight"` +} + +type NodeInfo struct { + Id string `json:"id"` + Azone string `json:"azone"` + SubnetId string `json:"subnetId"` + Cidr string `json:"cidr"` + Name string `json:"name"` + HostName string `json:"hostname"` +} + +type Subnet struct { + Name string `json:"name"` + SubnetId string `json:"subnetId"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + ShortId string `json:"shortId"` + VpcId string `json:"vpcId"` + VpcShortId string `json:"vpcShortId"` + Az string `json:"az"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} + +type Endpoint struct { + Address string `json:"address"` + Port int `json:"port"` + VnetIp string `json:"vnetIp"` + VnetIpBackup string `json:"vnetIpBackup"` + InetIp string `json:"inetIp"` +} + +type BackupPolicy struct { + BackupDays string `json:"backupDays"` + BackupTime string `json:"backupTime"` + Persistent bool `json:"persistent"` + ExpireInDays int `json:"expireInDays"` + FreeSpaceInGB int `json:"freeSpaceInGb"` +} + +type Topology struct { + Rdsproxy []string `json:"rdsproxy"` + Master []string `json:"master"` + ReadReplica []string `json:"readReplica"` +} + +type DeleteDdcArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type UpdateInstanceNameArgs struct { + InstanceName string `json:"instanceName"` +} + +type ListRdsResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Instances []Instance `json:"result"` +} + +type ListRdsArgs struct { + Marker string + MaxKeys int +} + +type GetBackupListResult struct { + Snapshots []Snapshot `json:"snapshots"` + FreeSpaceInMB int64 `json:"freeSpaceInMB"` + UsedSpaceInMB int64 `json:"usedSpaceInMB"` +} + +type GetZoneListResult struct { + Zones []ZoneName `json:"zones"` +} + +type ZoneName struct { + ZoneNames []string `json:"apiZoneNames"` + ApiZoneNames []string `json:"zoneNames"` + Available bool `json:"bool"` + DefaultSubnetId string `json:"defaultSubnetId"` +} + +type ListSubnetsArgs struct { + VpcId string `json:"vpcId"` + ZoneName string `json:"zoneName"` +} + +type ListSubnetsResult struct { + Subnets []Subnet `json:"subnets"` +} + +type SecurityIpsRawResult struct { + SecurityIps []string `json:"ip"` +} + +type UpdateSecurityIpsArgs struct { + SecurityIps []string `json:"securityIps"` +} + +type ListParametersResult struct { + Items []Parameter `json:"items"` +} + +type Parameter struct { + Name string `json:"name"` + DefaultValue string `json:"defaultValue"` + Value string `json:"value"` + PendingValue string `json:"pendingValue"` + Type string `json:"type"` + Dynamic bool `json:"dynamic"` + Modifiable bool `json:"modifiable"` + AllowedValues string `json:"allowedValues"` + Desc string `json:"desc"` +} + +type UpdateParameterArgs struct { + Parameters []KVParameter `json:"parameters"` +} + +type KVParameter struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type Snapshot struct { + SnapshotId string `json:"snapshotId"` + SnapshotSizeInBytes string `json:"snapshotSizeInBytes"` + SnapshotType string `json:"snapshotType"` + SnapshotStatus string `json:"snapshotStatus"` + SnapshotStartTime string `json:"snapshotStartTime"` + SnapshotEndTime string `json:"snapshotEndTime"` +} + +type SnapshotModel struct { + SnapshotId string `json:"snapshotId"` + SnapshotSizeInBytes string `json:"snapshotSizeInBytes"` + SnapshotType string `json:"snapshotType"` + SnapshotStatus string `json:"snapshotStatus"` + SnapshotStartTime string `json:"snapshotStartTime"` + SnapshotEndTime string `json:"snapshotEndTime"` + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type BackupDetailResult struct { + Snapshot SnapshotModel `json:"snapshot"` +} + +type Binlog struct { + BinlogId string `json:"binlogId"` + BinlogSizeInBytes int64 `json:"binlogSizeInBytes"` + BinlogStatus string `json:"binlogStatus"` + BinlogStartTime string `json:"binlogStartTime"` + BinlogEndTime string `json:"binlogEndTime"` +} + +type BinlogModel struct { + BinlogId string `json:"binlogId"` + BinlogSizeInBytes int64 `json:"binlogSizeInBytes"` + BinlogStatus string `json:"binlogStatus"` + BinlogStartTime string `json:"binlogStartTime"` + BinlogEndTime string `json:"binlogEndTime"` + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type BinlogListResult struct { + Binlogs []Binlog `json:"binlogs"` +} + +type BinlogDetailResult struct { + Binlog BinlogModel `json:"binlog"` +} + +type AuthType string + +const ( + AuthType_ReadOnly AuthType = "readOnly" + AuthType_ReadWrite AuthType = "readWrite" +) + +type AccountPrivilege struct { + AccountName string `json:"accountName"` + AuthType AuthType `json:"authType"` +} + +type CreateDatabaseArgs struct { + ClientToken string `json:"-"` + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + Remark string `json:"remark"` +} + +type UpdateDatabaseRemarkArgs struct { + Remark string `json:"remark"` +} + +type Database struct { + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + DbStatus string `json:"dbStatus"` + Remark string `json:"remark"` + AccountPrivileges []AccountPrivilege `json:"accountPrivileges"` +} + +type DatabaseResult struct { + Database Database `json:"database"` +} + +type ListDatabaseResult struct { + Databases []Database `json:"databases"` +} + +// Account +type AccountType string + +const ( + AccountType_Super AccountType = "rdssuper" + AccountType_Common AccountType = "common" +) + +type CreateAccountArgs struct { + ClientToken string `json:"-"` + AccountName string `json:"accountName"` + Password string `json:"password"` + // 为了兼容 RDS 参数结构 + AccountType AccountType `json:"type"` + Desc string `json:"remark"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges,omitempty"` +} + +type DatabasePrivilege struct { + DbName string `json:"dbName"` + AuthType string `json:"authType"` +} + +type Account struct { + AccountName string `json:"accountName"` + Desc string `json:"remark"` + Status string `json:"accountStatus"` + AccountType string `json:"accountType"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` +} + +type AccountResult struct { + Account Account `json:"account"` +} + +type ListAccountResult struct { + Accounts []Account `json:"accounts"` +} + +type UpdateAccountPasswordArgs struct { + Password string `json:"password"` +} + +type UpdateAccountDescArgs struct { + Desc string `json:"remark"` +} + +type UpdateAccountPrivilegesArgs struct { + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` +} + +type ListRoGroupResult struct { + RoGroups []RoGroup `json:"roGroups"` +} + +type VpcVo struct { + VpcId string `json:"vpcId"` + ShortId string `json:"shortId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + Status int `json:"status"` + CreateTime string `json:"createTime"` + Description string `json:"description"` + DefaultVpc bool `json:"defaultVpc"` + Ipv6Cidr string `json:"ipv6Cidr"` + AuxiliaryCidr []string `json:"auxiliaryCidr"` + Relay bool `json:"relay"` +} +type GetBackupListArgs struct { + Marker string + MaxKeys int +} +type GetSecurityIpsResult struct { + Etag string `json:"etag"` + SecurityIps []string `json:"securityIps"` +} + +type ResizeRdsArgs struct { + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + NodeAmount int `json:"nodeAmount,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + IsResizeNow bool `json:"isResizeNow,omitempty"` +} + +type RebootArgs struct { + IsRebootNow bool `json:"isRebootNow"` +} + +type SwitchArgs struct { + IsSwitchNow bool `json:"isSwitchNow"` +} + +type MaintainWindow struct { + MaintainTime MaintainTime `json:"maintentime"` +} + +type MaintainTime struct { + Period string `json:"period"` + StartTime string `json:"startTime"` + Duration int `json:"duration"` +} + +type RecycleInstance struct { + EngineVersion string `json:"engineVersion"` + VolumeCapacity int `json:"volumeCapacity"` + ApplicationType string `json:"applicationType"` + InstanceName string `json:"instanceName"` + PublicAccessStatus string `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceType string `json:"instanceType"` + Type string `json:"type"` + InstanceStatus string `json:"instanceStatus"` + MemoryCapacity float64 `json:"memoryCapacity"` + InstanceId string `json:"instanceId"` + Engine string `json:"engine"` + VpcId string `json:"vpcId"` + PubliclyAccessible bool `json:"publiclyAccessible"` + InstanceExpireTime string `json:"instanceExpireTime"` + DiskType string `json:"diskType"` + Region string `json:"region"` + CpuCount int `json:"cpuCount"` + UsedStorage float64 `json:"usedStorage"` +} + +type RecyclerInstanceList struct { + ListResultWithMarker + Result []RecycleInstance `json:"result"` +} + +type BatchInstanceIds struct { + InstanceIds string `json:"instanceIds"` +} + +type SecurityGroup struct { + Name string `json:"name"` + SecurityGroupID string `json:"securityGroupId"` + Description string `json:"description"` + TenantID string `json:"tenantId"` + AssociateNum int `json:"associateNum"` + VpcID string `json:"vpcId"` + VpcShortID string `json:"vpcShortId"` + VpcName string `json:"vpcName"` + CreatedTime string `json:"createdTime"` + Version int `json:"version"` + DefaultSecurityGroup int `json:"defaultSecurityGroup"` +} + +type SecurityGroupArgs struct { + InstanceIds []string `json:"instanceIds"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type ListSecurityGroupResult struct { + Groups []SecurityGroupDetail `json:"groups"` +} + +type SecurityGroupRule struct { + PortRange string `json:"portRange"` + Protocol string `json:"protocol"` + RemoteGroupID string `json:"remoteGroupId"` + RemoteIP string `json:"remoteIP"` + Ethertype string `json:"ethertype"` + TenantID string `json:"tenantId"` + Name string `json:"name"` + ID string `json:"id"` + SecurityGroupRuleID string `json:"securityGroupRuleId"` + Direction string `json:"direction"` +} + +type SecurityGroupDetail struct { + SecurityGroupName string `json:"securityGroupName"` + SecurityGroupID string `json:"securityGroupId"` + SecurityGroupRemark string `json:"securityGroupRemark"` + Inbound []SecurityGroupRule `json:"inbound"` + Outbound []SecurityGroupRule `json:"outbound"` + VpcName string `json:"vpcName"` + VpcID string `json:"vpcId"` + ProjectID string `json:"projectId"` +} + +type ListLogArgs struct { + LogType string `json:"logType"` + Datetime string `json:"datetime"` +} + +type Log struct { + LogStartTime string `json:"logStartTime"` + LogEndTime string `json:"logEndTime"` + LogID string `json:"logId"` + LogSizeInBytes int `json:"logSizeInBytes"` +} + +type LogDetail struct { + Log + DownloadURL string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type GetLogArgs struct { + ValidSeconds int `json:"downloadValidTimeInSec"` +} + +type CreateTableHardLinkArgs struct { + TableName string `json:"tableName"` +} + +type ModifySyncModeArgs struct { + SyncMode string `json:"syncMode"` +} +type Disk struct { + CapacityRatio []string `json:"capacityRatio"` +} + +type AccessDetailItem struct { + BackupID string `json:"backupID"` + AccessDateTime string `json:"accessDateTime"` + AccessResult string `json:"accessResult"` + AccessSrcAddressType string `json:"accessSrcAddressType"` + AvailableZone string `json:"availableZone"` + AccessSrcAddress string `json:"accessSrcAddress"` + AccessOperationType string `json:"accessOperationType"` + StorageType string `json:"storageType"` + StorageAddress string `json:"storageAddress"` + Region string `json:"region"` + BackupName string `json:"backupName"` + AccessSrcAgent string `json:"accessSrcAgent"` + StorageID string `json:"storageID"` +} + +type Pagination struct { + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Marker string `json:"marker"` + TotalKeys int `json:"totalKeys"` +} +type BackupAccessDetail struct { + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` + DataType string `json:"dataType"` + BackupAccessDetails []AccessDetailItem `json:"backupAccessDetails"` + Pagination Pagination `json:"pagination"` +} + +type AccessDetailArgs struct { + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + DataType string `json:"dataType,omitempty"` +} diff --git a/bce-sdk-go/services/ddc/v2/client.go b/bce-sdk-go/services/ddc/v2/client.go new file mode 100644 index 0000000..5b74db6 --- /dev/null +++ b/bce-sdk-go/services/ddc/v2/client.go @@ -0,0 +1,353 @@ +package ddcrds + +import ( + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/rds" +) + +const ( + DEFAULT_ENDPOINT = "ddc.su.baidubce.com" + DDC_NOT_SUPPORTED = "DDC does not support this feature." + RDS_NOT_SUPPORTED = "RDS does not support this feature." + URI_PREFIX = bce.URI_PREFIX + "v1/ddc" + URI_PREFIX_V2 = bce.URI_PREFIX + "v2/ddc" + REQUEST_DDC_INSTANCE_URL = "/instance" + REQUEST_DDC_POOL_URL = "/pool" + REQUEST_DDC_HOST_URL = "/host" + REQUEST_DDC_TASK_URL = "/task" + REQUEST_DDC_DEPLOY_URL = "/deploy" + REQUEST_DDC_DATABASE_URL = "/database" + REQUEST_DDC_TABLE_URL = "/table" + REQUEST_DDC_HARDLINK_URL = "/link" + REQUEST_DDC_ACCOUNT_URL = "/account" + REQUEST_DDC_ROGROUP_URL = "/roGroup" + REQUEST_DDC_RECYCLER_URL = "/recycler" + REQUEST_DDC_SECURITYGROUP_URL = "/security" + REQUEST_DDC_LOG_URL = "/logs" + REQUEST_DDC_UPDATE_ACTION = "/update" + REQUEST_DDC_MAINTAINTIME_URL = "/maintenTimeInfo" + REQUEST_UPDATE_MAINTAINTIME_URL = "/updateMaintenTime" +) + +func DDCNotSupportError() error { + return fmt.Errorf(DDC_NOT_SUPPORTED) +} +func RDSNotSupportError() error { + return fmt.Errorf(RDS_NOT_SUPPORTED) +} + +// Client of DDC service is a kind of BceClient, so derived from BceClient +type Client struct { + rdsClient *rds.Client + ddcClient *DDCClient +} + +func (c *Client) Config(config *bce.BceClientConfiguration) { + c.ddcClient.Config = config + c.rdsClient.Config = config +} + +func (c *Client) ConfigEndpoint(endPoint string) { + // 替换Endpoint,优先创建ddc Client + ddcEndpoint := strings.Replace(endPoint, "rds.", "ddc.", 1) + c.ddcClient.Config.Endpoint = ddcEndpoint + // 替换Endpoint + rdsEndpoint := strings.Replace(endPoint, "ddc.", "rds.", 1) + c.rdsClient.Config.Endpoint = rdsEndpoint +} + +func (c *Client) ConfigRegion(region string) { + c.ddcClient.Config.Region = region + c.rdsClient.Config.Region = region +} + +func (c *Client) ConfigRetry(policy bce.RetryPolicy) { + c.ddcClient.Config.Retry = policy + c.rdsClient.Config.Retry = policy +} + +func (c *Client) ConfigSignOption(option *auth.SignOptions) { + c.ddcClient.Config.SignOption = option + c.rdsClient.Config.SignOption = option +} + +func (c *Client) ConfigSignOptionHeadersToSign(header map[string]struct{}) { + c.ddcClient.Config.SignOption.HeadersToSign = header + c.rdsClient.Config.SignOption.HeadersToSign = header +} + +func (c *Client) ConfigSignOptionExpireSeconds(seconds int) { + c.ddcClient.Config.SignOption.ExpireSeconds = seconds + c.rdsClient.Config.SignOption.ExpireSeconds = seconds +} + +func (c *Client) ConfigCredentials(credentials *auth.BceCredentials) { + c.ddcClient.Config.Credentials = credentials + c.rdsClient.Config.Credentials = credentials +} + +func (c *Client) ConfigProxyUrl(proxyUrl string) { + c.ddcClient.Config.ProxyUrl = proxyUrl + c.rdsClient.Config.ProxyUrl = proxyUrl +} + +func (c *Client) ConfigConnectionTimeoutInMillis(millis int) { + c.ddcClient.Config.ConnectionTimeoutInMillis = millis + c.rdsClient.Config.ConnectionTimeoutInMillis = millis +} + +// 内部创建rds和ddc两个client +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + // 替换Endpoint,优先创建ddc Client + ddcEndpoint := strings.Replace(endPoint, "rds.", "ddc.", 1) + ddcClient, err := NewDDCClient(ak, sk, ddcEndpoint) + if err != nil { + return nil, err + } + // 替换Endpoint + rdsEndpoint := strings.Replace(endPoint, "ddc.", "rds.", 1) + rdsClient, err := rds.NewClient(ak, sk, rdsEndpoint) + if err != nil { + return nil, err + } + return &Client{rdsClient: rdsClient, ddcClient: ddcClient}, nil +} + +// Client for DDC service +type DDCClient struct { + *bce.BceClient +} + +func NewDDCClient(ak, sk, endPoint string) (*DDCClient, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &DDCClient{client}, nil +} + +func getDdcUri() string { + return URI_PREFIX +} + +func getDdcInstanceUri() string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL +} + +// Pool URL +func getPoolUri() string { + return URI_PREFIX + REQUEST_DDC_POOL_URL +} + +func getPoolUriWithId(poolId string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId +} + +// Host URL +func getHostUri() string { + return URI_PREFIX + REQUEST_DDC_HOST_URL +} + +func getHostUriWithPnetIp(poolId, hostPnetIP string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_HOST_URL + "/" + hostPnetIP +} + +// DeploySet URL +func getDeploySetUri(poolId string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_DEPLOY_URL +} + +func getDeploySetUriWithId(poolId, id string) string { + return URI_PREFIX + REQUEST_DDC_POOL_URL + "/" + poolId + REQUEST_DDC_DEPLOY_URL + "/" + id +} + +func getDdcUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId +} + +func getDdcUriWithInstanceIdV2(instanceId string) string { + return URI_PREFIX_V2 + REQUEST_DDC_INSTANCE_URL + "/" + instanceId +} + +// Database URL +func getDatabaseUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL +} + +func getDatabaseUriWithDbName(instanceId string, dbName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL + "/" + dbName +} + +func getQueryDatabaseUriWithDbName(instanceId string, dbName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL + "/" + dbName + "/amount" +} + +func getDatabaseDiskUsageUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL + "/usage" +} + +func getDatabaseRecoverTimeUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_DATABASE_URL + "/recoverableDateTimes" +} + +func getRecoverInstanceDatabaseUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/recoveryToSourceInstanceByDatetime" +} + +// Account URL +func getAccountUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ACCOUNT_URL +} + +func getAccountUriWithAccountName(instanceId string, accountName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ACCOUNT_URL + "/" + accountName +} + +// RoGroup URL +func getRoGroupUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_ROGROUP_URL +} + +// MaintainTime URL +func getMaintainTimeUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_MAINTAINTIME_URL +} + +// MaintainTime URL +func getUpdateMaintainTimeUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_UPDATE_MAINTAINTIME_URL +} + +// RoGroup URL +func getUpdateRoGroupUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + REQUEST_DDC_UPDATE_ACTION +} + +// RoGroupWeight URL +func getUpdateRoGroupWeightUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + "/updateWeight" +} + +// ReBalance RoGroup URL +func getReBalanceRoGroupUriWithId(roGroupId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + REQUEST_DDC_ROGROUP_URL + "/" + roGroupId + "/balanceRoLoad" +} + +// Recycler URL +func getRecyclerUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL +} + +// Recycler Recover URL +func getRecyclerRecoverUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL + "/batchRecover" +} + +// Recycler Recover URL +func getRecyclerDeleteUrl() string { + return URI_PREFIX + REQUEST_DDC_RECYCLER_URL + "/batchDelete" +} + +// List Security Group By Vpc URL +func getSecurityGroupWithVpcIdUrl(vpcId string) string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/" + vpcId + "/listByVpc" +} + +// List Security Group By Instance URL +func getSecurityGroupWithInstanceIdUrl(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/" + instanceId + "/list" +} + +// Bind Security Group To Instance URL +func getBindSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/bind" +} + +// UnBind Security Group To Instance URL +func getUnBindSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/unbind" +} + +// Batch Replace Security Group URL +func getReplaceSecurityGroupWithUrl() string { + return URI_PREFIX + REQUEST_DDC_SECURITYGROUP_URL + "/updateSecurityGroup" +} + +func getAccessLogUrl() string { + return URI_PREFIX + REQUEST_DDC_LOG_URL + "/accessLog" +} + +func getLogsUrlWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL +} + +func getLogsUrlWithLogId(instanceId, logId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL + "/" + logId +} + +func getErrorLogsUrlWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL + "/logErrorDetail" +} + +func getSlowLogsUrlWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + REQUEST_DDC_LOG_URL + "/logSlowDetail" +} + +func getCreateTableHardLinkUrl(instanceId, dbName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + + REQUEST_DDC_DATABASE_URL + "/" + dbName + + REQUEST_DDC_TABLE_URL + REQUEST_DDC_HARDLINK_URL +} + +func getTableHardLinkUrl(instanceId, dbName, tableName string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + + REQUEST_DDC_DATABASE_URL + "/" + dbName + + REQUEST_DDC_TABLE_URL + "/" + tableName + REQUEST_DDC_HARDLINK_URL +} + +func getChangeSemiSyncStatusUrlWithId(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/semisync" +} + +func getKillSessionUri(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/session/kill" +} + +func getKillSessionTaskUri(instanceId string, taskId int) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/session/task" + "/" + strconv.Itoa(taskId) +} + +func getMaintainTaskUri() string { + return URI_PREFIX + REQUEST_DDC_TASK_URL +} + +func getMaintainTaskDetailUri() string { + return URI_PREFIX + REQUEST_DDC_TASK_URL + "/detail" +} + +func getMaintainTaskUriWithTaskId(taskId string) string { + return URI_PREFIX + REQUEST_DDC_TASK_URL + "/" + taskId +} + +func getInstanceBackupStatusUrl(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/backupStatus" +} + +func getInstanceSyncDelayUrl(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/sync_delay" +} + +func getInstanceSyncDelayReplicationUrl(instanceId string) string { + return URI_PREFIX + REQUEST_DDC_INSTANCE_URL + "/" + instanceId + "/replication" +} diff --git a/bce-sdk-go/services/ddc/v2/client_test.go b/bce-sdk-go/services/ddc/v2/client_test.go new file mode 100644 index 0000000..97a6d84 --- /dev/null +++ b/bce-sdk-go/services/ddc/v2/client_test.go @@ -0,0 +1,1811 @@ +package ddcrds + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + DDCRDS_CLIENT *Client + DDC_ID string = "ddc-m8rs4yjz" + ACCOUNT_NAME string = "go_sdk_account_2" + ACCOUNT_PASSWORD string = "go_sdk_password_1" + ACCOUNT_REMARK string = "go-sdk-remark-1" + DB_NAME string = "go_sdk_db_1" + DB_CHARACTER_SET_NAME string = "utf8" + DB_REMARK string = "go_sdk_db_remark" + TASK_ID string = "1173906" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +const ( + SDK_NAME_PREFIX = "sdk_rds_" + POOL = "xdb_005a2d79-a4f4-4bfb-8284-0ffe9ddaa307_pool" + PNETIP = "100.88.65.121" + DEPLOY_ID = "ab89d829-9068-d88e-75bc-64bb6367d036" + DDC_INSTANCE_ID = "ddc-mqv3e72u" + RDS_INSTANCE_ID = "rds-OtTkC1OD" + ETAG = "v0" +) + +var instanceId = DDC_INSTANCE_ID +var client = DDCRDS_CLIENT + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 2; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + panic("config json file of ak/sk not given:") + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DDCRDS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + client = DDCRDS_CLIENT + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func assertAvailable(instanceId string, t *testing.T) { + result, err := DDCRDS_CLIENT.GetDetail(instanceId) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Available", result.InstanceStatus) +} + +func TestClient_CreateInstance(t *testing.T) { + args := &CreateRdsArgs{ + ClientToken: "81adc02cf0221a753d1ef969eb6c6360", + Billing: Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + PurchaseCount: 1, + InstanceName: "go_sdk_tester", + Engine: "mysql", + EngineVersion: "5.7", + Category: "Standard", + CpuCount: 2, + MemoryCapacity: 4, + VolumeCapacity: 50, + IsDirectPay: true, + AutoRenewTime: 1, + AutoRenewTimeUnit: "month", + PoolId: "xdb_9c72b2ea-a24c-41ba-b6c7-fc4eb7e8f538_pool", + VpcId: "vpc-4mcfvqcitav5", + ZoneNames: []string{"cn-bj-a"}, + Subnets: []SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-izx7eq3wy87e", + }, + }, + } + result, err := DDCRDS_CLIENT.CreateRds(args, "ddc") + ExpectEqual(t.Errorf, nil, err) + fmt.Println("create ddc success, orderId: ", result.OrderId) + for _, e := range result.InstanceIds { + fmt.Println("create ddc success, instanceId: ", e) + } +} + +func TestClient_ListDeploySets(t *testing.T) { + result, err := client.ListDeploySets(POOL, nil) + if err != nil { + fmt.Printf("list deploy set error: %+v\n", err) + return + } + + for i := range result.Result { + deploy := result.Result[i] + fmt.Println("ddc deploy id: ", deploy.DeployID) + fmt.Println("ddc deploy name: ", deploy.DeployName) + fmt.Println("ddc deploy strategy: ", deploy.Strategy) + fmt.Println("ddc deploy create time: ", deploy.CreateTime) + fmt.Println("ddc deploy centralizeThreshold: ", deploy.CentralizeThreshold) + fmt.Println("ddc instance ids: ", deploy.Instances) + } +} + +func TestClient_GetDeploySet(t *testing.T) { + deploy, err := DDCRDS_CLIENT.GetDeploySet(POOL, DEPLOY_ID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, DEPLOY_ID, deploy.DeployID) +} + +func TestClient_DeleteDeploySet(t *testing.T) { + err := DDCRDS_CLIENT.DeleteDeploySet(POOL, "b444142c-69ed-87a6-396b-fc4a76a9754f") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateDeploySet(t *testing.T) { + result, err := DDCRDS_CLIENT.CreateDeploySet(POOL, &CreateDeployRequest{ + DeployName: "api-from-go4", + CentralizeThreshold: 10, + }) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(result.DeployID) +} + +func TestClient_UpdateDeploySet(t *testing.T) { + deployId := "1424c210-f998-1072-9608-9def4d93dec7" + //err := DDCRDS_CLIENT.UpdateDeploySet(POOL, deployId, &UpdateDeployRequest{ + // Strategy: "centralized", + // CentralizeThreshold: 23, + //}) + //ExpectEqual(t.Errorf, nil, err) + + client := DDCRDS_CLIENT + args := &UpdateDeployRequest{ + // 幂等 Token + ClientToken: "xxxyyyzzz", + // 部署策略 支持集中部署(centralized)/完全打散(distributed) + Strategy: "distributed", + // 亲和度阈值 取值范围【0-96】,必须大于原亲和度阈值 + CentralizeThreshold: 30, + } + err := client.UpdateDeploySet(POOL, deployId, args) + if err != nil { + fmt.Printf("update deploy set error: %+v\n", err) + return + } + + fmt.Println("update deploy set success.") +} + +func TestClient_ListParameters(t *testing.T) { + parameters, err := DDCRDS_CLIENT.ListParameters(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + res, err := json.Marshal(parameters.Parameters) + fmt.Println(len(parameters.Parameters)) + + parameters, err = DDCRDS_CLIENT.ListParameters(RDS_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + res, err = json.Marshal(parameters.Parameters[:3]) + fmt.Println(string(res)) + fmt.Println(string(parameters.Etag)) +} + +func TestClient_UpdateParameter(t *testing.T) { + + instances := getRdsList(t) + for _, e := range *instances { + if "Available" == e.InstanceStatus { + res, err := DDCRDS_CLIENT.ListParameters(e.InstanceId) + ExpectEqual(t.Errorf, nil, err) + args := &UpdateParameterArgs{ + Parameters: []KVParameter{ + { + Name: "auto_increment_increment", + Value: "2", + }, + }, + WaitSwitch: 0, + } + result, er := DDCRDS_CLIENT.UpdateParameter(e.InstanceId, res.Etag, args) + ExpectEqual(t.Errorf, nil, er) + if result != nil { + fmt.Println("update parameter task success: ", result.Result.TaskID) + TASK_ID = result.Result.TaskID + TestClient_GetMaintainTaskDetail(t) + } else { + fmt.Println("update parameter task success.") + } + break + } + } +} + +func getRdsList(t *testing.T) *[]Instance { + listRdsArgs := &ListRdsArgs{} + result, err := DDCRDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + return &result.Instances +} + +func TestClient_GetSecurityIps(t *testing.T) { + ips, err := DDCRDS_CLIENT.GetSecurityIps(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, ips, ips) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(ips.SecurityIps) + + ips, err = DDCRDS_CLIENT.GetSecurityIps(RDS_INSTANCE_ID) + ExpectEqual(t.Errorf, ips, ips) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(ips.SecurityIps) +} + +// Only DDC +func TestClient_ListRoGroup(t *testing.T) { + ips, err := DDCRDS_CLIENT.ListRoGroup(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, ips, ips) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(ips) +} + +// Only DDC +func TestClient_UpdateRoGroup(t *testing.T) { + detail, err := client.GetDetail(instanceId) + if err != nil { + fmt.Printf("get ddc detail error: %+v\n", err) + return + } + roGroupList := detail.RoGroupList + if len(roGroupList) < 1 { + fmt.Printf("the ddc instance %s do not have roGroup \n", instanceId) + } + roGroupId := roGroupList[0].RoGroupID + fmt.Println(roGroupId) + args := &UpdateRoGroupArgs{ + RoGroupName: "testRo", + IsBalanceRoLoad: "1", + EnableDelayOff: "1", + DelayThreshold: "0", + LeastInstanceAmount: "1", + MasterDelay: "1", + } + err = client.UpdateRoGroup(roGroupId, args, "ddc") + if err != nil { + fmt.Printf("update ddc roGroup error: %+v\n", err) + return + } + fmt.Println("update ddc roGroup success") +} + +// Only DDC +func TestClient_UpdateRoGroupReplicaWeight(t *testing.T) { + detail, err := client.GetDetail(instanceId) + if err != nil { + fmt.Printf("get ddc detail error: %+v\n", err) + return + } + roGroupList := detail.RoGroupList + if len(roGroupList) < 1 { + fmt.Printf("the ddc instance %s do not have roGroup \n", instanceId) + } + roGroupId := roGroupList[0].RoGroupID + fmt.Println(roGroupId) + replicaId := roGroupList[0].ReplicaList[0].InstanceId + replicaWeight := ReplicaWeight{ + InstanceId: replicaId, + Weight: 20, + } + args := &UpdateRoGroupWeightArgs{ + RoGroupName: "testRo", + EnableDelayOff: "1", + DelayThreshold: "", + LeastInstanceAmount: "0", + IsBalanceRoLoad: "0", + MasterDelay: "1", + ReplicaList: []ReplicaWeight{replicaWeight}, + } + err = client.UpdateRoGroupReplicaWeight(roGroupId, args, "ddc") + if err != nil { + fmt.Printf("update ddc roGroup replica weight error: %+v\n", err) + return + } + fmt.Println("update ddc roGroup replica weight success") +} + +// Only DDC +func TestClient_ReBalanceRoGroup(t *testing.T) { + detail, err := client.GetDetail(instanceId) + if err != nil { + fmt.Printf("get ddc detail error: %+v\n", err) + return + } + roGroupList := detail.RoGroupList + if len(roGroupList) < 1 { + fmt.Printf("the ddc instance %s do not have roGroup \n", instanceId) + } + roGroupId := roGroupList[0].RoGroupID + fmt.Println(roGroupId) + err = client.ReBalanceRoGroup(roGroupId, "ddc") + if err != nil { + fmt.Printf("reBalance ddc roGroup error: %+v\n", err) + return + } + fmt.Println("reBalance ddc roGroup success") +} + +// Only DDC +func TestClient_ListPool(t *testing.T) { + //pools, err := DDCRDS_CLIENT.ListPool(nil, "ddc") + result, err := client.ListPool(nil, "ddc") + if err != nil { + fmt.Printf("list pool error: %+v\n", err) + return + } + + for i := range result.Result { + pool := result.Result[i] + fmt.Println("ddc pool id: ", pool.PoolID) + fmt.Println("ddc pool vpc id: ", pool.VpcID) + fmt.Println("ddc pool engine: ", pool.Engine) + fmt.Println("ddc pool create time: ", pool.CreateTime) + fmt.Println("ddc pool hosts: ", pool.Hosts) + } +} + +// Only DDC +func TestClient_ListVpc(t *testing.T) { + vpc, err := DDCRDS_CLIENT.ListVpc("ddc") + ExpectEqual(t.Errorf, vpc, vpc) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(vpc) +} + +func TestClient_GetDetail(t *testing.T) { + instanceId = "ddc-mqqint6z" + result, err := DDCRDS_CLIENT.GetDetail(instanceId) + ExpectEqual(t.Errorf, err, nil) + fmt.Println("ddc instanceId: ", result.InstanceId) + fmt.Println("ddc instanceName: ", result.InstanceName) + fmt.Println("ddc engine: ", result.Engine) + fmt.Println("ddc engineVersion: ", result.EngineVersion) + fmt.Println("ddc instanceStatus: ", result.InstanceStatus) + fmt.Println("ddc cpuCount: ", result.CpuCount) + fmt.Println("ddc memoryCapacity: ", result.MemoryCapacity) + fmt.Println("ddc volumeCapacity: ", result.VolumeCapacity) + fmt.Println("ddc usedStorage: ", result.UsedStorage) + fmt.Println("ddc paymentTiming: ", result.PaymentTiming) + fmt.Println("ddc instanceType: ", result.InstanceType) + fmt.Println("ddc instanceCreateTime: ", result.InstanceCreateTime) + fmt.Println("ddc instanceExpireTime: ", result.InstanceExpireTime) + fmt.Println("ddc publicAccessStatus: ", result.PublicAccessStatus) + fmt.Println("ddc vpcId: ", result.VpcId) + fmt.Println("ddc Subnets: ", result.Subnets) + fmt.Println("ddc BackupPolicy: ", result.BackupPolicy) + fmt.Println("ddc RoGroupList: ", result.RoGroupList) + fmt.Println("ddc NodeMaster: ", result.NodeMaster) + fmt.Println("master BBC hostname: ", result.NodeMaster.HostName) + fmt.Println("ddc NodeSlave: ", result.NodeSlave) + fmt.Println("slave BBC hostname: ", result.NodeSlave.HostName) + fmt.Println("ddc NodeReadReplica: ", result.NodeReadReplica) + fmt.Println("ddc DeployId: ", result.DeployId) + fmt.Println("ddc SyncMode: ", result.SyncMode) + fmt.Println("ddc Category: ", result.Category) + fmt.Println("ddc ZoneNames: ", result.ZoneNames) + fmt.Println("ddc Endpoint: ", result.Endpoint) + fmt.Println("ddc Endpoint vnetIpBackup: ", result.Endpoint.VnetIpBackup) + fmt.Println("ddc long BBC Id: ", result.LongBBCId) + fmt.Println("ddc topo: ", result.InstanceTopoForReadonly) + // 自动续费规则 + if result.AutoRenewRule != nil { + fmt.Println("ddc renewTime: ", result.AutoRenewRule.RenewTime) + fmt.Println("ddc renewTimeUnit: ", result.AutoRenewRule.RenewTimeUnit) + } +} + +func TestClient_UpdateSecurityIps(t *testing.T) { + err := DDCRDS_CLIENT.UpdateSecurityIps(DDC_INSTANCE_ID, "", &UpdateSecurityIpsArgs{ + SecurityIps: []string{ + "10.10.0.0/16", + }, + }) + ExpectEqual(t.Errorf, nil, err) + + instances := getRdsList(t) + for _, e := range *instances { + if "Available" == e.InstanceStatus { + res, err := DDCRDS_CLIENT.GetSecurityIps(e.InstanceId) + ExpectEqual(t.Errorf, nil, err) + args := &UpdateSecurityIpsArgs{ + SecurityIps: []string{ + "%", + "192.0.0.1", + "192.0.0.2", + }, + } + er := DDCRDS_CLIENT.UpdateSecurityIps(e.InstanceId, res.Etag, args) + ExpectEqual(t.Errorf, nil, er) + } + } +} + +func TestClient_GetBackupList(t *testing.T) { + args := &GetBackupListArgs{ + MaxKeys: 3, + } + list, err := DDCRDS_CLIENT.GetBackupList(DDC_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 3, list.MaxKeys) + res, err := json.Marshal(list.Backups) + fmt.Println(string(res)) + + list, err = DDCRDS_CLIENT.GetBackupList(RDS_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 3, list.MaxKeys) + res, err = json.Marshal(list.Backups) + fmt.Println(string(res)) +} + +// Only DDC +func TestClient_GetBackupDetail(t *testing.T) { + list, err := DDCRDS_CLIENT.GetBackupDetail(DDC_INSTANCE_ID, "1612625400590890206") + ExpectEqual(t.Errorf, nil, err) + fmt.Println(Json(list.Snapshot)) + + list, err = DDCRDS_CLIENT.GetBackupDetail(RDS_INSTANCE_ID, "snap-rdsmi35jqm07uyg-2021_02_07T17_00_19Z") + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Only DDC +func TestClient_CreateBackup(t *testing.T) { + err := DDCRDS_CLIENT.CreateBackup(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + err = DDCRDS_CLIENT.CreateBackup(RDS_INSTANCE_ID) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Only DDC +func TestClient_ModifyBackupPolicy(t *testing.T) { + err := DDCRDS_CLIENT.ModifyBackupPolicy(DDC_INSTANCE_ID, &BackupPolicy{ + BackupDays: "0,1,2,3,5", + ExpireInDaysInt: 100, + BackupTime: "17:00:00Z", + }) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.ModifyBackupPolicy(RDS_INSTANCE_ID, &BackupPolicy{ + BackupDays: "0,1,2,3,5", + ExpireInDaysInt: 100, + BackupTime: "17:00:00Z", + }) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// ddc 有多可用区 +func TestClient_GetZoneList(t *testing.T) { + list, err := DDCRDS_CLIENT.GetZoneList("ddc") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, list, list) + fmt.Println(Json(list)) + + list, err = DDCRDS_CLIENT.GetZoneList("rds") + ExpectEqual(t.Errorf, nil, err) + fmt.Println(Json(list)) +} + +// rds使用apiZoneNames,ddc使用zoneNames逻辑可用区 +func TestClient_ListSubnets(t *testing.T) { + //args := &ListSubnetsArgs{VpcId: "vpc-fhsajv3w2j7h"} + subnets, err := DDCRDS_CLIENT.ListSubnets(nil, "ddc") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, subnets, subnets) + fmt.Println(Json(subnets)) + + //args = &ListSubnetsArgs{ZoneName: "cn-su-a"} + //subnets, err = DDCRDS_CLIENT.ListSubnets(args, "rds") + //ExpectEqual(t.Errorf, nil, err) + //fmt.Println(Json(subnets)) +} + +// Only DDC +func TestClient_GetBinlogList(t *testing.T) { + // 获取两天前的日志备份 + yesterday := time.Now(). + AddDate(0, 0, -2). + Format("2006-01-02T15:04:05Z") + list, err := DDCRDS_CLIENT.GetBinlogList(DDC_INSTANCE_ID, yesterday) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(Json(list.Binlogs)) + + list, err = DDCRDS_CLIENT.GetBinlogList(RDS_INSTANCE_ID, "") + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Only DDC +func TestClient_GetBinlogDetail(t *testing.T) { + detail, err := DDCRDS_CLIENT.GetBinlogDetail(DDC_INSTANCE_ID, "1612713949797105170") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, detail.Binlog.BinlogId, "1612713949797105170") + fmt.Println(Json(detail)) + + detail, err = DDCRDS_CLIENT.GetBinlogDetail(RDS_INSTANCE_ID, "1612355516300875570") + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_DeleteDdcInstance(t *testing.T) { + instanceId := "rds-EOWuPqrI,rds-OtTkC1OD,ddc-m1e78f5f" + err := DDCRDS_CLIENT.DeleteRds(instanceId) + ExpectEqual(t.Errorf, nil, err) +} + +// Only DDC +func TestClient_SwitchInstance(t *testing.T) { + instanceId = "ddc-m8xc5hmz" + args := &SwitchArgs{ + IsSwitchNow: false, + } + result, err := client.SwitchInstance(instanceId, args) + if err != nil { + fmt.Printf(" main standby switching of the instance error: %+v\n", err) + return + } + if result != nil { + fmt.Printf(" main standby switching of the instance success, taskId: %v\n", result.Result.TaskID) + } else { + fmt.Printf(" main standby switching of the instance success\n") + } +} + +// Database +func TestClient_CreateDatabase(t *testing.T) { + args := &CreateDatabaseArgs{ + ClientToken: getClientToken(), + DbName: DB_NAME, + CharacterSetName: DB_CHARACTER_SET_NAME, + Remark: DB_REMARK, + } + + err := DDCRDS_CLIENT.CreateDatabase(DDC_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.CreateDatabase(RDS_INSTANCE_ID, args) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_GetDatabase(t *testing.T) { + result, err := DDCRDS_CLIENT.GetDatabase(DDC_INSTANCE_ID, DB_NAME) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Available", result.DbStatus) + + result, err = DDCRDS_CLIENT.GetDatabase(RDS_INSTANCE_ID, DB_NAME) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_ListDatabase(t *testing.T) { + result, err := DDCRDS_CLIENT.ListDatabase(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Databases { + if e.DbName == DB_NAME { + ExpectEqual(t.Errorf, "available", e.DbStatus) + } + fmt.Println("ddc dbStatus: ", e.DbStatus) + fmt.Println("ddc remark: ", e.Remark) + fmt.Println("ddc accountPrivileges: ", e.AccountPrivileges) + } + + result, err = DDCRDS_CLIENT.ListDatabase(RDS_INSTANCE_ID) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_GetTableAmount(t *testing.T) { + args := &GetTableAmountArgs{ + InstanceId: instanceId, + DbName: "test1", + Pattern: "0", + } + result, err := DDCRDS_CLIENT.GetTableAmount(args) + if err != nil { + fmt.Printf("get table amount error: %+v\n", err) + return + } + fmt.Printf("get table amount success.\n") + fmt.Println("ddc return amount ", result.ReturnAmount) + fmt.Println("ddc total amount ", result.TotalAmount) + for k, v := range result.Tables { + fmt.Println("ddc table ", k, " size: ", v) + } +} + +func TestClient_GetDatabaseDiskUsage(t *testing.T) { + result, err := DDCRDS_CLIENT.GetDatabaseDiskUsage(instanceId, "") + if err != nil { + fmt.Printf("get database disk usage error: %+v\n", err) + return + } + fmt.Printf("get database disk usage success.\n") + fmt.Println("ddc rest disk size(byte) ", result.RestDisk) + fmt.Println("ddc used disk size(byte) ", result.UsedDisk) + for k, v := range result.Databases { + fmt.Println("ddc table ", k, " size: ", v) + } +} + +func TestClient_GetRecoverableDateTime(t *testing.T) { + result, err := DDCRDS_CLIENT.GetRecoverableDateTime(instanceId) + if err != nil { + fmt.Printf("get recoverable datetimes error: %+v\n", err) + return + } + fmt.Printf("get recoverable datetimes success.\n") + for _, e := range result.RecoverableDateTimes { + fmt.Println("recover startTime: ", e.StartDateTime, " endTime: ", e.EndDateTime) + } +} + +func TestClient_RecoverToSourceInstanceByDatetime(t *testing.T) { + dbName := "test2" + tableName := "app_1" + args := &RecoverInstanceArgs{ + Datetime: "2021-11-03T11:38:04Z", + RecoverData: []RecoverData{ + { + DbName: dbName, + NewDbName: dbName + "_new", + RestoreMode: "database", + }, + { + DbName: dbName, + NewDbName: dbName + "_new", + RestoreMode: "table", + RecoverTables: []RecoverTable{ + { + TableName: tableName, + NewTableName: tableName + "_new", + }, + }, + }, + }, + } + taskResult, err := DDCRDS_CLIENT.RecoverToSourceInstanceByDatetime(instanceId, args) + if err != nil { + fmt.Printf("recover instance database error: %+v\n", err) + return + } + fmt.Printf("recover instance database success. taskId:%s\n", taskResult.TaskID) +} +func TestClient_UpdateDatabaseRemark(t *testing.T) { + args := &UpdateDatabaseRemarkArgs{ + Remark: DB_REMARK + "_update", + } + err := DDCRDS_CLIENT.UpdateDatabaseRemark(DDC_INSTANCE_ID, DB_NAME, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.UpdateDatabaseRemark(RDS_INSTANCE_ID, DB_NAME, args) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_DeleteDatabase(t *testing.T) { + err := DDCRDS_CLIENT.DeleteDatabase(DDC_INSTANCE_ID, DB_NAME) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.DeleteDatabase(RDS_INSTANCE_ID, DB_NAME) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Account +func TestClient_CreateAccount(t *testing.T) { + args := &CreateAccountArgs{ + ClientToken: getClientToken(), + AccountName: ACCOUNT_NAME, + Password: ACCOUNT_PASSWORD, + AccountType: "Common", + Desc: ACCOUNT_REMARK, + DatabasePrivileges: []DatabasePrivilege{ + { + DbName: DB_NAME, + AuthType: "ReadOnly", + }, + }, + } + + err := DDCRDS_CLIENT.CreateAccount(DDC_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.CreateAccount(RDS_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAccount(t *testing.T) { + result, err := DDCRDS_CLIENT.GetAccount(DDC_INSTANCE_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Available", result.Status) + + result, err = DDCRDS_CLIENT.GetAccount(RDS_INSTANCE_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(Json(result)) + ExpectEqual(t.Errorf, "Available", result.Status) +} + +func TestClient_ListAccount(t *testing.T) { + result, err := DDCRDS_CLIENT.ListAccount(DDC_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Accounts { + if e.AccountName == ACCOUNT_NAME { + ExpectEqual(t.Errorf, "Available", e.Status) + } + } + + result, err = DDCRDS_CLIENT.ListAccount(RDS_INSTANCE_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Accounts { + if e.AccountName == ACCOUNT_NAME { + ExpectEqual(t.Errorf, "Available", e.Status) + } + } +} + +// Only DDC +func TestClient_UpdateAccountPassword(t *testing.T) { + args := &UpdateAccountPasswordArgs{ + Password: ACCOUNT_PASSWORD + "_update", + } + err := DDCRDS_CLIENT.UpdateAccountPassword(DDC_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.UpdateAccountPassword(RDS_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Only DDC +func TestClient_UpdateAccountDesc(t *testing.T) { + args := &UpdateAccountDescArgs{ + Desc: ACCOUNT_REMARK + "_update", + } + err := DDCRDS_CLIENT.UpdateAccountDesc(DDC_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.UpdateAccountDesc(RDS_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +// Only DDC +func TestClient_UpdateAccountPrivileges(t *testing.T) { + databasePrivileges := []DatabasePrivilege{ + { + DbName: DB_NAME, + AuthType: "ReadWrite", + }, + } + args := &UpdateAccountPrivilegesArgs{ + DatabasePrivileges: databasePrivileges, + } + err := DDCRDS_CLIENT.UpdateAccountPrivileges(DDC_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.UpdateAccountPrivileges(RDS_INSTANCE_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, RDSNotSupportError(), err) +} + +func TestClient_DeleteAccount(t *testing.T) { + err := DDCRDS_CLIENT.DeleteAccount(DDC_INSTANCE_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) + + err = DDCRDS_CLIENT.DeleteAccount(RDS_INSTANCE_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) +} + +// util +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateRds(t *testing.T) { + args := &CreateRdsArgs{ + PurchaseCount: 1, + InstanceName: "mysql57fromgo", + //SourceInstanceId: "ddc-mmqptugx", + Engine: "mysql", + EngineVersion: "5.7", + CpuCount: 1, + MemoryCapacity: 1, + VolumeCapacity: 50, + Billing: Billing{ + PaymentTiming: "Postpaid", + Reservation: Reservation{ReservationLength: 1, ReservationTimeUnit: "Month"}, + }, + VpcId: "vpc-fhsajv3w2j7h", + ZoneNames: []string{ + "cn-bj-a", + }, + Subnets: []SubnetMap{ + { + ZoneName: "cn-bj-a", + SubnetId: "sbn-7zdak3vr8jv2", + }, + }, + //DeployId: "86be443c-a40d-be6a-58d5-e3aedc966cc1", + PoolId: "xdb_005a2d79-a4f4-4bfb-8284-0ffe9ddaa307_pool", + Category: STANDARD, + SyncMode: "Semi_sync", + } + subnetArgs := &ListSubnetsArgs{VpcId: args.VpcId} + resp, err := DDCRDS_CLIENT.ListSubnets(subnetArgs, "ddc") + ExpectEqual(t.Errorf, nil, err) + if len(resp.Subnets) > 0 { + subnet := resp.Subnets[0] + fmt.Printf("ddc use subnet: %s\n", Json(subnet)) + // DDC使用ShortId + args.Subnets[0].SubnetId = subnet.ShortId + } + ddc, err := DDCRDS_CLIENT.CreateRds(args, "ddc") + ExpectEqual(t.Errorf, nil, err) + fmt.Println(Json(ddc)) + + // 修改VPCId和子网Id + //subnetArgs = &ListSubnetsArgs{ZoneName: "cn-su-c"} + //resp, err = DDCRDS_CLIENT.ListSubnets(subnetArgs, "rds") + //ExpectEqual(t.Errorf, nil, err) + //if len(resp.Subnets) > 0 { + // subnet := resp.Subnets[0] + // fmt.Printf("rds use subnet: %s\n", Json(subnet)) + // args.VpcId = subnet.VpcId + // args.Subnets[0].SubnetId = subnet.ShortId + //} + //rds, err := DDCRDS_CLIENT.CreateRds(args, "rds") + //ExpectEqual(t.Errorf, nil, err) + //fmt.Println(Json(rds)) +} + +func TestClient_CreateReadReplica(t *testing.T) { + client := DDCRDS_CLIENT + instanceId := "ddc-m1b5gjr5" + args := &CreateReadReplicaArgs{ + ClientToken: "320cfd8dceaf98529bd9f7c1d43a52c5", + // 计费相关参数,DDC 只读实例只支持预付费,RDS 只读实例只支持后付费Postpaid,必选 + Billing: Billing{ + PaymentTiming: "Prepaid", + Reservation: Reservation{ReservationLength: 5, ReservationTimeUnit: "Month"}, + }, + PurchaseCount: 3, + //主实例ID,必选 + SourceInstanceId: instanceId, + InstanceName: "go_tester_read", + // CPU核数,必选 + CpuCount: 2, + //套餐内存大小,单位GB,必选 + MemoryCapacity: 8, + //套餐磁盘大小,单位GB,每5G递增,必选 + VolumeCapacity: 20, + //批量创建云数据库 ddc 只读实例个数, 目前只支持一次创建一个,可选 + //PurchaseCount: 2, + //实例名称,允许小写字母、数字,长度限制为1~32,默认命名规则:{engine} + {engineVersion},可选 + //指定zone信息,默认为空,由系统自动选择,可选 + //zoneName命名规范是小写的“国家-region-可用区序列",例如北京可用区A为"cn-bj-a"。 + //ZoneNames: []string{"cn-su-c"}, + //与主实例 vpcId 相同,可选 + //VpcId: "vpc-IyrqYIQ7", + //是否进行直接支付,默认false,设置为直接支付的变配订单会直接扣款,不需要再走支付逻辑,可选 + IsDirectPay: false, + //vpc内,每个可用区的subnetId;如果不是默认vpc则必须指定 subnetId,可选 + //Subnets: []SubnetMap{ + // { + // ZoneName: "cn-su-c", + // SubnetId: "sbn-8v3p33vhyhq5", + // }, + //}, + // 资源池id 必选与主实例保持一致 + //PoolId:"xdb_5cf97afb-ee06-4b80-9146-4a840e5d0288_pool", + // RO组ID。(创建只读实例时) 可选 + // 如果不传,默认会创建一个RO组,并将该只读加入RO组中 + RoGroupId: "yyzzcc2", + // RO组是否启用延迟剔除,默认不启动。(创建只读实例时)可选 + EnableDelayOff: "0", + // 延迟阈值。(创建只读实例时)可选 + DelayThreshold: "1", + // RO组最少保留实例数目。默认为1. (创建只读实例时)可选 + LeastInstanceAmount: "1", + // 只读实例在RO组中的读流量权重。默认为1(创建只读实例时)可选 + RoGroupWeight: "1", + } + result, err := client.CreateReadReplica(args) + if err != nil { + fmt.Printf("create ddc readReplica error: %+v\n", err) + return + } + + for _, e := range result.InstanceIds { + fmt.Println("create ddc readReplica success, instanceId: ", e) + } +} + +func TestClient_ListRds(t *testing.T) { + args := &ListRdsArgs{ + // 批量获取列表的查询的起始位置,实例列表中Marker需要指定实例Id,可选 + Marker: "-1", + // 指定每页包含的最大数量(主实例),最大数量不超过1000,缺省值为1000,可选 + MaxKeys: 10, + } + resp, err := DDCRDS_CLIENT.ListRds(args) + + if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return + } + + // 返回标记查询的起始位置 + fmt.Println("list marker: ", resp.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("list isTruncated: ", resp.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("list nextMarker: ", resp.NextMarker) + // 每页包含的最大数量 + fmt.Println("list maxKeys: ", resp.MaxKeys) + + // 获取instance的列表信息 + for _, e := range resp.Instances { + fmt.Println("=====================================>") + fmt.Println("instance productType: ", e.ProductType()) + fmt.Println("instanceId: ", e.InstanceId) + fmt.Println("instanceName: ", e.InstanceName) + fmt.Println("engine: ", e.Engine) + fmt.Println("engineVersion: ", e.EngineVersion) + fmt.Println("instanceStatus: ", e.InstanceStatus) + fmt.Println("cpuCount: ", e.CpuCount) + fmt.Println("memoryCapacity: ", e.MemoryCapacity) + fmt.Println("volumeCapacity: ", e.VolumeCapacity) + fmt.Println("usedStorage: ", e.UsedStorage) + fmt.Println("paymentTiming: ", e.PaymentTiming) + fmt.Println("instanceType: ", e.InstanceType) + fmt.Println("instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("publiclyAccessible: ", e.PubliclyAccessible) + fmt.Println("backup expireInDays: ", e.BackupPolicy.ExpireInDaysInt) + fmt.Println("vpcId: ", e.VpcId) + fmt.Println("endpoint: ", e.Endpoint) + fmt.Println("vnetIp: ", e.Endpoint.VnetIp) + fmt.Println("vnetIpBackup: ", e.Endpoint.VnetIpBackup) + fmt.Println("long BBC Id: ", e.LongBBCId) + fmt.Println("bbc hostname: ", e.HostName) + if e.AutoRenewRule != nil { + fmt.Println("renewTime: ", e.AutoRenewRule.RenewTime) + fmt.Println("renewTimeUnit: ", e.AutoRenewRule.RenewTimeUnit) + } + } +} + +func TestClient_ListPage(t *testing.T) { + args := &ListPageArgs{ + // 页码 + PageNo: 1, + // 页大小 + PageSize: 10, + // 筛选条件 + // 筛选字段类型,各筛选条件只能单独筛选,当取值为type、status、dbType、zone时可在集合中增加筛选项 + // all:匹配全部 + // instanceName:匹配实例名称 + // instanceId:匹配实例id; + // vnetIpBackup:备库ip; + // vnetIp:主库ip + //Filters: []Filter{ + // {KeywordType: "all", Keyword: "mysql"}, + // {KeywordType: "zone", Keyword: "cn-bj-a"}, + //}, + } + resp, err := DDCRDS_CLIENT.ListPage(args) + + if err != nil { + fmt.Printf("get instance error: %+v\n", err) + return + } + + // 返回分页页码 + fmt.Println("list pageNo: ", resp.Page.PageNo) + // 返回页大小 + fmt.Println("list pageSize: ", resp.Page.PageSize) + // 返回总数量 + fmt.Println("list totalCount: ", resp.Page.TotalCount) + + // 获取instance的列表信息 + for _, e := range resp.Page.Result { + fmt.Println("=====================================>") + fmt.Println("instanceId: ", e.InstanceId) + fmt.Println("instanceName: ", e.InstanceName) + fmt.Println("engine: ", e.Engine) + fmt.Println("engineVersion: ", e.EngineVersion) + fmt.Println("instanceStatus: ", e.InstanceStatus) + fmt.Println("cpuCount: ", e.CpuCount) + fmt.Println("memoryCapacity: ", e.MemoryCapacity) + fmt.Println("volumeCapacity: ", e.VolumeCapacity) + fmt.Println("usedStorage: ", e.UsedStorage) + fmt.Println("paymentTiming: ", e.PaymentTiming) + fmt.Println("instanceType: ", e.InstanceType) + fmt.Println("instanceCreateTime: ", e.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", e.InstanceExpireTime) + fmt.Println("publiclyAccessible: ", e.PubliclyAccessible) + fmt.Println("backup expireInDays: ", e.BackupPolicy.ExpireInDaysInt) + fmt.Println("vpcId: ", e.VpcId) + fmt.Println("endpoint: ", e.Endpoint) + fmt.Println("vnetIp: ", e.Endpoint.VnetIp) + fmt.Println("vnetIpBackup: ", e.Endpoint.VnetIpBackup) + fmt.Println("long BBC Id: ", e.LongBBCId) + fmt.Println("bbc hostname: ", e.HostName) + if e.AutoRenewRule != nil { + fmt.Println("renewTime: ", e.AutoRenewRule.RenewTime) + fmt.Println("renewTimeUnit: ", e.AutoRenewRule.RenewTimeUnit) + } + } +} + +func TestClient_UpdateInstanceName(t *testing.T) { + args := &UpdateInstanceNameArgs{ + // DDC实例名称,允许小写字母、数字,中文,长度限制为1~64 + InstanceName: "备份恢复测试-勿删-02", + } + err := DDCRDS_CLIENT.UpdateInstanceName(DDC_INSTANCE_ID, args) + if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return + } + fmt.Printf("update instance name success\n") + + args = &UpdateInstanceNameArgs{ + // DDC实例名称,允许小写字母、数字,中文,长度限制为1~64 + InstanceName: "mysql56_rds", + } + err = DDCRDS_CLIENT.UpdateInstanceName(RDS_INSTANCE_ID, args) + if err != nil { + fmt.Printf("update instance name error: %+v\n", err) + return + } + fmt.Printf("update instance name success\n") +} + +// 以下操作仅支持RDS +// Only RDS +func TestClient_CreateRdsProxy(t *testing.T) { + args := &CreateRdsProxyArgs{ + SourceInstanceId: RDS_INSTANCE_ID, + NodeAmount: 2, + Billing: Billing{ + PaymentTiming: "Postpaid", + }, + ClientToken: getClientToken(), + } + assertAvailable(RDS_INSTANCE_ID, t) + _, err := DDCRDS_CLIENT.CreateRdsProxy(args) + ExpectEqual(t.Errorf, nil, err) +} + +// Only RDS +func TestClient_ResizeRds(t *testing.T) { + args := &ResizeRdsArgs{ + CpuCount: 1, + MemoryCapacity: 2, + VolumeCapacity: 10, + IsResizeNow: true, + IsDirectPay: true, + ClientToken: getClientToken(), + } + orderIdResponse, err := DDCRDS_CLIENT.ResizeRds(DDC_INSTANCE_ID, args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println("resize ddc success, orderId: ", orderIdResponse.OrderId) + time.Sleep(30 * time.Second) + assertAvailable(DDC_INSTANCE_ID, t) +} + +func TestClient_RebootInstance(t *testing.T) { + // client := DDCRDS_CLIENT + // err := client.RebootInstance("rds-deaaDuV9") + // if err != nil { + // fmt.Printf("reboot error: %+v\n", err) + // return + // } + + // 延迟重启(仅支持DDC) + args := &RebootArgs{ + IsRebootNow: false, + } + result, err := client.RebootInstanceWithArgs(instanceId, args) + if err != nil { + fmt.Printf("reboot ddc error: %+v\n", err) + return + } + if result != nil { + fmt.Printf("reboot ddc success, taskId: %+v\n", result.TaskID) + } +} + +func TestClient_ModifySyncMode(t *testing.T) { + assertAvailable(DDC_INSTANCE_ID, t) + listRdsArgs := &ListRdsArgs{} + result, err := DDCRDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if "Available" == e.InstanceStatus { + args := &ModifySyncModeArgs{ + SyncMode: "Semi_sync", // Semi_sync + } + err := DDCRDS_CLIENT.ModifySyncMode(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +// Only RDS +func TestClient_ModifyEndpoint(t *testing.T) { + assertAvailable(RDS_INSTANCE_ID, t) + listRdsArgs := &ListRdsArgs{} + result, err := DDCRDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if "Available" == e.InstanceStatus { + args := &ModifyEndpointArgs{ + Address: "gosdk", + } + err := DDCRDS_CLIENT.ModifyEndpoint(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +// Only RDS +func TestClient_ModifyPublicAccess(t *testing.T) { + assertAvailable(RDS_INSTANCE_ID, t) + listRdsArgs := &ListRdsArgs{} + result, err := DDCRDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if "Available" == e.InstanceStatus { + args := &ModifyPublicAccessArgs{ + PublicAccess: true, + } + err := DDCRDS_CLIENT.ModifyPublicAccess(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_AutoRenew(t *testing.T) { + args := &AutoRenewArgs{ + // 自动续费时长(续费单位为year 不大于3,续费单位为month 不大于9)必选 + AutoRenewTime: 1, + // 自动续费单位("year";"month")必选 + AutoRenewTimeUnit: "month", + // 实例id集合 必选 + InstanceIds: []string{ + "rds-OtTkC1OD", + "rds-rbmh6gJl", + }, + } + err := client.AutoRenew(args, "rds") + if err != nil { + fmt.Printf("create auto renew error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetMaintainTime(t *testing.T) { + maintainTime, err := client.GetMaintainTime(instanceId) + if err != nil { + fmt.Printf("get maintain time error: %+v\n", err) + return + } + fmt.Println("maintainTime duration", maintainTime.Duration) + fmt.Println("maintainTime period", maintainTime.Period) + fmt.Println("maintainTime startTime", maintainTime.StartTime) +} + +func TestClient_UpdateMaintainTime(t *testing.T) { + args := &MaintainTime{ + // 时长间隔 + Duration: 2, + // 1-7分别代表周一到周日 + Period: "1,2,3,4,5,0", + // 所有涉及的时间皆为北京时间24小时制 + StartTime: "14:07", + } + err := client.UpdateMaintainTime(instanceId, args) + if err != nil { + fmt.Printf("update maintain time error: %+v\n", err) + } +} +func TestClient_ListRecycleInstances(t *testing.T) { + marker := &Marker{MaxKeys: 10} + instances, err := client.ListRecycleInstances(marker, "ddc") + if err != nil { + fmt.Printf("list recycler instances error: %+v\n", err) + return + } + fmt.Println(Json(instances)) + for _, instance := range instances.Result { + fmt.Println("+-------------------------------------------+") + fmt.Println("instanceId: ", instance.InstanceId) + fmt.Println("instanceName: ", instance.InstanceName) + fmt.Println("engine: ", instance.Engine) + fmt.Println("engineVersion: ", instance.EngineVersion) + fmt.Println("instanceStatus: ", instance.InstanceStatus) + fmt.Println("cpuCount: ", instance.CpuCount) + fmt.Println("memoryCapacity: ", instance.MemoryCapacity) + fmt.Println("volumeCapacity: ", instance.VolumeCapacity) + fmt.Println("usedStorage: ", instance.UsedStorage) + fmt.Println("instanceType: ", instance.InstanceType) + fmt.Println("instanceCreateTime: ", instance.InstanceCreateTime) + fmt.Println("instanceExpireTime: ", instance.InstanceExpireTime) + fmt.Println("publicAccessStatus: ", instance.PublicAccessStatus) + fmt.Println("vpcId: ", instance.VpcId) + } +} + +func TestClient_RecoverRecyclerInstances(t *testing.T) { + instanceIds := []string{ + "ddc-mv8zcy6u", + "ddc-mof1m3hb", + } + orderIdResponse, err := client.RecoverRecyclerInstances(instanceIds) + if err != nil { + fmt.Printf("recover recycler instances error: %+v\n", err) + return + } + fmt.Println("recover recycler instances success, orderId: ", orderIdResponse.OrderId) +} + +func TestClient_DeleteRecyclerInstances(t *testing.T) { + instanceIds := []string{ + "ddc-mof1m3hb", + "ddc-moxgq5dm", + } + err := client.DeleteRecyclerInstances(instanceIds) + if err != nil { + fmt.Printf("delete recycler instances error: %+v\n", err) + return + } + fmt.Println("delete recycler instances success.") +} + +func TestClient_ListSecurityGroupByVpcId(t *testing.T) { + vpcId := "vpc-j1vaxw1cx2mw" + securityGroups, err := client.ListSecurityGroupByVpcId(vpcId) + if err != nil { + fmt.Printf("list security group by vpcId error: %+v\n", err) + return + } + for _, group := range *securityGroups { + fmt.Println("+-------------------------------------------+") + fmt.Println("id: ", group.SecurityGroupID) + fmt.Println("name: ", group.Name) + fmt.Println("description: ", group.Description) + fmt.Println("associateNum: ", group.AssociateNum) + fmt.Println("createdTime: ", group.CreatedTime) + fmt.Println("version: ", group.Version) + fmt.Println("defaultSecurityGroup: ", group.DefaultSecurityGroup) + fmt.Println("vpc name: ", group.VpcName) + fmt.Println("vpc id: ", group.VpcShortID) + fmt.Println("tenantId: ", group.TenantID) + } + fmt.Println("list security group by vpcId success.") +} + +func TestClient_ListSecurityGroupByInstanceId(t *testing.T) { + instanceId := "ddc-m1h4mma5" + result, err := client.ListSecurityGroupByInstanceId(instanceId) + if err != nil { + fmt.Printf("list security group by instanceId error: %+v\n", err) + return + } + for _, group := range result.Groups { + fmt.Println("+-------------------------------------------+") + fmt.Println("securityGroupId: ", group.SecurityGroupID) + fmt.Println("securityGroupName: ", group.SecurityGroupName) + fmt.Println("securityGroupRemark: ", group.SecurityGroupRemark) + fmt.Println("projectId: ", group.ProjectID) + fmt.Println("vpcId: ", group.VpcID) + fmt.Println("vpcName: ", group.VpcName) + fmt.Println("inbound: ", group.Inbound) + fmt.Println("outbound: ", group.Outbound) + } + fmt.Println("list security group by instanceId success.") +} + +func TestClient_BindSecurityGroups(t *testing.T) { + instanceIds := []string{ + "ddc-mf4c901b", + } + securityGroupIds := []string{ + "g-mi74p78rtq07", + } + args := &SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, + } + + err := client.BindSecurityGroups(args) + if err != nil { + fmt.Printf("bind security groups to instances error: %+v\n", err) + return + } + fmt.Println("bind security groups to instances success.") +} + +func TestClient_UnBindSecurityGroups(t *testing.T) { + instanceIds := []string{ + "ddc-mjafcdu0", + } + securityGroupIds := []string{ + "g-iutg5rtcydsk", + } + args := &SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, + } + + err := client.UnBindSecurityGroups(args) + if err != nil { + fmt.Printf("unbind security groups to instances error: %+v\n", err) + return + } + fmt.Println("unbind security groups to instances success.") +} + +func TestClient_ReplaceSecurityGroups(t *testing.T) { + instanceIds := []string{ + "ddc-mjafcdu0", + } + securityGroupIds := []string{ + "g-iutg5rtcydsk", + } + args := &SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, + } + + err := client.ReplaceSecurityGroups(args) + if err != nil { + fmt.Printf("replace security groups to instances error: %+v\n", err) + return + } + fmt.Println("replace security groups to instances success.") +} + +func TestClient_ListLogByInstanceId(t *testing.T) { + // 两天前 + date := time.Now(). + AddDate(0, 0, -2). + Format("2006-01-02") + fmt.Println(date) + args := &ListLogArgs{ + LogType: "error", + Datetime: date, + } + logs, err := client.ListLogByInstanceId(instanceId, args) + if err != nil { + fmt.Printf("list logs of instance error: %+v\n", err) + return + } + fmt.Println("list logs of instance success.") + for _, log := range *logs { + fmt.Println("+-------------------------------------------+") + fmt.Println("id: ", log.LogID) + fmt.Println("size: ", log.LogSizeInBytes) + fmt.Println("start time: ", log.LogStartTime) + fmt.Println("end time: ", log.LogEndTime) + } +} + +func TestClient_GetLogById(t *testing.T) { + args := &GetLogArgs{ + ValidSeconds: 20, + } + logId := "errlog.202103091300" + log, err := client.GetLogById(instanceId, logId, args) + if err != nil { + fmt.Printf("get log detail of instance error: %+v\n", err) + return + } + fmt.Println("list logs of instances success.") + fmt.Println("+-------------------------------------------+") + fmt.Println("id: ", log.LogID) + fmt.Println("size: ", log.LogSizeInBytes) + fmt.Println("start time: ", log.LogStartTime) + fmt.Println("end time: ", log.LogEndTime) + fmt.Println("download url: ", log.DownloadURL) + fmt.Println("download url expires: ", log.DownloadExpires) +} + +func TestClient_LazyDropCreateHardLink(t *testing.T) { + dbName := "test2" + tableName := "app_3" + err := client.LazyDropCreateHardLink(instanceId, dbName, tableName) + if err != nil { + fmt.Printf("[lazy drop] create hard link error: %+v\n", err) + return + } + fmt.Println("[lazy drop] create hard link success.") +} + +func TestClient_LazyDropDeleteHardLink(t *testing.T) { + dbName := "test2" + tableName := "app_1" + result, err := client.LazyDropDeleteHardLink(instanceId, dbName, tableName) + if err != nil { + fmt.Printf("[lazy drop] delete hard link error: %+v\n", err) + return + } + fmt.Println("[lazy drop] delete hard link success.taskId:", result.TaskID) +} + +func TestClient_GetDisk(t *testing.T) { + disk, err := client.GetDisk(instanceId) + if err != nil { + fmt.Printf("get disk of instance error: %+v\n", err) + return + } + fmt.Println("get disk of instance success.") + for _, diskItem := range disk.Response.Items { + fmt.Println("instance id: ", diskItem.InstanceID) + fmt.Println("instance role: ", diskItem.InstanceRole) + fmt.Println("disk disk partition: ", diskItem.DiskPartition) + fmt.Println("disk totle size in bytes: ", diskItem.TotalSize) + fmt.Println("disk used size in bytes: ", diskItem.UsedSize) + fmt.Println("disk report time: ", diskItem.ReportTime) + } +} + +func TestClient_GetMachineInfo(t *testing.T) { + machine, err := client.GetMachineInfo(instanceId) + if err != nil { + fmt.Printf("get machine info error: %+v\n", err) + return + } + fmt.Println("get machine info success.") + for _, machine := range machine.Response.Items { + fmt.Println("instance id: ", machine.InstanceID) + fmt.Println("instance role: ", machine.Role) + fmt.Println("cpu(core): ", machine.CPUInCore) + fmt.Println("cpu(core) free: ", machine.FreeCPUInCore) + fmt.Println("memory(MB): ", machine.MemSizeInMB) + fmt.Println("memory(MB) free: ", machine.FreeMemSizeInMB) + fmt.Println("disk info: ", machine.SizeInGB) + fmt.Println("----------------------") + } +} + +func TestClient_GetResidual(t *testing.T) { + residual, err := client.GetResidual(POOL) + if err != nil { + fmt.Printf("get residual of pool error: %+v\n", err) + return + } + fmt.Println("get residual of pool success.") + for zoneName, residualByZone := range residual.Residual { + fmt.Println("zone name: ", zoneName) + fmt.Printf("Single residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.Single.DiskInGb, residualByZone.Single.MemoryInGb, residualByZone.Single.CPUInCore) + fmt.Printf("Slave residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.Slave.DiskInGb, residualByZone.Slave.MemoryInGb, residualByZone.Slave.CPUInCore) + fmt.Printf("HA residual: disk %v GB, memory %v GB, cpu cores %d\n", + residualByZone.HA.DiskInGb, residualByZone.HA.MemoryInGb, residualByZone.HA.CPUInCore) + } +} + +func TestClient_GetFlavorCapacity(t *testing.T) { + args := &GetFlavorCapacityArgs{ + CpuInCore: 2, + MemoryInGb: 4, + DiskInGb: 50, + Affinity: 2, + } + + args = NewDefaultGetFlavorCapacityArgs(2, 4, 50) + capacityResult, err := client.GetFlavorCapacity(POOL, args) + if err != nil { + fmt.Printf("get flavor capacity of pool error: %+v\n", err) + return + } + fmt.Println("get flavor capacity of pool success.") + for zoneName, residualByZone := range capacityResult.Capacity { + fmt.Println("zone name: ", zoneName) + fmt.Printf("HA capacity: %d\n", residualByZone.HA) + fmt.Printf("Single capacity: %d\n", residualByZone.Single) + fmt.Printf("Slave capacity: %d\n", residualByZone.Slave) + } +} + +func TestClient_KillSession(t *testing.T) { + args := &KillSessionArgs{ + Role: "master", + SessionIds: []int{8661, 8662}, + } + result, err := client.KillSession(instanceId, args) + if err != nil { + fmt.Printf("start kill session task error: %+v\n", err) + return + } + fmt.Println("start kill session task success. TaskID:", result.TaskID) +} + +func TestClient_GetKillSessionTaskResult(t *testing.T) { + taskId := 285647 + result, err := client.GetKillSessionTask(instanceId, taskId) + if err != nil { + fmt.Printf("get kill session task error: %+v\n", err) + return + } + fmt.Println("get kill session task success.") + for _, task := range result.Tasks { + fmt.Println("sessionId: ", task.SessionID) + fmt.Println("task status: ", task.Status) + } +} + +func TestClient_GetMaintainTaskList(t *testing.T) { + args := &GetMaintainTaskListArgs{ + Marker: Marker{ + MaxKeys: 10, + }, + // 任务起始时间 必选 + StartTime: "2021-08-10 00:00:00", + } + result, err := client.GetMaintainTaskList(args) + if err != nil { + fmt.Printf("get tasks error: %+v\n", err) + return + } + fmt.Println("get tasks success.") + // 返回标记查询的起始位置 + fmt.Println("list marker: ", result.Marker) + // true表示后面还有数据,false表示已经是最后一页 + fmt.Println("list isTruncated: ", result.IsTruncated) + // 获取下一页所需要传递的marker值。当isTruncated为false时,该域不出现 + fmt.Println("list nextMarker: ", result.NextMarker) + // 每页包含的最大数量 + fmt.Println("list maxKeys: ", result.MaxKeys) + for _, task := range result.Result { + fmt.Println("task id: ", task.TaskID) + fmt.Println("task name: ", task.TaskName) + fmt.Println("task status: ", task.TaskStatus) + fmt.Println("instance id: ", task.InstanceID) + fmt.Println("instance name: ", task.InstanceName) + fmt.Println("instance region: ", task.Region) + fmt.Println("start time: ", task.StartTime) + fmt.Println("end time: ", task.EndTime) + fmt.Println("--------------------------") + } +} + +func TestClient_GetMaintainTaskDetail(t *testing.T) { + result, err := client.GetMaintainTaskDetail(TASK_ID) + if err != nil { + fmt.Printf("get task detail error: %+v\n", err) + return + } + fmt.Println("get task detail success.") + for _, task := range result.Tasks { + fmt.Println("task id: ", task.TaskID) + fmt.Println("task name: ", task.TaskName) + fmt.Println("instance instanceId: ", task.InstanceID) + fmt.Println("instance instanceName: ", task.InstanceName) + fmt.Println("instance instanceName: ", task.AppID) + fmt.Println("instance task type: ", task.TaskType) + fmt.Println("task status: ", task.TaskStatus) + fmt.Println("instance create time: ", task.CreateTime) + fmt.Println("instance create time: ", task.TaskSpecialAction) + fmt.Println("instance create time: ", task.TaskSpecialActionTime) + fmt.Println("--------------------------") + } +} + +func TestClient_ExecuteMaintainTaskImmediately(t *testing.T) { + taskId := "880337" + err := client.ExecuteMaintainTaskImmediately(taskId) + if err != nil { + fmt.Printf("execute task invoke error: %+v\n", err) + return + } + fmt.Println("execute task invoke success.") +} + +func TestClient_CancelMaintainTask(t *testing.T) { + taskId := "880337" + err := client.CancelMaintainTask(taskId) + if err != nil { + fmt.Printf("cancel task invoke error: %+v\n", err) + return + } + fmt.Println("cancel task invoke success.") +} + +func TestClient_GetAccessLog(t *testing.T) { + date := "20210810" + downloadInfo, err := client.GetAccessLog(date) + if err != nil { + fmt.Printf("get access logs error: %+v\n", err) + return + } + fmt.Println("get access logs success.") + fmt.Println("mysql access logs link: ", downloadInfo.Downloadurl.Mysql) + fmt.Println("bbc access logs link: ", downloadInfo.Downloadurl.Bbc) + fmt.Println("bos access logs link: ", downloadInfo.Downloadurl.Bos) +} + +func TestClient_GetErrorLogs(t *testing.T) { + args := &GetErrorLogsArgs{ + InstanceId: "ddc-mp8lme9w", + StartTime: "2021-08-16T02:28:51Z", + EndTime: "2021-08-17T02:28:51Z", + PageNo: 1, + PageSize: 10, + Role: "master", + KeyWord: "Aborted", + } + errorLogsResponse, err := client.GetErrorLogs(args) + if err != nil { + fmt.Printf("get error logs error: %+v\n", err) + return + } + fmt.Println("get error logs success.") + fmt.Println("error logs count: ", errorLogsResponse.Count) + for _, errorLog := range errorLogsResponse.ErrorLogs { + fmt.Println("=================================================") + fmt.Println("error log instanceId: ", errorLog.InstanceId) + fmt.Println("error log executeTime: ", errorLog.ExecuteTime) + fmt.Println("error log logLevel: ", errorLog.LogLevel) + fmt.Println("error log logText: ", errorLog.LogText) + } +} + +func TestClient_GetSlowLogs(t *testing.T) { + args := &GetSlowLogsArgs{ + InstanceId: "ddc-mp8lme9w", + StartTime: "2021-08-16T02:28:51Z", + EndTime: "2021-08-17T02:28:51Z", + PageNo: 1, + PageSize: 10, + Role: "master", + DbName: []string{"baidu_dba"}, + UserName: []string{"_root"}, + HostIp: []string{"localhost"}, + Sql: "update heartbeat set id=?, value=?", + } + slowLogsResponse, err := client.GetSlowLogs(args) + if err != nil { + fmt.Printf("get slow logs error: %+v\n", err) + return + } + fmt.Println("get slow logs success.") + fmt.Println("slow logs count: ", slowLogsResponse.Count) + for _, slowLog := range slowLogsResponse.SlowLogs { + fmt.Println("=================================================") + fmt.Println("slow log instanceId: ", slowLog.InstanceId) + fmt.Println("slow log userName: ", slowLog.UserName) + fmt.Println("slow log dbName: ", slowLog.DbName) + fmt.Println("slow log hostIp: ", slowLog.HostIp) + fmt.Println("slow log queryTime: ", slowLog.QueryTime) + fmt.Println("slow log lockTime: ", slowLog.LockTime) + fmt.Println("slow log rowsExamined: ", slowLog.RowsExamined) + fmt.Println("slow log rowsSent: ", slowLog.RowsSent) + fmt.Println("slow log sql: ", slowLog.Sql) + fmt.Println("slow log executeTime: ", slowLog.ExecuteTime) + } +} + +func TestClient_GetInstanceBackupStatus(t *testing.T) { + instanceId = "ddc-mvxhc1fq" + backupStatusResult, err := client.GetInstanceBackupStatus(instanceId) + if err != nil { + fmt.Printf("get backup status error: %+v\n", err) + return + } + fmt.Println("get backup status success.") + fmt.Println("instance is backuping: ", backupStatusResult.IsBackuping) + if backupStatusResult.IsBackuping { + fmt.Println("instance backup start time: ", backupStatusResult.SnapshotStartTime) + } +} + +func TestClient_InstanceVersionRollBack(t *testing.T) { + instanceId = "ddc-mvxhc1fq" + args := &InstanceVersionRollBackArg{ + // 是否维护时间执行 + WaitSwitch: true, + } + result, err := client.InstanceVersionRollBack(instanceId, args) + if err != nil { + fmt.Printf("rollback instance version faild: %+v\n", err) + return + } + fmt.Printf("rollback instance version success. taskId:%s\n", result.TaskID) +} + +func TestClient_InstanceVersionUpgrade(t *testing.T) { + instanceId = "ddc-mvxhc1fq" + args := &InstanceVersionUpgradeArg{ + // 是否立即执行 + IsUpgradeNow: true, + } + result, err := client.InstanceVersionUpgrade(instanceId, args) + if err != nil { + fmt.Printf("upgrade instance version faild: %+v\n", err) + return + } + fmt.Printf("upgrade instance version success. taskId:%s\n", result.TaskID) +} + +func TestClient_GetInstanceSyncDelay(t *testing.T) { + instanceId = "ddc-mvxhc1fq" + result, err := client.GetInstanceSyncDelay(instanceId) + if err != nil { + fmt.Printf("get readonly instance syncDelay and syncStatus faild: %+v\n", err) + return + } + fmt.Println("get readonly instance syncDelay and syncStatus success.") + if result.Success { + fmt.Println("instance is SyncDelay: ", result.Result.SyncDelay) + fmt.Println("instance is SyncStatus: ", result.Result.SyncStatus) + } +} + +func TestClient_InstanceSyncDelayReplication(t *testing.T) { + instanceId = "ddc-mvxhc1fq" + args := &InstanceSyncDelayReplicationArg{ + // 开启 + Action: "start", + } + result, err := client.InstanceSyncDelayReplication(instanceId, args) + if err != nil { + fmt.Printf("instance syncDelay replication faild: %+v\n", err) + return + } + fmt.Printf("instance syncDelay replication success. success:%+v\n", result.Success) +} + +func TestClient_BinlogAccessDetail(t *testing.T) { + args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, + } + result, err := client.BinlogAccessDetail(args) + if err != nil { + fmt.Printf("get binlog access detail error: %+v\n", err) + return + } + fmt.Printf("get binlog access detail success\n") + fmt.Println("result: ", result) +} + +func TestClient_SnapshotAccessDetail(t *testing.T) { + args := &AccessDetailArgs{ + StartDateTime: "2023-02-02T01:00:00Z", + EndDateTime: "2023-02-02T10:00:00Z", + Marker: "0", + MaxKeys: 100, + } + result, err := client.SnapshotAccessDetail(args) + if err != nil { + fmt.Printf("get snapshot access detail error: %+v\n", err) + return + } + fmt.Printf("get snapshot access detail success\n %+v", result) +} diff --git a/bce-sdk-go/services/ddc/v2/ddc.go b/bce-sdk-go/services/ddc/v2/ddc.go new file mode 100644 index 0000000..c3acdff --- /dev/null +++ b/bce-sdk-go/services/ddc/v2/ddc.go @@ -0,0 +1,2524 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// ddc.go - the ddc APIs definition supported by the DDC service +package ddcrds + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + KEY_CLIENT_TOKEN = "clientToken" + KEY_MARKER = "marker" + KEY_MAX_KEYS = "maxKeys" + COMMA = "," +) + +// Convert marker to request params +func getMarkerParams(marker *Marker) map[string]string { + if marker == nil { + marker = &Marker{Marker: "-1"} + } + params := make(map[string]string, 2) + params[KEY_MARKER] = marker.Marker + if marker.MaxKeys > 0 { + params[KEY_MAX_KEYS] = strconv.Itoa(marker.MaxKeys) + } + return params +} + +// Convert struct to request params +func getQueryParams(val interface{}) (map[string]string, error) { + var params map[string]string + if val != nil { + err := json.Unmarshal([]byte(Json(val)), ¶ms) + if err != nil { + return nil, err + } + } + return params, nil +} + +// CreateInstance - create a Instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create a instance +// +// RETURNS: +// - *InstanceIds: the result of create RDS, contains new RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateInstance(args *CreateInstanceArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDdcInstanceUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateRds - create a DDC with the specific parameters +// +// PARAMS: +// - args: the arguments to create a ddc +// +// RETURNS: +// - *InstanceIds: the result of create DDC, contains new DDC's instanceIds +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateRds(args *CreateRdsArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Engine == "" { + return nil, fmt.Errorf("unset Engine") + } + + if args.EngineVersion == "" { + return nil, fmt.Errorf("unset EngineVersion") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + + newArgs := &CreateInstanceArgs{ + InstanceType: "RDS", + Number: args.PurchaseCount, + ClientToken: args.ClientToken, + Instance: CreateInstance{ + InstanceName: args.InstanceName, + Engine: strings.ToLower(args.Engine), + EngineVersion: args.EngineVersion, + CpuCount: args.CpuCount, + AllocatedMemoryInGB: int(args.MemoryCapacity), + AllocatedStorageInGB: args.VolumeCapacity, + DiskIoType: "ssd", + DeployId: args.DeployId, + PoolId: args.PoolId, + IsDirectPay: args.IsDirectPay, + Billing: args.Billing, + AutoRenewTime: args.AutoRenewTime, + AutoRenewTimeUnit: args.AutoRenewTimeUnit, + Tags: args.Tags, + Category: args.Category, + SyncMode: strings.ToLower(args.SyncMode), + }, + } + + info, err := c.SupplyVpcInfo(newArgs, args) + if err != nil { + return nil, err + } + result, err2 := c.CreateInstance(info) + if err2 != nil { + return nil, err2 + } + return result, nil +} + +func (c *DDCClient) SupplyVpcInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + + info := newArgs + vpc, err := c.ListVpc() + if err != nil { + fmt.Printf("list vpc error: %+v\n", err) + return nil, err + } + defaultVpcId := "" + if args.VpcId == "" { + for _, e := range *vpc { + defaultVpcId = e.VpcId + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + if args.VpcId == defaultVpcId { + for _, e := range *vpc { + if e.VpcId == args.VpcId { + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + info, err = c.UnDefaultVpcInfo(info, args) + if err != nil { + fmt.Printf("set vpc error: %+v\n", err) + return nil, err + } + } else { + for _, e := range *vpc { + if args.VpcId == e.ShortId { + info.Instance.VpcId = e.VpcId + args.VpcId = e.VpcId + } + } + info, err = c.UnDefaultVpcInfo(info, args) + if err != nil { + fmt.Printf("supply zoneAndSubnet info error: %+v\n", err) + return nil, err + } + } + return info, nil +} + +func (c *DDCClient) SupplyZoneAndSubnetInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + newZoneName := "" + if args.Subnets != nil { + for _, e := range args.Subnets { + newZoneName += e.ZoneName + "," + } + if newZoneName != "" && newZoneName[0:len(newZoneName)-1] != strings.Join(args.ZoneNames, ",") { + fmt.Printf("subnets and zoneNames not matcher: %+v\n", nil) + return nil, errors.New("subnets and zoneNames not matcher") + } else { + listSubnetsArgs := &ListSubnetsArgs{ + VpcId: args.VpcId, + } + subnets, err1 := c.ListSubnets(listSubnetsArgs) + if err1 != nil { + fmt.Printf("list subnets error: %+v\n", err1) + return nil, err1 + } + subnetId := "" + if args.Subnets != nil { + for _, e := range subnets.Subnets { + for _, e1 := range args.Subnets { + if e.ShortId == e1.SubnetId { + subnetId += e.Az + ":" + e.LongId + "," + } + } + } + if subnetId == "" { + return nil, errors.New("subnetId no match vpc or pool") + } + newArgs.Instance.SubnetId = subnetId[0 : len(subnetId)-1] + } + } + } else { + var subnetStr string + for _, e := range args.ZoneNames { + if !strings.Contains(e, ",") { + listSubnetsArgs := &ListSubnetsArgs{ + VpcId: args.VpcId, + ZoneName: e, + } + subnets, err := c.ListSubnets(listSubnetsArgs) + if err != nil { + fmt.Printf("list subnets error: %+v\n", err) + return nil, err + } + if subnets != nil && len(subnets.Subnets) > 0 { + subnetId := subnets.Subnets[0].LongId + subnetStr += e + ":" + subnetId + "," + } + } + } + if len(subnetStr) < 1 { + return nil, errors.New("Have no available subnet") + } + newArgs.Instance.SubnetId = subnetStr[:len(subnetStr)-1] + } + return newArgs, nil +} + +func (c *DDCClient) UnDefaultVpcInfo(newArgs *CreateInstanceArgs, args *CreateRdsArgs) (*CreateInstanceArgs, error) { + info := newArgs + list, err2 := c.GetZoneList() + if err2 != nil { + fmt.Printf("get zone list error: %+v\n", err2) + return nil, err2 + } + newZoneName := "" + if args.ZoneNames == nil { + if args.Subnets == nil { + subnets, _ := c.ListSubnets(&ListSubnetsArgs{VpcId: args.VpcId}) + if subnets == nil || len(subnets.Subnets) == 0 { + return nil, errors.New("Have no available subnet or zone") + } + + for _, e := range subnets.Subnets { + info.Instance.AZone = e.Az + args.ZoneNames = append(args.ZoneNames, e.Az) + break + } + //for _, e := range list.Zones { + // info.Instance.AZone = e.ApiZoneNames[0] + // args.ZoneNames = append(args.ZoneNames, e.ApiZoneNames[0]) + // break + //} + } else { + for _, e := range args.Subnets { + newZoneName += e.ZoneName + "," + args.ZoneNames = append(args.ZoneNames, e.ZoneName) + } + for _, e := range list.Zones { + if newZoneName == strings.Join(e.ApiZoneNames, ",") { + info.Instance.AZone = strings.Join(e.ApiZoneNames, ",") + } + } + } + if args.ZoneNames == nil { + return nil, errors.New("Have no available zone for your operation.") + } + } else { + newZoneName = "" + for _, e1 := range list.Zones { + if strings.Join(args.ZoneNames, ",") == strings.Join(e1.ZoneNames, ",") { + newZoneName = strings.Join(e1.ApiZoneNames, ",") + } + } + info.Instance.AZone = newZoneName + } + + info, err2 = c.SupplyZoneAndSubnetInfo(info, args) + if err2 != nil { + return nil, err2 + } + return info, nil +} + +// CreateReadReplica - create a readReplica ddc with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica ddc +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica ddc, contains the readReplica DDC's instanceIds +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateReadReplica(args *CreateReadReplicaArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.SourceInstanceId == "" { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + detail, err2 := c.GetDdcDetail(args.SourceInstanceId) + if err2 != nil { + return nil, err2 + } + newArgs := &CreateInstanceArgs{ + InstanceType: "RDS", + Number: args.PurchaseCount, + ClientToken: args.ClientToken, + Instance: CreateInstance{ + SourceInstanceId: args.SourceInstanceId, + InstanceName: args.InstanceName, + Engine: strings.ToLower(detail.Instance.Engine), + EngineVersion: detail.Instance.EngineVersion, + CpuCount: args.CpuCount, + AllocatedMemoryInGB: int(args.MemoryCapacity), + AllocatedStorageInGB: args.VolumeCapacity, + DiskIoType: "ssd", + DeployId: args.DeployId, + PoolId: args.PoolId, + RoGroupId: args.RoGroupId, + RoGroupWeight: Int(args.RoGroupWeight), + EnableDelayOff: Int(args.EnableDelayOff), + DelayThreshold: Int(args.DelayThreshold), + LeastInstanceAmount: Int(args.LeastInstanceAmount), + Billing: args.Billing, + IsDirectPay: args.IsDirectPay, + AutoRenewTime: args.AutoRenewTime, + AutoRenewTimeUnit: args.AutoRenewTimeUnit, + Tags: args.Tags, + }, + } + + createRdsArgs := &CreateRdsArgs{ + VpcId: args.VpcId, + Subnets: args.Subnets, + ZoneNames: args.ZoneNames, + } + + info, err := c.SupplyVpcInfo(newArgs, createRdsArgs) + if err != nil { + return nil, err + } + result, err2 := c.CreateInstance(info) + if err2 != nil { + return nil, err2 + } + + return result, err +} + +// UpdateRoGroup - update a roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateRoGroup(roGroupId string, args *UpdateRoGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + body := &UpdateRoGroupRealArgs{ + RoGroupName: args.RoGroupName, + // 处理零值序列化问题 + EnableDelayOff: Int(args.EnableDelayOff), + DelayThreshold: Int(args.DelayThreshold), + LeastInstanceAmount: Int(args.LeastInstanceAmount), + IsBalanceRoLoad: Int(args.IsBalanceRoLoad), + MasterDelay: Int(args.MasterDelay), + } + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateRoGroupUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(body). + Do() + return err +} + +// UpdateRoGroupReplicaWeight- update repica weight in roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateRoGroupReplicaWeight(roGroupId string, args *UpdateRoGroupWeightArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + body := &UpdateRoGroupWeightRealArgs{ + RoGroupName: args.RoGroupName, + // 处理零值序列化问题 + EnableDelayOff: Int(args.EnableDelayOff), + DelayThreshold: Int(args.DelayThreshold), + LeastInstanceAmount: Int(args.LeastInstanceAmount), + IsBalanceRoLoad: Int(args.IsBalanceRoLoad), + MasterDelay: Int(args.MasterDelay), + ReplicaList: args.ReplicaList, + } + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateRoGroupWeightUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(body). + Do() + return err +} + +// ReBalanceRoGroup- Initiate a rebalance for foGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ReBalanceRoGroup(roGroupId string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getReBalanceRoGroupUriWithId(roGroupId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateDeploySet(poolId string, args *CreateDeployRequest) (*CreateDeployResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.Strategy) < 1 { + args.Strategy = "centralized" + } + if !(args.Strategy == "distributed" || args.Strategy == "centralized") { + return nil, fmt.Errorf("Only support strategy centralized, current strategy: %v", args.Strategy) + } + + result := &CreateDeployResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDeploySetUri(poolId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateDeploySet - update a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateDeploySet(poolId string, deployId string, args *UpdateDeployRequest) error { + if args == nil { + return fmt.Errorf("unset args") + } + if !(args.Strategy == "distributed" || args.Strategy == "centralized") { + return fmt.Errorf("Only support strategy distributed/centralized, current strategy: %v", args.Strategy) + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDeploySetUriWithId(poolId, deployId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListRds - list all instances +// RETURNS: +// - *ListRdsResult: the result of list instances with marker +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListRds(marker *ListRdsArgs) (*ListRdsResult, error) { + req := &bce.BceRequest{} + req.SetUri(getDdcInstanceUri() + "/list") + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &ListRdsResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// ListPage - list all instances with page +// RETURNS: +// - *ListPageResult: the result of list instances with marker +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListPage(args *ListPageArgs) (*ListPageResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + result := &ListPageResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDdcInstanceUri()+"/listPage"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetDdcDetail - get details of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - *InstanceModelResult: the detail of the instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDdcDetail(instanceId string) (*InstanceModelResult, error) { + result := &InstanceModelResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)). + WithResult(result). + Do() + + return result, err +} + +// GetDetail - get a specific ddc Instance's detail +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *Instance: the specific ddc Instance's detail +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDetail(instanceId string) (*Instance, error) { + detail, err := c.GetDdcDetail(instanceId) + result := &Instance{ + InstanceName: detail.Instance.InstanceName, + InstanceId: detail.Instance.InstanceId, + SourceInstanceId: detail.Instance.SourceInstanceId, + Endpoint: detail.Instance.Endpoint, + Engine: detail.Instance.Engine, + EngineVersion: detail.Instance.EngineVersion, + InstanceStatus: detail.Instance.InstanceStatus, + CpuCount: detail.Instance.CpuCount, + MemoryCapacity: detail.Instance.AllocatedMemoryInGB, + VolumeCapacity: detail.Instance.AllocatedStorageInGB, + UsedStorage: detail.Instance.UsedStorageInGB, + InstanceType: detail.Instance.Type, + InstanceCreateTime: detail.Instance.InstanceCreateTime, + InstanceExpireTime: detail.Instance.InstanceExpireTime, + PubliclyAccessible: detail.Instance.PublicAccessStatus, + PaymentTiming: detail.Instance.PaymentTiming, + SyncMode: detail.Instance.SyncMode, + Region: detail.Instance.Region, + VpcId: detail.Instance.VpcId, + BackupPolicy: detail.Instance.BackupPolicy, + RoGroupList: detail.Instance.RoGroupList, + NodeMaster: detail.Instance.NodeMaster, + NodeSlave: detail.Instance.NodeSlave, + NodeReadReplica: detail.Instance.NodeReadReplica, + Subnets: detail.Instance.Subnets, + DeployId: detail.Instance.DeployId, + ZoneNames: detail.Instance.ZoneNames, + Category: detail.Instance.Category, + LongBBCId: detail.Instance.LongBBCId, + InstanceTopoForReadonly: detail.Instance.InstanceTopoForReadonly, + AutoRenewRule: detail.Instance.AutoRenewRule, + } + // 兼容RDS字段 + result.PublicAccessStatus = strconv.FormatBool(result.PubliclyAccessible) + return result, err +} + +// DeleteRds - delete instances +// +// PARAMS: +// - instanceIds: id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) DeleteRds(instanceIds string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getDdcInstanceUri()+"/delete"). + WithQueryParam("instanceIds", instanceIds). + Do() +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// - args: reboot args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) RebootInstanceWithArgs(instanceId string, args *RebootArgs) (*MaintainTaskIdResult, error) { + result := &MaintainTaskIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/reboot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateInstanceName - update name of a specified instance +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateInstanceName(instanceId string, args *UpdateInstanceNameArgs) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/updateName"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return err +} + +// GetBackupList - get backup list of the instance +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *GetBackupListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetBackupList(instanceId string, args *GetBackupListArgs) (*GetBackupListResult, error) { + result := &GetBackupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/snapshot"). + WithResult(result). + Do() + + if args != nil && result.Backups != nil { + backups := result.Backups + backupsLen := len(backups) + if backupsLen > args.MaxKeys { + // marker 分页,兼容rds sdk + find := false + start, end := 0, 0 + masterCount, markerIndex := 0, 1 + if "-1" == args.Marker || "" == args.Marker { + find = true + } + for i := 0; i < backupsLen; i++ { + backup := backups[i] + masterCount++ + if ("-1" != args.Marker) && backup.SnapshotId == args.Marker { + start = i + find = true + markerIndex = masterCount + } + if find && masterCount == markerIndex+args.MaxKeys { + end = i + } + } + if end == 0 { + end = backupsLen + } else { + // 设置下个Marker + result.NextMarker = backups[end].SnapshotId + result.IsTruncated = true + } + result.Backups = result.Backups[start:end] + } + result.MaxKeys = args.MaxKeys + } + + return result, err +} + +// GetZoneList - list all zone +// +// PARAMS: +// - c: the client agent which can perform sending request +// +// RETURNS: +// - *GetZoneListResult: result of the zone list +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetZoneList() (*GetZoneListResult, error) { + result := &GetZoneListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX + "/zone"). + WithResult(result). + Do() + + return result, err +} + +// ListsSubnet - list all Subnets +// +// PARAMS: +// - c: the client agent which can perform sending request +// - args: the arguments to list all subnets, not necessary +// +// RETURNS: +// - *ListSubnetsResult: result of the subnet list +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListSubnets(args *ListSubnetsArgs) (*ListSubnetsResult, error) { + if args == nil { + args = &ListSubnetsArgs{} + } + result := &ListSubnetsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX+"/subnet"). + WithQueryParam("vpcId", args.VpcId). + WithResult(result). + Do() + if args.ZoneName == "" { + return result, err + } + // to compat rds api, filter by zone and vpcId + if result.Subnets != nil && len(result.Subnets) > 0 { + var filterd = []Subnet{} + for _, subnet := range result.Subnets { + // subnet az is logical zone + if subnet.Az == args.ZoneName { + if args.VpcId == "" || args.VpcId == subnet.VpcId { + filterd = append(filterd, subnet) + } + } + } + result.Subnets = filterd + } + return result, err +} + +// ListPool - list current pools +// RETURNS: +// - *ListResultWithMarker: the result of list hosts with marker +// - error: nil if success otherwise the specific error +func (cli *DDCClient) ListPool(marker *Marker) (*ListPoolResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getPoolUri()) + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPoolResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// ListDeploySets - list all deploy sets +// RETURNS: +// - *ListResultWithMarker: the result of list deploy sets with marker +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListDeploySets(poolId string, marker *Marker) (*ListDeploySetResult, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUri(poolId)) + req.SetMethod(http.GET) + if marker != nil { + req.SetParam(KEY_MARKER, marker.Marker) + req.SetParam(KEY_MAX_KEYS, strconv.Itoa(marker.MaxKeys)) + } + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListDeploySetResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - deploySetId: the id of the deploy set +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) DeleteDeploySet(poolId string, deploySetId string) error { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(poolId, deploySetId)) + req.SetMethod(http.DELETE) + + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + defer func() { resp.Body().Close() }() + + return nil +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *DeploySet: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDeploySet(poolId string, deploySetId string) (*DeploySet, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getDeploySetUriWithId(poolId, deploySetId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &DeploySet{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} + +// GetSecurityIps - get all SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *GetSecurityIpsResult: all security IP +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetSecurityIps(instanceId string) (*GetSecurityIpsResult, error) { + rowResult := &SecurityIpsRawResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/authIp"). + WithResult(rowResult). + Do() + // to compat rds api,json annotations for SecurityIps are different + result := &GetSecurityIpsResult{ + SecurityIps: rowResult.SecurityIps, + } + return result, err +} + +// UpdateSecurityIps - update SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: all SecurityIps +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateSecurityIps(instacneId string, args *UpdateSecurityIpsArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instacneId)+"/updateAuthIp"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ListParameters - list all parameters of a RDS instance +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListParametersResult: the result of list all parameters +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListParameters(instanceId string) (*ListParametersResult, error) { + result := &ListParametersResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/parameter" + "/list"). + WithResult(result). + Do() + + return result, err +} + +// UpdateParameter - update Parameter +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: *UpdateParameterArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateParameter(instanceId string, args *UpdateParameterArgs) (*ProducedMaintainTaskResult, error) { + + result := &ProducedMaintainTaskResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceIdV2(instanceId)+"/parameter"+"/modify"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateBackup - create backup of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateBackup(instanceId string) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDdcUriWithInstanceId(instanceId)+"/snapshot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return err +} + +// GetBackupDetail - get details of the instance'Backup +// +// PARAMS: +// - instanceId: the id of the instance +// - snapshotId: the id of the backup +// +// RETURNS: +// - *BackupDetailResult: the detail of the backup +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetBackupDetail(instanceId string, snapshotId string) (*BackupDetailResult, error) { + result := &BackupDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/snapshot" + "/" + snapshotId). + WithResult(result). + Do() + + return result, err +} + +// ModifyBackupPolicy - update backupPolicy +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: the specific rds Instance's BackupPolicy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ModifyBackupPolicy(instanceId string, args *BackupPolicy) error { + + result := &bce.BceResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/snapshot"+"/update"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return err +} + +// GetBinlogList - get backup list of the instance +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *BinlogListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetBinlogList(instanceId string, datetime string) (*BinlogListResult, error) { + + result := &BinlogListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)+"/binlog"). + WithQueryParam("datetime", datetime). + WithResult(result). + Do() + + return result, err +} + +// GetBinlogDetail - get details of the instance'Binlog +// +// PARAMS: +// - instanceId: the id of the instance +// - binlog: the id of the binlog +// +// RETURNS: +// - *BinlogDetailResult: the detail of the binlog +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetBinlogDetail(instanceId string, binlog string) (*BinlogDetailResult, error) { + result := &BinlogDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId) + "/binlog" + "/" + binlog). + WithResult(result). + Do() + + return result, err +} + +// SwitchInstance - main standby switching of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// - args: switch now or wait to the maintain time +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) SwitchInstance(instanceId string, args *SwitchArgs) (*ProducedMaintainTaskResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + result := &ProducedMaintainTaskResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/switchMaster"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateDatabase - create a database with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateDatabase(instanceId string, args *CreateDatabaseArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.DbName == "" { + return fmt.Errorf("unset DbName") + } + + if args.CharacterSetName == "" { + return fmt.Errorf("unset CharacterSetName") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDatabaseUriWithInstanceId(instanceId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteDatabase - delete an database of a DDC instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - dbName: the specific database's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) DeleteDatabase(instanceId, dbName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + Do() +} + +// UpdateDatabaseRemark - update a database remark with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - dbName: the specific accountName +// - args: the arguments to update a database remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateDatabaseRemark(instanceId string, dbName string, args *UpdateDatabaseRemarkArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + //if args.Remark == "" { + // return fmt.Errorf("unset Remark") + //} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + WithQueryParam("remark", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetDatabase - get an database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - dbName: the specific database's name +// +// RETURNS: +// - *Database: the database's meta +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDatabase(instanceId, dbName string) (*Database, error) { + result := &DatabaseResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDatabaseUriWithDbName(instanceId, dbName)). + WithResult(result). + Do() + + result.Database.DbStatus = strings.Title(result.Database.DbStatus) + return &result.Database, err +} + +// ListDatabase - list all database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *ListDatabaseResult: the result of list all database, contains all databases' meta +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListDatabase(instanceId string) (*ListDatabaseResult, error) { + result := &ListDatabaseResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDatabaseUriWithInstanceId(instanceId)). + WithResult(result). + Do() + + if result.Databases != nil { + for idx, _ := range result.Databases { + result.Databases[idx].DbStatus = strings.Title(result.Databases[idx].DbStatus) + } + } + return result, err +} + +// GetTableAmount - query amount of tables +// +// PARAMS: +// - args: the specific ddc instanceId, dbName and search pattern +// +// RETURNS: +// - *TableAmountResult: the size of the table that meets the criteria +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetTableAmount(args *GetTableAmountArgs) (*TableAmountResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.InstanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(args.DbName) < 1 { + return nil, fmt.Errorf("unset dbName") + } + result := &TableAmountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParam("pattern", args.Pattern). + WithURL(getQueryDatabaseUriWithDbName(args.InstanceId, args.DbName)). + WithResult(result). + Do() + + return result, err +} + +// GetDatabaseDiskUsage - get the disk footprint and the remaining space for database +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *ListDatabaseResult: the disk footprint and the remaining space for database +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDatabaseDiskUsage(instanceId, dbName string) (*DatabaseDiskUsageResult, error) { + result := &DatabaseDiskUsageResult{} + req := bce.NewRequestBuilder(c) + if len(dbName) > 0 { + req.WithQueryParam("pattern", dbName) + } + err := req. + WithMethod(http.GET). + WithURL(getDatabaseDiskUsageUriWithInstanceId(instanceId)). + WithResult(result). + Do() + return result, err +} + +// GetRecoverableDateTime - get a list of recoverable times +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *GetRecoverableDateTimeResult: the result of list all recoverable datetimes +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetRecoverableDateTime(instanceId string) (*GetRecoverableDateTimeResult, error) { + result := &GetRecoverableDateTimeResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDatabaseRecoverTimeUriWithInstanceId(instanceId)). + WithResult(result). + Do() + return result, err +} + +// RecoverToSourceInstanceByDatetime - recover database or tables for the specific instance by a datetime +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *MaintainTaskIdResult: ID of generated maintain task +// - error: nil if success otherwise the specific error +func (c *DDCClient) RecoverToSourceInstanceByDatetime(instanceId string, args *RecoverInstanceArgs) (*MaintainTaskIdResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.Datetime) < 1 { + return nil, fmt.Errorf("unset datetime. Please query recoverable datetime by GetRecoverableDateTime()") + } + if args.RecoverData == nil || len(args.RecoverData) < 1 { + return nil, fmt.Errorf("unset recover data") + } + result := &MaintainTaskIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRecoverInstanceDatabaseUriWithInstanceId(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// CreateAccount - create a account with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) CreateAccount(instanceId string, args *CreateAccountArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.AccountName == "" { + return fmt.Errorf("unset AccountName") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + if args.AccountType == "" { + args.AccountType = "common" + } + if args.AccountType == "Super" { + args.AccountType = "rdssuper" + } + if args.AccountType == "Common" { + args.AccountType = "common" + } + if args.DatabasePrivileges != nil { + for idx, _ := range args.DatabasePrivileges { + if args.DatabasePrivileges[idx].AuthType == "ReadOnly" { + args.DatabasePrivileges[idx].AuthType = "readOnly" + } else if args.DatabasePrivileges[idx].AuthType == "ReadWrite" { + args.DatabasePrivileges[idx].AuthType = "readWrite" + } + } + } + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getAccountUriWithInstanceId(instanceId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteAccount - delete an account of a RDS instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - accountName: the specific account's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) DeleteAccount(instanceId, accountName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + Do() +} + +// UpdateAccountPassword - update a account password with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateAccountPassword(instanceId string, accountName string, args *UpdateAccountPasswordArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("password", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountDesc - update a account desc with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateAccountDesc(instanceId string, accountName string, args *UpdateAccountDescArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + //if args.Remark == "" { + // return fmt.Errorf("unset Remark") + //} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("remark", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountPrivileges - update a account privileges with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account privileges +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateAccountPrivileges(instanceId string, accountName string, args *UpdateAccountPrivilegesArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + for idx, _ := range args.DatabasePrivileges { + if args.DatabasePrivileges[idx].AuthType == "ReadOnly" { + args.DatabasePrivileges[idx].AuthType = "readOnly" + } else if args.DatabasePrivileges[idx].AuthType == "ReadWrite" { + args.DatabasePrivileges[idx].AuthType = "readWrite" + } + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithQueryParam("privileges", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetAccount - get an account of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - accountName: the specific account's name +// +// RETURNS: +// - *Account: the account's meta +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetAccount(instanceId, accountName string) (*Account, error) { + result := &AccountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAccountUriWithAccountName(instanceId, accountName)). + WithResult(result). + Do() + + if result.Account.AccountType == "common" { + result.Account.AccountType = "Common" + } else if result.Account.AccountType == "rdssuper" { + result.Account.AccountType = "Super" + } + + for idx, _ := range result.Account.DatabasePrivileges { + if result.Account.DatabasePrivileges[idx].AuthType == "readOnly" { + result.Account.DatabasePrivileges[idx].AuthType = "ReadOnly" + } else if result.Account.DatabasePrivileges[idx].AuthType == "readWrite" { + result.Account.DatabasePrivileges[idx].AuthType = "ReadWrite" + } + } + result.Account.Status = strings.Title(result.Account.Status) + return &result.Account, err +} + +// ListAccount - list all account of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListAccountResult: the result of list all account, contains all accounts' meta +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListAccount(instanceId string) (*ListAccountResult, error) { + result := &ListAccountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAccountUriWithInstanceId(instanceId)). + WithResult(result). + Do() + for idx, _ := range result.Accounts { + if result.Accounts[idx].AccountType == "common" { + result.Accounts[idx].AccountType = "Common" + } else if result.Accounts[idx].AccountType == "rdssuper" { + result.Accounts[idx].AccountType = "Super" + } + result.Accounts[idx].Status = strings.Title(result.Accounts[idx].Status) + + for iidx, _ := range result.Accounts[idx].DatabasePrivileges { + if result.Accounts[idx].DatabasePrivileges[iidx].AuthType == "readOnly" { + result.Accounts[idx].DatabasePrivileges[iidx].AuthType = "ReadOnly" + } else if result.Accounts[idx].DatabasePrivileges[iidx].AuthType == "readWrite" { + result.Accounts[idx].DatabasePrivileges[iidx].AuthType = "ReadWrite" + } + } + } + return result, err +} + +// ListRoGroup - list all roGroups of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListRoGroupResult: All roGroups of the current instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListRoGroup(instanceId string) (*ListRoGroupResult, error) { + result := &ListRoGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRoGroupUriWithInstanceId(instanceId) + "/list"). + WithResult(result). + Do() + + return result, err +} + +// ListVpc - list all Vpc +// +// PARAMS: +// RETURNS: +// - *ListVpc: All vpc of +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListVpc() (*[]VpcVo, error) { + result := &[]VpcVo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri() + "/vpcList"). + WithResult(result). + Do() + + return result, err +} + +// GetMaintainTime - get details of the maintainTime +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *MaintainTime: the maintainTime of the instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetMaintainTime(instanceId string) (*MaintainTime, error) { + // Build the request + req := &bce.BceRequest{} + req.SetUri(getMaintainTimeUriWithInstanceId(instanceId)) + req.SetMethod(http.GET) + + // Send request and get response + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &MaintainWindow{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + + return &jsonBody.MaintainTime, nil +} + +// UpdateMaintainTime - update UpdateMaintainTime of instance +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UpdateMaintainTime(instanceId string, args *MaintainTime) error { + if args == nil { + return fmt.Errorf("unset args") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUpdateMaintainTimeUriWithInstanceId(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListRecycleInstances - list all instances in recycler with marker +// +// PARAMS: +// - marker: marker page +// +// RETURNS: +// - *RecyclerInstanceList: the result of instances in recycler +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListRecycleInstances(marker *Marker) (*RecyclerInstanceList, error) { + result := &RecyclerInstanceList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParams(getMarkerParams(marker)). + WithURL(getRecyclerUrl()). + WithResult(result). + Do() + + return result, err +} + +// RecoverRecyclerInstances - batch recover instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to recover +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) RecoverRecyclerInstances(instanceIds []string) (*OrderIdResponse, error) { + if instanceIds == nil || len(instanceIds) < 1 { + return nil, fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return nil, fmt.Errorf("the instanceIds length max value is 10") + } + + args := &BatchInstanceIds{ + InstanceIds: strings.Join(instanceIds, COMMA), + } + + result := &OrderIdResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRecyclerRecoverUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DeleteRecyclerInstances - batch delete instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) DeleteRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + // delete use query params + instanceIdsParam := strings.Join(instanceIds, COMMA) + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRecyclerDeleteUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("instanceIds", instanceIdsParam). + Do() + return err +} + +// ListSecurityGroupByVpcId - list security groups by vpc id +// +// PARAMS: +// - vpcId: id of vpc +// +// RETURNS: +// - *[]SecurityGroup:security groups of vpc +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListSecurityGroupByVpcId(vpcId string) (*[]SecurityGroup, error) { + if len(vpcId) < 1 { + return nil, fmt.Errorf("unset vpcId") + } + result := &[]SecurityGroup{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithVpcIdUrl(vpcId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// ListSecurityGroupByInstanceId - list security groups by instance id +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *ListSecurityGroupResult: list secrity groups result of instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListSecurityGroupByInstanceId(instanceId string) (*ListSecurityGroupResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + result := &ListSecurityGroupResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithInstanceIdUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// BindSecurityGroups - bind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) BindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// UnBindSecurityGroups - unbind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) UnBindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUnBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ReplaceSecurityGroups - replace SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ReplaceSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getReplaceSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListLogByInstanceId - list error or slow logs of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *[]Log:logs of instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) ListLogByInstanceId(instanceId string, args *ListLogArgs) (*[]Log, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if args == nil { + return nil, fmt.Errorf("unset list args") + } + if "error" != args.LogType && "slow" != args.LogType { + return nil, fmt.Errorf("invalid logType, should be 'error' or 'slow'") + } + result := &[]Log{} + _, err := time.Parse("2006-01-02", args.Datetime) + if err != nil { + return nil, err + } + err = bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithInstanceId(instanceId)). + WithQueryParam("logType", strings.ToLower(args.LogType)). + WithQueryParam("datetime", args.Datetime). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetLogById - get log's detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Log:log's detail of instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetLogById(instanceId, logId string, args *GetLogArgs) (*LogDetail, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(logId) < 1 { + return nil, fmt.Errorf("unset logId") + } + if args == nil { + return nil, fmt.Errorf("unset get log args") + } + + result := &LogDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithLogId(instanceId, logId)). + WithQueryParam("downloadValidTimeInSec", strconv.Itoa(args.ValidSeconds)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// LazyDropCreateHardLink - create a hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) LazyDropCreateHardLink(instanceId, dbName, tableName string) error { + if len(instanceId) < 1 { + return fmt.Errorf("unset instanceId") + } + if len(dbName) < 1 { + return fmt.Errorf("unset dbName") + } + if len(tableName) < 1 { + return fmt.Errorf("unset tableName") + } + + args := &CreateTableHardLinkArgs{ + TableName: tableName, + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getCreateTableHardLinkUrl(instanceId, dbName)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// LazyDropDeleteHardLink - delete the hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) LazyDropDeleteHardLink(instanceId, dbName, tableName string) (*MaintainTaskIdResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(dbName) < 1 { + return nil, fmt.Errorf("unset dbName") + } + if len(tableName) < 1 { + return nil, fmt.Errorf("unset tableName") + } + result := &MaintainTaskIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getTableHardLinkUrl(instanceId, dbName, tableName)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// ResizeRds - resize an RDS with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to resize an RDS +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ResizeRds(instanceId string, args *ResizeRdsArgs) (*OrderIdResponse, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + result := &OrderIdResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/resize"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateSyncMode - update sync mode of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update syncMode +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ModifySyncMode(instanceId string, args *ModifySyncModeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getChangeSemiSyncStatusUrlWithId(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetDisk - get disk detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Disk:disk of instance +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetDisk(instanceId string) (*Disk, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &Disk{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)+"/disk"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetMachineInfo - get machine info of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *MachineInfo:info of machine resource +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetMachineInfo(instanceId string) (*MachineInfo, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &MachineInfo{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUriWithInstanceId(instanceId)+"/machine"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetResidual - get residual of pool +// +// PARAMS: +// - poolId: id of pool +// - zoneName: the zone name +// +// RETURNS: +// - *GetResidualResult:residual of pool +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetResidual(poolId string) (*GetResidualResult, error) { + if len(poolId) < 1 { + return nil, fmt.Errorf("unset poolId") + } + + result := &GetResidualResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getPoolUriWithId(poolId)+"/residual"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetFlavorCapacity - get flavor capacity of pool +// +// PARAMS: +// - poolId: id of pool +// - args: request params +// +// RETURNS: +// - *GetResidualResult:get flavor capacity of pool +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetFlavorCapacity(poolId string, args *GetFlavorCapacityArgs) (*GetFlavorCapacityResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(poolId) < 1 { + return nil, fmt.Errorf("unset poolId") + } + + result := &GetFlavorCapacityResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getPoolUriWithId(poolId)+"/flavorCap"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("cpuInCore", strconv.Itoa(args.CpuInCore)). + WithQueryParam("diskInGb", strconv.FormatInt(args.DiskInGb, 10)). + WithQueryParam("memoryInGb", strconv.FormatInt(args.MemoryInGb, 10)). + WithQueryParam("affinity", strconv.FormatInt(args.Affinity, 10)). + WithResult(result). + Do() + return result, err +} + +// KillSession - start kill session tasks +// +// PARAMS: +// - instanceId: id of the instance +// - args: instance role and sessionIds +// +// RETURNS: +// - *KillSessionResult: the response of kill session task id +// - error: nil if success otherwise the specific error +func (c *DDCClient) KillSession(instanceId string, args *KillSessionArgs) (*KillSessionResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + result := &KillSessionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getKillSessionUri(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + if err != nil { + return nil, err + } + return result, err +} + +// GetKillSessionTask - get kill session tasks by taskId +// +// PARAMS: +// - instanceId: the specific instanceId of ddc +// - taskId: kill session returned id +// +// RETURNS: +// - *GetKillSessionTaskResult: the response of kill session task +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetKillSessionTask(instanceId string, taskId int) (*GetKillSessionTaskResult, error) { + result := &GetKillSessionTaskResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getKillSessionTaskUri(instanceId, taskId)). + WithResult(result). + Do() + + return result, err +} + +// GetMaintainTaskList - get maintain tasks by taskId +// +// PARAMS: +// RETURNS: +// - *ListMaintainTaskResult: the response of maintain tasks +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetMaintainTaskList(args *GetMaintainTaskListArgs) (*ListMaintainTaskResult, error) { + if args == nil || len(args.StartTime) < 1 { + return nil, fmt.Errorf("unset startTime") + } + result := &ListMaintainTaskResult{} + req := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getMaintainTaskUri()). + WithResult(result). + WithQueryParams(getMarkerParams(&args.Marker)). + WithQueryParam("startTime", args.StartTime) + if len(args.EndTime) > 0 { + req.WithQueryParam("endTime", args.EndTime) + } + err := req.Do() + return result, err +} + +// GetMaintainTaskDetail - get maintain task detail by taskId +// +// PARAMS: +// RETURNS: +// - *MaintainTaskDetailList: the response of maintain task detail +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetMaintainTaskDetail(taskIds string) (*MaintainTaskDetailList, error) { + if len(taskIds) < 1 { + return nil, fmt.Errorf("unset taskIds") + } + result := &MaintainTaskDetailList{} + req := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getMaintainTaskDetailUri()). + WithResult(result). + WithQueryParam("taskId", taskIds) + err := req.Do() + return result, err +} + +// ExecuteMaintainTaskImmediately - execute maintain task immediately +// +// PARAMS: +// - taskId: id of the task +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) ExecuteMaintainTaskImmediately(taskId string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getMaintainTaskUriWithTaskId(taskId)+"/executeNow"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + if err != nil { + return err + } + return nil +} + +// CancelMaintainTask - cancel maintain task +// +// PARAMS: +// - taskId: id of the task +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *DDCClient) CancelMaintainTask(taskId string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getMaintainTaskUriWithTaskId(taskId)+"/cancel"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + if err != nil { + return err + } + return nil +} + +// GetAccessLog - get access logs's download info by date +// +// PARAMS: +// RETURNS: +// - *AccessLog: the access logs's download info +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetAccessLog(date string) (*AccessLog, error) { + if len(date) < 1 { + return nil, fmt.Errorf("unset date") + } + result := &AccessLog{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getAccessLogUrl()). + WithResult(result). + WithQueryParam("date", date). + Do() + + return result, err +} + +// GetErrorLog - get error logs +// +// PARAMS: +// RETURNS: +// - *ErrorLogsResponse: the error logs +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetErrorLogs(args *GetErrorLogsArgs) (*ErrorLogsResponse, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.InstanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(args.StartTime) < 1 { + return nil, fmt.Errorf("unset startTime") + } + if len(args.EndTime) < 1 { + return nil, fmt.Errorf("unset endTime") + } + if args.PageNo < 1 { + return nil, fmt.Errorf("unset pageNo") + } + if args.PageSize < 1 { + return nil, fmt.Errorf("unset pageSize") + } + if len(args.Role) < 1 { + return nil, fmt.Errorf("unset role") + } + + result := &ErrorLogsResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getErrorLogsUrlWithInstanceId(args.InstanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetSlowLog - get slow logs +// +// PARAMS: +// RETURNS: +// - *SlowLogsResponse: the slow logs +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetSlowLogs(args *GetSlowLogsArgs) (*SlowLogsResponse, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.InstanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(args.StartTime) < 1 { + return nil, fmt.Errorf("unset startTime") + } + if len(args.EndTime) < 1 { + return nil, fmt.Errorf("unset endTime") + } + if args.PageNo < 1 { + return nil, fmt.Errorf("unset pageNo") + } + if args.PageSize < 1 { + return nil, fmt.Errorf("unset pageSize") + } + if len(args.Role) < 1 { + return nil, fmt.Errorf("unset role") + } + + result := &SlowLogsResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getSlowLogsUrlWithInstanceId(args.InstanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetInstanceBackupStatus - get instance backup status and backup start time +// +// PARAMS: +// RETURNS: +// - *GetBackupStatusResponse: the response of backup status +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetInstanceBackupStatus(instanceId string) (*GetBackupStatusResponse, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &GetBackupStatusResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceBackupStatusUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// InstanceVersionRollBack - rollback instance version from 5.7 to 5.6 +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to set WaitSwitch +// +// RETURNS: +// - *MaintainTaskIdResult: ID of generated maintain task +// - error: nil if success otherwise the specific error +func (c *DDCClient) InstanceVersionRollBack(instanceId string, args *InstanceVersionRollBackArg) (*MaintainTaskIdResult, error) { + + result := &MaintainTaskIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/rollback"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// InstanceVersionUpgrade - upgrade instance version from 5.6 to 5.7 +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to set IsUpgradeNow +// +// RETURNS: +// - *MaintainTaskIdResult: ID of generated maintain task +// - error: nil if success otherwise the specific error +func (c *DDCClient) InstanceVersionUpgrade(instanceId string, args *InstanceVersionUpgradeArg) (*MaintainTaskIdResult, error) { + + result := &MaintainTaskIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDdcUriWithInstanceId(instanceId)+"/upgrade"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// GetInstanceSyncDelay - get readonly instance syncDelay and syncStatus. +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *InstanceSyncDelayResponse: the response of syncDelay +// - error: nil if success otherwise the specific error +func (c *DDCClient) GetInstanceSyncDelay(instanceId string) (*InstanceSyncDelayResponse, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &InstanceSyncDelayResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceSyncDelayUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// InstanceSyncDelayReplication - start or stop readonly instance syncDelay. +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *InstanceSyncDelayReplicationResponse: the response of success +// - error: nil if success otherwise the specific error +func (c *DDCClient) InstanceSyncDelayReplication(instanceId string, args *InstanceSyncDelayReplicationArg) (*InstanceSyncDelayReplicationResponse, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &InstanceSyncDelayReplicationResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceSyncDelayReplicationUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// SnapshotAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *DDCClient) SnapshotAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + result := &BackupAccessDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri()+"/snapshot/accessdetail"). + WithQueryParam("startDateTime", args.StartDateTime). + WithQueryParam("endDateTime", args.EndDateTime). + WithQueryParam("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("dataType", args.DataType). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// BinlogAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *DDCClient) BinlogAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + result := &BackupAccessDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDdcUri()+"/binlog/accessdetail"). + WithQueryParam("startDateTime", args.StartDateTime). + WithQueryParam("endDateTime", args.EndDateTime). + WithQueryParam("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("dataType", args.DataType). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/ddc/v2/ddcrds.go b/bce-sdk-go/services/ddc/v2/ddcrds.go new file mode 100644 index 0000000..fcb98b1 --- /dev/null +++ b/bce-sdk-go/services/ddc/v2/ddcrds.go @@ -0,0 +1,1636 @@ +package ddcrds + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/services/ddc/ddc_util" + "github.com/baidubce/bce-sdk-go/services/rds" +) + +// Int int point helper +func Int(value string) *int { + if len(value) < 1 { + return nil + } + intPtr, err := strconv.Atoi(value) + if err != nil { + panic("please pass valid int value ") + } + return &intPtr +} + +func Json(v interface{}) string { + jsonStr, err := json.Marshal(v) + if err != nil { + panic("convert to json faild") + } + return string(jsonStr) +} + +func isDDC(productType string) bool { + return productType == "ddc" || productType == "DDC" +} + +// 根据实例ID判断产品类型是否为DDC +func isDDCId(instanceId string) bool { + if strings.HasPrefix(instanceId, "rds") { + return false + } + return true +} + +func getDDCAndRDSIds(instanceIds string) (string, string) { + instanceIdArr := strings.Split(instanceIds, ",") + ddcIds, rdsIds := "", "" + if len(instanceIdArr) > 0 { + for _, id := range instanceIdArr { + if isDDCId(id) { + ddcIds += id + "," + } else { + rdsIds += id + "," + } + } + } + if strings.HasSuffix(ddcIds, ",") { + ddcIds = ddcIds[:len(ddcIds)-1] + } + if strings.HasSuffix(rdsIds, ",") { + rdsIds = rdsIds[:len(rdsIds)-1] + } + return ddcIds, rdsIds +} + +// CreateInstance - create a Instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create a instance +// +// RETURNS: +// - *InstanceIds: the result of create RDS, contains new RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateRds(args *CreateRdsArgs, productType string) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + // 限制InstanceName + if strings.Contains(args.InstanceName, ".") { + return nil, fmt.Errorf("invalid InstanceName:. not support") + } + var result *CreateResult + var err error + if isDDC(productType) { + result, err = c.ddcClient.CreateRds(args) + } else { + rdsArgs := &rds.CreateRdsArgs{} + // copy 请求参数 + err = ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return nil, err + } + var rdsRes *rds.CreateResult + rdsRes, err = c.rdsClient.CreateRds(rdsArgs) + if rdsRes != nil { + result = &CreateResult{InstanceIds: rdsRes.InstanceIds} + } + } + + return result, err +} + +// CreateReadReplica - create a readReplica RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica rds +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica RDS, contains the readReplica RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateReadReplica(args *CreateReadReplicaArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.SourceInstanceId) < 1 { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + // 默认创建1个只读实例 + if args.PurchaseCount < 1 { + args.PurchaseCount = 1 + } + var result *CreateResult + var err error + if isDDCId(args.SourceInstanceId) { + result, err = c.ddcClient.CreateReadReplica(args) + } else { + rdsArgs := &rds.CreateReadReplicaArgs{} + // copy 请求参数 + err = ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return nil, err + } + var rdsRes *rds.CreateResult + rdsRes, err = c.rdsClient.CreateReadReplica(rdsArgs) + if rdsRes != nil { + result = &CreateResult{InstanceIds: rdsRes.InstanceIds} + } + } + return result, err +} + +// UpdateRoGroup - update a roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateRoGroup(roGroupId string, args *UpdateRoGroupArgs, productType string) error { + if isDDC(productType) { + return c.ddcClient.UpdateRoGroup(roGroupId, args) + } + return RDSNotSupportError() +} + +// UpdateRoGroupReplicaWeight- update repica weight in roGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateRoGroupReplicaWeight(roGroupId string, args *UpdateRoGroupWeightArgs, productType string) error { + if isDDC(productType) { + return c.ddcClient.UpdateRoGroupReplicaWeight(roGroupId, args) + } + return RDSNotSupportError() +} + +// ReBalanceRoGroup- Initiate a rebalance for foGroup +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReBalanceRoGroup(roGroupId, productType string) error { + if len(roGroupId) < 1 { + return fmt.Errorf("unset roGroupId") + } + if isDDC(productType) { + return c.ddcClient.ReBalanceRoGroup(roGroupId) + } + return RDSNotSupportError() +} + +// CreateRdsProxy - create a proxy RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica rds +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica RDS, contains the readReplica RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateRdsProxy(args *CreateRdsProxyArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if len(args.SourceInstanceId) < 1 { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + if isDDCId(args.SourceInstanceId) { + return nil, DDCNotSupportError() + } + // copy请求参数 + rdsArgs := &rds.CreateRdsProxyArgs{} + err := ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return nil, err + } + // copy返回结果 + rdsRes, err := c.rdsClient.CreateRdsProxy(rdsArgs) + if err != nil { + return nil, err + } + result := &CreateResult{InstanceIds: rdsRes.InstanceIds} + return result, err +} + +func (c *Client) listRdsInstance(marker *ListRdsArgs) (*ListRdsResult, error) { + rdsArgs := &rds.ListRdsArgs{} + // copy请求参数 + err := ddc_util.SimpleCopyProperties(rdsArgs, marker) + if err != nil { + return nil, err + } + var rdsRes *rds.ListRdsResult + rdsRes, err = c.rdsClient.ListRds(rdsArgs) + // copy返回结果 + result := &ListRdsResult{} + if rdsRes != nil { + err = ddc_util.SimpleCopyProperties(result, rdsRes) + } + if result.Instances != nil && + len(result.Instances) > 0 { + for i := range result.Instances { + err = convertRdsInstance(&result.Instances[i]) + } + } + return result, err +} + +// ListPage - list all instances with page +// RETURNS: +// - *ListRdsResult: the result of list instances with page +// - error: nil if success otherwise the specific error +func (c *Client) ListPage(args *ListPageArgs) (*ListPageResult, error) { + result, err := c.ddcClient.ListPage(args) + return result, err +} + +// ListRds - list all instances +// RETURNS: +// - *ListRdsResult: the result of list instances with marker +// - error: nil if success otherwise the specific error +func (c *Client) ListRds(marker *ListRdsArgs) (*ListRdsResult, error) { + var result *ListRdsResult + var err error + // 先获取DDC列表 + if len(marker.Marker) < 1 || marker.Marker == "-1" || isDDCId(marker.Marker) { + result, err = c.ddcClient.ListRds(marker) + if err != nil { + return nil, err + } + // 数量不够时获取RDS列表 + if len(result.Instances) < marker.MaxKeys { + // 修改marker + marker.MaxKeys = marker.MaxKeys - len(result.Instances) + rdsResult, err := c.listRdsInstance(marker) + if err != nil { + return nil, err + } + // 合并结果到result + if rdsResult != nil && len(rdsResult.Instances) > 0 { + result.Instances = append(result.Instances, rdsResult.Instances...) + result.Marker = rdsResult.Marker + result.IsTruncated = rdsResult.IsTruncated + result.NextMarker = rdsResult.NextMarker + } + } else if !result.IsTruncated { + // 使用IsTruncated判断DDC实例是否已查询完 + marker.MaxKeys = 1 + rdsResult, err := c.listRdsInstance(marker) + if err != nil { + return nil, err + } + if rdsResult != nil && len(rdsResult.Instances) > 0 { + result.NextMarker = rdsResult.Instances[0].InstanceId + result.IsTruncated = true + } + } + return result, err + } + // marker 到达rds时,直接取rds列表 + result, err = c.listRdsInstance(marker) + return result, err +} + +// GetDetail - get a specific ddc Instance's detail +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *Instance: the specific ddc Instance's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetDetail(instanceId string) (*Instance, error) { + var result *Instance + var err error + if isDDCId(instanceId) { + result, err = c.ddcClient.GetDetail(instanceId) + } else { + rdsRes, err1 := c.rdsClient.GetDetail(instanceId) + if err1 != nil { + return nil, err + } + // copy返回结果 + if rdsRes != nil { + result = &Instance{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + if err != nil { + return nil, err + } + err = convertRdsInstance(result) + } + } + + return result, err +} + +// 修改RDS中参数类型无法匹配的字段 +func convertRdsInstance(result *Instance) error { + // rds公网访问 + // Closed 未开放外网权限 + // Creating 公网开通中,成功后状态为Available + // Available 已开通公网 + result.PubliclyAccessible = result.PublicAccessStatus == "Available" + days := result.BackupPolicy.ExpireInDaysStr + var err error + if len(days) > 0 { + result.BackupPolicy.ExpireInDaysInt, err = strconv.Atoi(days) + } + return err +} + +// DeleteRds - delete instances +// +// PARAMS: +// - instanceIds: id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRds(instanceIds string) error { + var err error + ddcIds, rdsIds := getDDCAndRDSIds(instanceIds) + if len(ddcIds) > 0 { + err = c.ddcClient.DeleteRds(ddcIds) + } + if err != nil { + return err + } + if len(rdsIds) > 0 { + err = c.rdsClient.DeleteRds(rdsIds) + } + return err +} + +// ResizeRds - resize an RDS with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to resize an RDS +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeRds(instanceId string, args *ResizeRdsArgs) (*OrderIdResponse, error) { + if isDDCId(instanceId) { + return c.ddcClient.ResizeRds(instanceId, args) + } + // copy请求参数 + resReq := &rds.ResizeRdsArgs{} + err := ddc_util.SimpleCopyProperties(resReq, args) + if err != nil { + return nil, err + } + err = c.rdsClient.ResizeRds(instanceId, resReq) + return nil, err +} + +// CreateAccount - create a account with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateAccount(instanceId string, args *CreateAccountArgs) error { + var err error + if isDDCId(instanceId) { + err = c.ddcClient.CreateAccount(instanceId, args) + } else { + rdsArgs := &rds.CreateAccountArgs{} + // copy请求参数 + err = ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return err + } + err = c.rdsClient.CreateAccount(instanceId, rdsArgs) + } + + return err +} + +// ListAccount - list all account of a RDS instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListAccountResult: the result of list all account, contains all accounts' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListAccount(instanceId string) (*ListAccountResult, error) { + var result *ListAccountResult + var err error + if isDDCId(instanceId) { + result, err = c.ddcClient.ListAccount(instanceId) + } else { + var rdsRes *rds.ListAccountResult + rdsRes, err = c.rdsClient.ListAccount(instanceId) + // copy返回结果 + if rdsRes != nil { + result = &ListAccountResult{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + } + } + + return result, err +} + +// GetAccount - get an account of a RDS instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - accountName: the specific account's name +// +// RETURNS: +// - *Account: the account's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetAccount(instanceId, accountName string) (*Account, error) { + var result *Account + var err error + if isDDCId(instanceId) { + result, err = c.ddcClient.GetAccount(instanceId, accountName) + } else { + var rdsRes *rds.Account + rdsRes, err = c.rdsClient.GetAccount(instanceId, accountName) + // copy返回结果 + if rdsRes != nil { + result = &Account{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + } + } + + return result, err +} + +// DeleteAccount - delete an account of a RDS instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - accountName: the specific account's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAccount(instanceId, accountName string) error { + var err error + if isDDCId(instanceId) { + err = c.ddcClient.DeleteAccount(instanceId, accountName) + } else { + err = c.rdsClient.DeleteAccount(instanceId, accountName) + } + return err +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(instanceId string) error { + if isDDCId(instanceId) { + args := &RebootArgs{IsRebootNow: true} + _, err := c.ddcClient.RebootInstanceWithArgs(instanceId, args) + return err + } + err := c.rdsClient.RebootInstance(instanceId) + return err +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// - args: reboot args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstanceWithArgs(instanceId string, args *RebootArgs) (*MaintainTaskIdResult, error) { + if isDDCId(instanceId) { + return c.ddcClient.RebootInstanceWithArgs(instanceId, args) + } + return nil, RDSNotSupportError() +} + +// UpdateInstanceName - update name of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceName(instanceId string, args *UpdateInstanceNameArgs) error { + var err error + if isDDCId(instanceId) { + err = c.ddcClient.UpdateInstanceName(instanceId, args) + } else { + rdsArgs := &rds.UpdateInstanceNameArgs{ + InstanceName: args.InstanceName, + } + err = c.rdsClient.UpdateInstanceName(instanceId, rdsArgs) + } + return err +} + +// UpdateSyncMode - update sync mode of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update syncMode +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifySyncMode(instanceId string, args *ModifySyncModeArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if isDDCId(instanceId) { + return c.ddcClient.ModifySyncMode(instanceId, args) + } + rdsArgs := &rds.ModifySyncModeArgs{SyncMode: args.SyncMode} + err := c.rdsClient.ModifySyncMode(instanceId, rdsArgs) + return err +} + +// ModifyEndpoint - modify the prefix of endpoint +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify endpoint +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyEndpoint(instanceId string, args *ModifyEndpointArgs) error { + if isDDCId(instanceId) { + return DDCNotSupportError() + } + rdsArgs := &rds.ModifyEndpointArgs{Address: args.Address} + err := c.rdsClient.ModifyEndpoint(instanceId, rdsArgs) + return err +} + +// ModifyPublicAccess - modify public access +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify public access +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyPublicAccess(instanceId string, args *ModifyPublicAccessArgs) error { + if isDDCId(instanceId) { + return DDCNotSupportError() + } + rdsArgs := &rds.ModifyPublicAccessArgs{PublicAccess: args.PublicAccess} + err := c.rdsClient.ModifyPublicAccess(instanceId, rdsArgs) + return err +} + +// GetBackupList - get backup list of the instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetBackupListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupList(instanceId string, args *GetBackupListArgs) (*GetBackupListResult, error) { + + var result *GetBackupListResult + var err error + if isDDCId(instanceId) { + result, err = c.ddcClient.GetBackupList(instanceId, args) + } else { + rdsArgs := &rds.GetBackupListArgs{} + // copy请求参数 + err = ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return nil, err + } + var rdsRes *rds.GetBackupListResult + rdsRes, err = c.rdsClient.GetBackupList(instanceId, rdsArgs) + // 转换返回结果 + if rdsRes != nil { + result = &GetBackupListResult{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + } + } + + return result, err +} + +// GetZoneList - list all zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *GetZoneListResult: result of the zone list +// - error: nil if success otherwise the specific error +func (c *Client) GetZoneList(productType string) (*GetZoneListResult, error) { + if isDDC(productType) { + return c.ddcClient.GetZoneList() + } + rdsRes, err := c.rdsClient.GetZoneList() + if err != nil { + return nil, err + } + result := &GetZoneListResult{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + if err != nil { + return nil, err + } + return result, err +} + +// ListsSubnet - list all Subnets +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list all subnets, not necessary +// +// RETURNS: +// - *ListSubnetsResult: result of the subnet list +// - error: nil if success otherwise the specific error +func (c *Client) ListSubnets(args *ListSubnetsArgs, productType string) (*ListSubnetsResult, error) { + if isDDC(productType) { + return c.ddcClient.ListSubnets(args) + } + rdsArgs := &rds.ListSubnetsArgs{} + err := ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return nil, err + } + rdsRes, err := c.rdsClient.ListSubnets(rdsArgs) + if err != nil { + return nil, err + } + result := &ListSubnetsResult{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + return result, err +} + +// GetSecurityIps - get all SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *GetSecurityIpsResult: all security IP +// - error: nil if success otherwise the specific error +func (c *Client) GetSecurityIps(instanceId string) (*GetSecurityIpsResult, error) { + if isDDCId(instanceId) { + return c.ddcClient.GetSecurityIps(instanceId) + } + rdsRes, err := c.rdsClient.GetSecurityIps(instanceId) + if err != nil { + return nil, err + } + result := &GetSecurityIpsResult{} + err = ddc_util.SimpleCopyProperties(result, rdsRes) + return result, err +} + +// UpdateSecurityIps - update SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Etag: get latest etag by GetSecurityIps +// - Args: all SecurityIps +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSecurityIps(instanceId, Etag string, args *UpdateSecurityIpsArgs) error { + if isDDCId(instanceId) { + return c.ddcClient.UpdateSecurityIps(instanceId, args) + } + rdsArgs := &rds.UpdateSecurityIpsArgs{SecurityIps: args.SecurityIps} + err := c.rdsClient.UpdateSecurityIps(instanceId, Etag, rdsArgs) + return err +} + +// ListParameters - list all parameters of a RDS instance +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListParametersResult: the result of list all parameters +// - error: nil if success otherwise the specific error +func (c *Client) ListParameters(instanceId string) (*ListParametersResult, error) { + if isDDCId(instanceId) { + return c.ddcClient.ListParameters(instanceId) + } + rdsRes, err := c.rdsClient.ListParameters(instanceId) + result := &ListParametersResult{} + err1 := ddc_util.SimpleCopyProperties(result, rdsRes) + if err1 != nil { + return nil, err1 + } + // 兼容rds处理 + if result.Parameters != nil && len(result.Parameters) > 0 { + for i, parameter := range result.Parameters { + result.Parameters[i].IsDynamic = parameter.Dynamic == "true" + result.Parameters[i].ISModifiable = parameter.Modifiable == "true" + } + } + return result, err +} + +// UpdateParameter - update Parameter +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Etag: get latest etag by ListParameters +// - Args: *UpdateParameterArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateParameter(instanceId, Etag string, args *UpdateParameterArgs) (*ProducedMaintainTaskResult, error) { + if isDDCId(instanceId) { + return c.ddcClient.UpdateParameter(instanceId, args) + } + rdsArgs := &rds.UpdateParameterArgs{} + err1 := ddc_util.SimpleCopyProperties(rdsArgs, args) + if err1 != nil { + return nil, err1 + } + err := c.rdsClient.UpdateParameter(instanceId, Etag, rdsArgs) + return nil, err +} + +// CreateDeploySet - create a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateDeploySet(poolId string, args *CreateDeployRequest) (*CreateDeployResult, error) { + return c.ddcClient.CreateDeploySet(poolId, args) +} + +// UpdateDeploySet - create a deploy set +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDeploySet(poolId, deployId string, args *UpdateDeployRequest) error { + return c.ddcClient.UpdateDeploySet(poolId, deployId, args) +} + +// ListDeploySets - list all deploy sets +// RETURNS: +// - *ListResultWithMarker: the result of list deploy sets with marker +// - error: nil if success otherwise the specific error +func (c *Client) ListDeploySets(poolId string, marker *Marker) (*ListDeploySetResult, error) { + return c.ddcClient.ListDeploySets(poolId, marker) +} + +// ListPool - list current pools +// RETURNS: +// - *ListResultWithMarker: the result of list hosts with marker +// - error: nil if success otherwise the specific error +func (c *Client) ListPool(marker *Marker, productType string) (*ListPoolResult, error) { + if !isDDC(productType) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListPool(marker) +} + +// GetDeploySet - get details of the deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *DeploySet: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) GetDeploySet(poolId string, deploySetId string) (*DeploySet, error) { + return c.ddcClient.GetDeploySet(poolId, deploySetId) +} + +// DeleteDeploySet - delete a deploy set +// +// PARAMS: +// - poolId: the id of the pool +// - deploySetId: the id of the deploy set +// - clientToken: idempotent token, an ASCII string no longer than 64 bits +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDeploySet(poolId string, deploySetId string) error { + return c.ddcClient.DeleteDeploySet(poolId, deploySetId) +} + +// CreateBackup - create backup of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateBackup(instanceId string) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.CreateBackup(instanceId) +} + +// ModifyBackupPolicy - update backupPolicy +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Args: the specific rds Instance's BackupPolicy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyBackupPolicy(instanceId string, args *BackupPolicy) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.ModifyBackupPolicy(instanceId, args) +} + +// GetBackupDetail - get details of the instance'Backup +// +// PARAMS: +// - instanceId: the id of the instance +// - snapshotId: the id of the backup +// +// RETURNS: +// - *BackupDetailResult: the detail of the backup +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupDetail(instanceId string, snapshotId string) (*BackupDetailResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetBackupDetail(instanceId, snapshotId) +} + +// GetBinlogList - get backup list of the instance +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *BinlogListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogList(instanceId string, datetime string) (*BinlogListResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetBinlogList(instanceId, datetime) +} + +// GetBinlogDetail - get details of the instance'Binlog +// +// PARAMS: +// - instanceId: the id of the instance +// - binlog: the id of the binlog +// +// RETURNS: +// - *BinlogDetailResult: the detail of the binlog +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogDetail(instanceId string, binlog string) (*BinlogDetailResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetBinlogDetail(instanceId, binlog) +} + +// SwitchInstance - main standby switching of the instance +// +// PARAMS: +// - instanceId: the id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SwitchInstance(instanceId string, args *SwitchArgs) (*ProducedMaintainTaskResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.SwitchInstance(instanceId, args) +} + +// CreateDatabase - create a database with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateDatabase(instanceId string, args *CreateDatabaseArgs) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.CreateDatabase(instanceId, args) +} + +// DeleteDatabase - delete an database of a DDC instance +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - dbName: the specific database's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDatabase(instanceId, dbName string) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.DeleteDatabase(instanceId, dbName) +} + +// UpdateDatabaseRemark - update a database remark with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - dbName: the specific accountName +// - args: the arguments to update a database remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDatabaseRemark(instanceId string, dbName string, args *UpdateDatabaseRemarkArgs) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.UpdateDatabaseRemark(instanceId, dbName, args) +} + +// GetDatabase - get an database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - dbName: the specific database's name +// +// RETURNS: +// - *Database: the database's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetDatabase(instanceId, dbName string) (*Database, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetDatabase(instanceId, dbName) +} + +// ListDatabase - list all database of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *ListDatabaseResult: the result of list all database, contains all databases' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListDatabase(instanceId string) (*ListDatabaseResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListDatabase(instanceId) +} + +// GetTableAmount - query amount of tables +// +// PARAMS: +// - args: the specific ddc instanceId, dbName and search pattern +// +// RETURNS: +// - *TableAmountResult: the size of the table that meets the criteria +// - error: nil if success otherwise the specific error +func (c *Client) GetTableAmount(args *GetTableAmountArgs) (*TableAmountResult, error) { + return c.ddcClient.GetTableAmount(args) +} + +// GetDatabaseDiskUsage - get the disk footprint and the remaining space for database +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *ListDatabaseResult: the disk footprint and the remaining space for database +// - error: nil if success otherwise the specific error +func (c *Client) GetDatabaseDiskUsage(instanceId, dbName string) (*DatabaseDiskUsageResult, error) { + return c.ddcClient.GetDatabaseDiskUsage(instanceId, dbName) +} + +// GetRecoverableDateTime - get a list of recoverable times +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// +// RETURNS: +// - *GetRecoverableDateTimeResult: the result of list all recoverable datetimes +// - error: nil if success otherwise the specific error +func (c *Client) GetRecoverableDateTime(instanceId string) (*GetRecoverableDateTimeResult, error) { + return c.ddcClient.GetRecoverableDateTime(instanceId) +} + +// RecoverToSourceInstanceByDatetime - recover database or tables for the specific instance by a datetime +// +// PARAMS: +// - instanceId: the specific ddc Instance's ID +// - args: recover instance args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoverToSourceInstanceByDatetime(instanceId string, args *RecoverInstanceArgs) (*MaintainTaskIdResult, error) { + return c.ddcClient.RecoverToSourceInstanceByDatetime(instanceId, args) +} + +// UpdateAccountPassword - update a account password with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPassword(instanceId string, accountName string, args *UpdateAccountPasswordArgs) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.UpdateAccountPassword(instanceId, accountName, args) +} + +// UpdateAccountDesc - update a account desc with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account remark +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountDesc(instanceId string, accountName string, args *UpdateAccountDescArgs) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.UpdateAccountDesc(instanceId, accountName, args) +} + +// UpdateAccountPrivileges - update a account privileges with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific accountName +// - args: the arguments to update a account privileges +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPrivileges(instanceId string, accountName string, args *UpdateAccountPrivilegesArgs) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.UpdateAccountPrivileges(instanceId, accountName, args) +} + +// ListRoGroup - list all roGroups of a DDC instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListRoGroupResult: All roGroups of the current instance +// - error: nil if success otherwise the specific error +func (c *Client) ListRoGroup(instanceId string) (*ListRoGroupResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListRoGroup(instanceId) +} + +// ListVpc - list all Vpc +// +// PARAMS: +// RETURNS: +// - *ListVpc: All vpc of +// - error: nil if success otherwise the specific error +func (c *Client) ListVpc(productType string) (*[]VpcVo, error) { + if !isDDC(productType) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListVpc() +} + +// autoRenew - create autoRenew +// +// PARAMS: +// - Args: *autoRenewArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AutoRenew(args *AutoRenewArgs, productType string) error { + if isDDC(productType) { + return DDCNotSupportError() + } + // 实例列表不能为空 + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + rdsArgs := &rds.AutoRenewArgs{} + err := ddc_util.SimpleCopyProperties(rdsArgs, args) + if err != nil { + return err + } + return c.rdsClient.AutoRenew(rdsArgs) +} + +// GetMaintenTime - get details of the maintenTime +// +// PARAMS: +// - poolId: the id of the pool +// - cli: the client agent which can perform sending request +// - deploySetId: the id of the deploy set +// +// RETURNS: +// - *DeploySet: the detail of the deploy set +// - error: nil if success otherwise the specific error +func (c *Client) GetMaintainTime(instanceId string) (*MaintainTime, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetMaintainTime(instanceId) +} + +// UpdateMaintenTime - update UpdateMaintenTime of instance +// +// PARAMS: +// - body: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateMaintainTime(instanceId string, args *MaintainTime) error { + if args == nil { + return fmt.Errorf("unset args") + } + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.UpdateMaintainTime(instanceId, args) +} + +// ListRecycleInstances - list all instances in recycler with marker +// +// PARAMS: +// - marker: marker page +// +// RETURNS: +// - *RecyclerInstanceList: the result of instances in recycler +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycleInstances(marker *Marker, productType string) (*RecyclerInstanceList, error) { + if !isDDC(productType) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListRecycleInstances(marker) +} + +// RecoverRecyclerInstances - batch recover instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to recover +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoverRecyclerInstances(instanceIds []string) (*OrderIdResponse, error) { + if instanceIds == nil || len(instanceIds) < 1 { + return nil, fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return nil, fmt.Errorf("the instanceIds length max value is 10") + } + + for _, id := range instanceIds { + if !isDDCId(id) { + return nil, RDSNotSupportError() + } + } + return c.ddcClient.RecoverRecyclerInstances(instanceIds) +} + +// DeleteRecyclerInstances - batch delete instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + for _, id := range instanceIds { + if !isDDCId(id) { + return RDSNotSupportError() + } + } + return c.ddcClient.DeleteRecyclerInstances(instanceIds) +} + +// ListSecurityGroupByVpcId - list security groups by vpc id +// +// PARAMS: +// - vpcId: id of vpc +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByVpcId(vpcId string) (*[]SecurityGroup, error) { + return c.ddcClient.ListSecurityGroupByVpcId(vpcId) +} + +// ListSecurityGroupByInstanceId - list security groups by instance id +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *ListSecurityGroupResult: list secrity groups result of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByInstanceId(instanceId string) (*ListSecurityGroupResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListSecurityGroupByInstanceId(instanceId) +} + +// BindSecurityGroups - bind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + for _, id := range args.InstanceIds { + if !isDDCId(id) { + return RDSNotSupportError() + } + } + return c.ddcClient.BindSecurityGroups(args) +} + +// UnBindSecurityGroups - unbind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + for _, id := range args.InstanceIds { + if !isDDCId(id) { + return RDSNotSupportError() + } + } + return c.ddcClient.UnBindSecurityGroups(args) +} + +// ReplaceSecurityGroups - replace SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReplaceSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + for _, id := range args.InstanceIds { + if !isDDCId(id) { + return RDSNotSupportError() + } + } + return c.ddcClient.ReplaceSecurityGroups(args) +} + +// ListLogByInstanceId - list error or slow logs of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *[]Log:logs of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListLogByInstanceId(instanceId string, args *ListLogArgs) (*[]Log, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.ListLogByInstanceId(instanceId, args) +} + +// GetLogById - list log's detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Log:log's detail of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetLogById(instanceId, logId string, args *GetLogArgs) (*LogDetail, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetLogById(instanceId, logId, args) +} + +// LazyDropCreateHardLink - create a hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) LazyDropCreateHardLink(instanceId, dbName, tableName string) error { + if !isDDCId(instanceId) { + return RDSNotSupportError() + } + return c.ddcClient.LazyDropCreateHardLink(instanceId, dbName, tableName) +} + +// LazyDropDeleteHardLink - delete the hard link for specified large table +// +// PARAMS: +// - instanceId: id of instance +// - dbName: name of database +// - tableName: name of table +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) LazyDropDeleteHardLink(instanceId, dbName, tableName string) (*MaintainTaskIdResult, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.LazyDropDeleteHardLink(instanceId, dbName, tableName) +} + +// GetMachineInfo - get machine detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *MachineInfo:info of machine resource +// - error: nil if success otherwise the specific error +func (c *Client) GetMachineInfo(instanceId string) (*MachineInfo, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetMachineInfo(instanceId) +} + +// GetDisk - get disk detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Disk:disk of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetDisk(instanceId string) (*Disk, error) { + if !isDDCId(instanceId) { + return nil, RDSNotSupportError() + } + return c.ddcClient.GetDisk(instanceId) +} + +// GetResidual - get residual of pool +// +// PARAMS: +// - poolId: id of pool +// - zoneName: the zone name +// +// RETURNS: +// - *GetResidualResult:residual of pool +// - error: nil if success otherwise the specific error +func (c *Client) GetResidual(poolId string) (*GetResidualResult, error) { + return c.ddcClient.GetResidual(poolId) +} + +// GetFlavorCapacity - get flavor capacity of pool +// +// PARAMS: +// - poolId: id of pool +// - args: request params +// +// RETURNS: +// - *GetResidualResult:get flavor capacity of pool +// - error: nil if success otherwise the specific error +func (c *Client) GetFlavorCapacity(poolId string, args *GetFlavorCapacityArgs) (*GetFlavorCapacityResult, error) { + return c.ddcClient.GetFlavorCapacity(poolId, args) +} + +// KillSession - start kill session tasks +// +// PARAMS: +// - instanceId: id of the instance +// - args: instance role and sessionIds +// +// RETURNS: +// - *KillSessionResult: the response of kill session task id +// - error: nil if success otherwise the specific error +func (c *Client) KillSession(instanceId string, args *KillSessionArgs) (*KillSessionResult, error) { + return c.ddcClient.KillSession(instanceId, args) +} + +// GetKillSessionTask - get kill session tasks by taskId +// +// PARAMS: +// - instanceId: the specific instanceId of ddc +// - taskId: kill session returned id +// +// RETURNS: +// - *GetKillSessionTaskResult: the response of kill session task +// - error: nil if success otherwise the specific error +func (c *Client) GetKillSessionTask(instanceId string, taskId int) (*GetKillSessionTaskResult, error) { + return c.ddcClient.GetKillSessionTask(instanceId, taskId) +} + +// GetMaintainTaskList - get maintain tasks by taskId +// +// PARAMS: +// RETURNS: +// - *ListMaintainTaskResult: the response of maintain tasks +// - error: nil if success otherwise the specific error +func (c *Client) GetMaintainTaskList(args *GetMaintainTaskListArgs) (*ListMaintainTaskResult, error) { + return c.ddcClient.GetMaintainTaskList(args) +} + +// GetTaskDetail - get maintain task detail by taskId +// +// PARAMS: +// RETURNS: +// - *MaintainTaskDetailList: the response of maintain task detail +// - error: nil if success otherwise the specific error +func (c *Client) GetMaintainTaskDetail(taskIds string) (*MaintainTaskDetailList, error) { + return c.ddcClient.GetMaintainTaskDetail(taskIds) +} + +// ExecuteMaintainTaskImmediately - execute maintain task immediately +// +// PARAMS: +// - taskId: id of the task +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ExecuteMaintainTaskImmediately(taskId string) error { + return c.ddcClient.ExecuteMaintainTaskImmediately(taskId) +} + +// CancelMaintainTask - cancel maintain task +// +// PARAMS: +// - taskId: id of the task +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelMaintainTask(taskId string) error { + return c.ddcClient.CancelMaintainTask(taskId) +} + +// GetAccessLog - get access logs's download info by date +// +// PARAMS: +// RETURNS: +// - *AccessLog: the access logs's download info +// - error: nil if success otherwise the specific error +func (c *Client) GetAccessLog(date string) (*AccessLog, error) { + return c.ddcClient.GetAccessLog(date) +} + +// GetErrorLogs - get error logs +// +// PARAMS: +// RETURNS: +// - *ErrorLogsResponse: the error logs +// - error: nil if success otherwise the specific error +func (c *Client) GetErrorLogs(args *GetErrorLogsArgs) (*ErrorLogsResponse, error) { + return c.ddcClient.GetErrorLogs(args) +} + +// GetSlowLogs - get slow logs +// +// PARAMS: +// RETURNS: +// - *SlowLogsResponse: the slow logs +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowLogs(args *GetSlowLogsArgs) (*SlowLogsResponse, error) { + return c.ddcClient.GetSlowLogs(args) +} + +// GetInstanceBackupStatus - get instance backup status and backup start time +// +// PARAMS: +// RETURNS: +// - *GetBackupStatusResponse: the response of backup status +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceBackupStatus(instanceId string) (*GetBackupStatusResponse, error) { + return c.ddcClient.GetInstanceBackupStatus(instanceId) +} + +// InstanceVersionRollBack - rollback instance version from 5.7 to 5.6 +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to set WaitSwitch +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceVersionRollBack(instanceId string, args *InstanceVersionRollBackArg) (*MaintainTaskIdResult, error) { + return c.ddcClient.InstanceVersionRollBack(instanceId, args) +} + +// InstanceVersionUpgrade - upgrade instance version from 5.6 to 5.7 +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to set IsUpgradeNow +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceVersionUpgrade(instanceId string, args *InstanceVersionUpgradeArg) (*MaintainTaskIdResult, error) { + return c.ddcClient.InstanceVersionUpgrade(instanceId, args) +} + +// GetInstanceSyncDelay - get readonly instance syncDelay and syncStatus. +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *InstanceSyncDelayResponse: the response of syncDelay +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceSyncDelay(instanceId string) (*InstanceSyncDelayResponse, error) { + return c.ddcClient.GetInstanceSyncDelay(instanceId) +} + +// InstanceSyncDelayReplication - start or stop readonly instance syncDelay. +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *InstanceSyncDelayReplicationResponse: the response of success +// - error: nil if success otherwise the specific error +func (c *Client) InstanceSyncDelayReplication(instanceId string, args *InstanceSyncDelayReplicationArg) (*InstanceSyncDelayReplicationResponse, error) { + return c.ddcClient.InstanceSyncDelayReplication(instanceId, args) +} + +// SnapshotAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *Client) SnapshotAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + return c.ddcClient.SnapshotAccessDetail(args) +} + +// BinlogAccessDetail - get snapshot access detail +// +// PARAMS: +// - args: the arguments to get snapshot access detail +// +// RETURNS: +// - *AccessDetail +// - error: nil if success otherwise the specific error +func (c *Client) BinlogAccessDetail(args *AccessDetailArgs) (*BackupAccessDetail, error) { + + return c.ddcClient.BinlogAccessDetail(args) +} diff --git a/bce-sdk-go/services/ddc/v2/model.go b/bce-sdk-go/services/ddc/v2/model.go new file mode 100644 index 0000000..39c4f87 --- /dev/null +++ b/bce-sdk-go/services/ddc/v2/model.go @@ -0,0 +1,1237 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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 ddcrds + +const ( + STANDARD string = "Standard" + SINGLETON string = "Singleton" +) + +type Integer *int + +type TagModel struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} + +type CreateInstanceArgs struct { + ClientToken string `json:"-"` + InstanceType string `json:"instanceType,omitempty"` + Number int `json:"number,omitempty"` + Instance CreateInstance `json:"instance,omitempty"` +} + +type CreateRdsArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + Engine string `json:"engine,omitempty"` + EngineVersion string `json:"engineVersion,omitempty"` + Category string `json:"category,omitempty"` + CpuCount int `json:"cpuCount,omitempty"` + MemoryCapacity float64 `json:"memoryCapacity,omitempty"` + VolumeCapacity int `json:"volumeCapacity,omitempty"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []TagModel `json:"tags,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + DeployId string `json:"deployId,omitempty"` + PoolId string `json:"poolId"` + SyncMode string `json:"syncMode"` +} + +type CreateReadReplicaArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing,omitempty"` + PurchaseCount int `json:"purchaseCount,omitempty"` + SourceInstanceId string `json:"sourceInstanceId"` + InstanceName string `json:"instanceName,omitempty"` + CpuCount int `json:"cpuCount,omitempty"` + MemoryCapacity float64 `json:"memoryCapacity,omitempty"` + VolumeCapacity int `json:"volumeCapacity,omitempty"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []TagModel `json:"tags,omitempty"` + DeployId string `json:"deployId,omitempty"` + PoolId string `json:"poolId,omitempty"` + RoGroupId string `json:"roGroupId,omitempty"` + EnableDelayOff string `json:"enableDelayOff,omitempty"` + DelayThreshold string `json:"delayThreshold,omitempty"` + LeastInstanceAmount string `json:"leastInstanceAmount,omitempty"` + RoGroupWeight string `json:"roGroupWeight,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` +} + +type Instance struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + Category string `json:"category"` + InstanceStatus string `json:"instanceStatus"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"allocatedMemoryInGB"` + VolumeCapacity int `json:"allocatedStorageInGB"` + NodeAmount int `json:"nodeAmount"` + UsedStorage float64 `json:"usedStorageInGB"` + PublicAccessStatus string `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Endpoint Endpoint `json:"endpoint"` + SyncMode string `json:"syncMode"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + Region string `json:"region"` + InstanceType string `json:"type"` + SourceInstanceId string `json:"sourceInstanceId"` + SourceRegion string `json:"sourceRegion"` + ZoneNames []string `json:"zoneNames"` + VpcId string `json:"vpcId"` + Subnets []Subnet `json:"subnets"` + Topology Topology `json:"topology"` + PaymentTiming string `json:"paymentTiming"` + + PubliclyAccessible bool `json:"publiclyAccessible"` + RoGroupList []RoGroup `json:"roGroupList"` + NodeMaster NodeInfo `json:"nodeMaster"` + NodeSlave NodeInfo `json:"nodeSlave"` + NodeReadReplica NodeInfo `json:"nodeReadReplica"` + DeployId string `json:"deployId"` + LongBBCId string `json:"longBBCId"` + HostName string `json:"hostname,omitempty"` + InstanceTopoForReadonly InstanceTopoForReadonly `json:"instanceTopoForReadonly,omitempty"` + AutoRenewRule *AutoRenewRule `json:"autoRenewRule,omitempty"` +} + +type AutoRenewRule struct { + RenewTime int `json:"renewTime"` + RenewTimeUnit string `json:"renewTimeUnit"` +} + +func (instance *Instance) ProductType() string { + if isDDCId(instance.InstanceId) { + return "ddc" + } + return "rds" +} + +type SubnetMap struct { + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation Reservation `json:"reservation,omitempty"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength,omitempty"` + ReservationTimeUnit string `json:"reservationTimeUnit,omitempty"` +} + +type CreateResult struct { + OrderId string `json:"orderId"` + InstanceIds []string `json:"instanceIds"` +} + +type InstanceModelResult struct { + Instance InstanceModel `json:"instance"` +} + +type CreateInstance struct { + InstanceId string `json:"instanceId,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + SourceInstanceId string `json:"sourceInstanceId,omitempty"` + Engine string `json:"engine,omitempty"` + EngineVersion string `json:"engineVersion,omitempty"` + CpuCount int `json:"cpuCount,omitempty"` + AllocatedMemoryInGB int `json:"allocatedMemoryInGB,omitempty"` + AllocatedStorageInGB int `json:"allocatedStorageInGB,omitempty"` + AZone string `json:"azone,omitempty"` + VpcId string `json:"vpcId,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + DiskIoType string `json:"diskIoType,omitempty"` + DeployId string `json:"deployId,omitempty"` + PoolId string `json:"poolId,omitempty"` + RoGroupId string `json:"roGroupId,omitempty"` + IsBalanceRoLoad Integer `json:"isBalanceRoLoad,omitempty"` + EnableDelayOff Integer `json:"enableDelayOff,omitempty"` + DelayThreshold Integer `json:"delayThreshold,omitempty"` + LeastInstanceAmount Integer `json:"leastInstanceAmount,omitempty"` + RoGroupWeight Integer `json:"roGroupWeight,omitempty"` + IsDirectPay bool `json:"IsDirectPay,omitempty"` + Billing Billing `json:"billing,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Category string `json:"category,omitempty"` + Tags []TagModel `json:"tags,omitempty"` + SyncMode string `json:"syncMode,omitempty"` +} + +type Pool struct { + CPUQuotaTotal int `json:"cpuQuotaTotal"` + CPUQuotaUsed int `json:"cpuQuotaUsed"` + CreateTime string `json:"createTime"` + DeployMethod string `json:"deployMethod"` + DiskQuotaTotal int `json:"diskQuotaTotal"` + DiskQuotaUsed int `json:"diskQuotaUsed"` + Engine string `json:"engine"` + Hosts []Host `json:"hosts"` + MaxMemoryUsedRatio string `json:"maxMemoryUsedRatio"` + MemoryQuotaTotal int `json:"memoryQuotaTotal"` + MemoryQuotaUsed int `json:"memoryQuotaUsed"` + PoolID string `json:"poolId"` + PoolName string `json:"poolName"` + VpcID string `json:"vpcId"` +} + +type Host struct { + Containers []Container `json:"containers"` + Flavor Flavor `json:"flavor"` + CPUQuotaTotal int `json:"cpuQuotaTotal"` + CPUQuotaUsed int `json:"cpuQuotaUsed"` + DeploymentStatus string `json:"deploymentStatus"` + DiskQuotaTotal int `json:"diskQuotaTotal"` + DiskQuotaUsed int `json:"diskQuotaUsed"` + HostID string `json:"hostId"` + HostName string `json:"hostName"` + ImageType string `json:"imageType"` + MemoryQuotaTotal int64 `json:"memoryQuotaTotal"` + MemoryQuotaUsed int64 `json:"memoryQuotaUsed"` + PnetIP string `json:"pnetIp"` + Role string `json:"role"` + Status string `json:"status"` + SubnetID string `json:"subnetId"` + VnetIP string `json:"vnetIp"` + VpcID string `json:"vpcId"` + Zone string `json:"zone"` +} + +type OperateHostRequest struct { + Action string `json:"action"` +} + +type Flavor struct { + CPUCount int `json:"cpuCount"` + CPUType string `json:"cpuType"` + Disk int `json:"disk"` + FlavorID string `json:"flavorId"` + MemoryCapacityInGB int `json:"memoryCapacityInGB"` +} + +type Container struct { + ContainerID string `json:"containerId"` + DeployID string `json:"deployId"` + DeployName string `json:"deployName"` + Engine string `json:"engine"` + HostID string `json:"hostId"` + HostName string `json:"hostName"` + PoolName string `json:"poolName"` + Role string `json:"role"` + Zone string `json:"zone"` +} + +type DeploySet struct { + CreateTime string `json:"createTime"` + DeployID string `json:"deployId"` + DeployName string `json:"deployName"` + Instances []string `json:"instances"` + PoolID string `json:"poolId"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type CreateDeployRequest struct { + ClientToken string `json:"-"` + DeployName string `json:"deployName"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type CreateDeployResult struct { + DeployID string `json:"deployId"` +} + +type UpdateDeployRequest struct { + ClientToken string `json:"-"` + Strategy string `json:"strategy"` + CentralizeThreshold int `json:"centralizeThreshold"` +} + +type Marker struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListResultWithMarker struct { + IsTruncated bool `json:"isTruncated"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` +} + +type ListPoolResult struct { + ListResultWithMarker + Result []Pool `json:"result"` +} + +type ListHostResult struct { + ListResultWithMarker + Result []Host `json:"result"` +} + +type ListDeploySetResult struct { + ListResultWithMarker + Result []DeploySet `json:"result"` +} + +type InstanceModel struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + InstanceStatus string `json:"instanceStatus"` + CpuCount int `json:"cpuCount"` + AllocatedMemoryInGB float64 `json:"allocatedMemoryInGB"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + NodeAmount int `json:"nodeAmount"` + UsedStorageInGB float64 `json:"usedStorageInGB"` + PublicAccessStatus bool `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Endpoint Endpoint `json:"endpoint"` + SyncMode string `json:"syncMode"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + Region string `json:"region"` + InstanceType string `json:"instanceType"` + SourceInstanceId string `json:"sourceInstanceId"` + SourceRegion string `json:"sourceRegion"` + ZoneNames []string `json:"zoneNames"` + VpcId string `json:"vpcId"` + Subnets []Subnet `json:"subnets"` + NodeMaster NodeInfo `json:"nodeMaster"` + NodeSlave NodeInfo `json:"nodeSlave"` + NodeReadReplica NodeInfo `json:"nodeReadReplica"` + DeployId string `json:"deployId"` + Topology Topology `json:"topology"` + DiskType string `json:"diskType"` + Type string `json:"type"` + ApplicationType string `json:"applicationType"` + RoGroupList []RoGroup `json:"roGroupList"` + PaymentTiming string `json:"paymentTiming"` + Category string `json:"category"` + LongBBCId string `json:"longBBCId"` + InstanceTopoForReadonly InstanceTopoForReadonly `json:"instanceTopoForReadonly,omitempty"` + AutoRenewRule *AutoRenewRule `json:"autoRenewRule,omitempty"` +} +type TopoInstance struct { + InstanceID string `json:"instanceId,omitempty"` + SyncMode string `json:"syncMode,omitempty"` + MasterSlaveDelay int `json:"masterSlaveDelay,omitempty"` + Type string `json:"type,omitempty"` + Region string `json:"region,omitempty"` + RoGroupID string `json:"roGroupId,omitempty"` + ZoneName string `json:"zoneName,omitempty"` + InstanceStatus string `json:"instanceStatus,omitempty"` +} +type InstanceTopoForReadonly struct { + ReadReplica []TopoInstance `json:"readReplica,omitempty"` + Master []TopoInstance `json:"master,omitempty"` +} + +type SubnetVo struct { + Name string `json:"name"` + SubnetId string `json:"subnetId"` + Az string `json:"az"` + Cidr string `json:"cidr"` + ShortId string `json:"shortId"` +} + +type RoGroup struct { + RoGroupID string `json:"roGroupId"` + RoGroupName string `json:"roGroupName"` + VnetIP string `json:"vnetIp"` + IsBalanceRoLoad int `json:"isBalanceRoLoad"` + EnableDelayOff int `json:"enableDelayOff"` + DelayThreshold int `json:"delayThreshold"` + LeastInstanceAmount int `json:"leastInstanceAmount"` + ReplicaList []Replica `json:"replicaList"` +} + +type UpdateRoGroupArgs struct { + RoGroupName string `json:"roGroupName,omitempty"` + IsBalanceRoLoad string `json:"isBalanceRoLoad,omitempty"` + EnableDelayOff string `json:"enableDelayOff,omitempty"` + DelayThreshold string `json:"delayThreshold,omitempty"` + LeastInstanceAmount string `json:"leastInstanceAmount,omitempty"` + MasterDelay string `json:"masterDelay,omitempty"` +} + +type UpdateRoGroupRealArgs struct { + RoGroupName string `json:"roGroupName,omitempty"` + IsBalanceRoLoad Integer `json:"isBalanceRoLoad,omitempty"` + EnableDelayOff Integer `json:"enableDelayOff,omitempty"` + DelayThreshold Integer `json:"delayThreshold,omitempty"` + LeastInstanceAmount Integer `json:"leastInstanceAmount,omitempty"` + MasterDelay Integer `json:"masterDelay,omitempty"` +} + +type UpdateRoGroupWeightArgs struct { + RoGroupName string `json:"roGroupName,omitempty"` + EnableDelayOff string `json:"enableDelayOff,omitempty"` + DelayThreshold string `json:"delayThreshold,omitempty"` + LeastInstanceAmount string `json:"leastInstanceAmount,omitempty"` + IsBalanceRoLoad string `json:"isBalanceRoLoad,omitempty"` + MasterDelay string `json:"masterDelay,omitempty"` + ReplicaList []ReplicaWeight `json:"replicaList,omitempty"` +} + +type UpdateRoGroupWeightRealArgs struct { + RoGroupName string `json:"roGroupName,omitempty"` + EnableDelayOff Integer `json:"enableDelayOff,omitempty"` + DelayThreshold Integer `json:"delayThreshold,omitempty"` + LeastInstanceAmount Integer `json:"leastInstanceAmount,omitempty"` + IsBalanceRoLoad Integer `json:"isBalanceRoLoad,omitempty"` + MasterDelay Integer `json:"masterDelay,omitempty"` + ReplicaList []ReplicaWeight `json:"replicaList,omitempty"` +} + +type ReplicaWeight struct { + InstanceId string `json:"instanceId"` + Weight int `json:"weight"` +} + +type Replica struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Status string `json:"status"` + RoGroupWeight int `json:"roGroupWeight"` +} + +type NodeInfo struct { + Id string `json:"id"` + Azone string `json:"azone"` + SubnetId string `json:"subnetId"` + Cidr string `json:"cidr"` + Name string `json:"name"` + HostName string `json:"hostname"` +} + +type Subnet struct { + Name string `json:"name"` + LongId string `json:"subnetId"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + ShortId string `json:"shortId"` + VpcId string `json:"vpcId"` + VpcShortId string `json:"vpcShortId"` + Az string `json:"az"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} + +type Endpoint struct { + Address string `json:"address"` + Port int `json:"port"` + VnetIp string `json:"vnetIp"` + VnetIpBackup string `json:"vnetIpBackup"` + InetIp string `json:"inetIp"` +} + +type BackupPolicy struct { + BackupDays string `json:"backupDays"` + BackupTime string `json:"backupTime"` + Persistent bool `json:"persistent"` + ExpireInDaysStr string `json:"expireInDaysStr"` + FreeSpaceInGB int `json:"freeSpaceInGb"` + + ExpireInDaysInt int `json:"expireInDays"` +} + +type Topology struct { + Rdsproxy []string `json:"rdsproxy"` + Master []string `json:"master"` + ReadReplica []string `json:"readReplica"` +} + +type DeleteDdcArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type UpdateInstanceNameArgs struct { + InstanceName string `json:"instanceName"` +} + +type ListRdsResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Instances []Instance `json:"result"` +} + +type ListRdsArgs struct { + Marker string + MaxKeys int +} + +type ListPageArgs struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Filters []Filter `json:"filters"` +} +type Filter struct { + KeywordType string `json:"keywordType"` + Keyword string `json:"keyword"` +} + +type ListPageResult struct { + Page Page `json:"page"` +} + +type Page struct { + Result []Instance `json:"result"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type GetBackupListResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Backups []Snapshot `json:"snapshots"` + FreeSpaceInMB int64 `json:"freeSpaceInMB"` + UsedSpaceInMB int64 `json:"usedSpaceInMB"` +} + +type GetZoneListResult struct { + Zones []ZoneName `json:"zones"` +} + +type ZoneName struct { + ZoneNames []string `json:"apiZoneNames"` + ApiZoneNames []string `json:"zoneNames"` + Available bool `json:"bool"` + DefaultSubnetId string `json:"defaultSubnetId"` +} + +type ListSubnetsArgs struct { + VpcId string `json:"vpcId"` + ZoneName string `json:"zoneName"` +} + +type ListSubnetsResult struct { + Subnets []Subnet `json:"subnets"` +} + +type SecurityIpsRawResult struct { + SecurityIps []string `json:"ip"` +} + +type UpdateSecurityIpsArgs struct { + SecurityIps []string `json:"securityIps"` +} + +type ListParametersResult struct { + Etag string `json:"etag"` + Parameters []Parameter `json:"items"` +} + +type Parameter struct { + Name string `json:"name"` + DefaultValue string `json:"defaultValue"` + Value string `json:"value"` + PendingValue string `json:"pendingValue"` + Type string `json:"type"` + IsDynamic bool `json:"dynamic"` + ISModifiable bool `json:"modifiable"` + AllowedValues string `json:"allowedValues"` + Desc string `json:"desc"` + // 多加字段,兼容RDS + Dynamic string `json:"dynamicStr"` + Modifiable string `json:"modifiableStr"` +} + +type UpdateParameterArgs struct { + Parameters []KVParameter `json:"parameters"` + WaitSwitch int `json:"waitSwitch"` +} + +type KVParameter struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type Snapshot struct { + SnapshotId string `json:"snapshotId"` + SnapshotSizeInBytes string `json:"snapshotSizeInBytes"` + SnapshotType string `json:"snapshotType"` + SnapshotStatus string `json:"snapshotStatus"` + SnapshotStartTime string `json:"snapshotStartTime"` + SnapshotEndTime string `json:"snapshotEndTime"` +} + +type SnapshotModel struct { + SnapshotId string `json:"snapshotId"` + SnapshotSizeInBytes string `json:"snapshotSizeInBytes"` + SnapshotType string `json:"snapshotType"` + SnapshotStatus string `json:"snapshotStatus"` + SnapshotStartTime string `json:"snapshotStartTime"` + SnapshotEndTime string `json:"snapshotEndTime"` + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type BackupDetailResult struct { + Snapshot SnapshotModel `json:"snapshot"` +} + +type Binlog struct { + BinlogId string `json:"binlogId"` + BinlogSizeInBytes int64 `json:"binlogSizeInBytes"` + BinlogStatus string `json:"binlogStatus"` + BinlogStartTime string `json:"binlogStartTime"` + BinlogEndTime string `json:"binlogEndTime"` +} + +type BinlogModel struct { + BinlogId string `json:"binlogId"` + BinlogSizeInBytes int64 `json:"binlogSizeInBytes"` + BinlogStatus string `json:"binlogStatus"` + BinlogStartTime string `json:"binlogStartTime"` + BinlogEndTime string `json:"binlogEndTime"` + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type BinlogListResult struct { + Binlogs []Binlog `json:"binlogs"` +} + +type BinlogDetailResult struct { + Binlog BinlogModel `json:"binlog"` +} + +type AuthType string + +const ( + AuthType_ReadOnly AuthType = "readOnly" + AuthType_ReadWrite AuthType = "readWrite" +) + +type AccountPrivilege struct { + AccountName string `json:"accountName"` + AuthType AuthType `json:"authType"` +} + +type CreateDatabaseArgs struct { + ClientToken string `json:"-"` + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + Remark string `json:"remark"` +} + +type UpdateDatabaseRemarkArgs struct { + Remark string `json:"remark"` +} + +type Database struct { + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + DbStatus string `json:"dbStatus"` + Remark string `json:"remark"` + AccountPrivileges []AccountPrivilege `json:"accountPrivileges"` +} + +type DatabaseResult struct { + Database Database `json:"database"` +} + +type ListDatabaseResult struct { + Databases []Database `json:"databases"` +} + +// Account +type AccountType string + +const ( + AccountType_Super AccountType = "rdssuper" + AccountType_Common AccountType = "common" +) + +type CreateAccountArgs struct { + ClientToken string `json:"-"` + AccountName string `json:"accountName"` + Password string `json:"password"` + // 为了兼容 RDS 参数结构 + AccountType AccountType `json:"type"` + Desc string `json:"remark"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges,omitempty"` +} + +type DatabasePrivilege struct { + DbName string `json:"dbName"` + AuthType string `json:"authType"` +} + +type Account struct { + AccountName string `json:"accountName"` + Desc string `json:"remark"` + Status string `json:"accountStatus"` + AccountType string `json:"accountType"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` +} + +type AccountResult struct { + Account Account `json:"account"` +} + +type ListAccountResult struct { + Accounts []Account `json:"accounts"` +} + +type UpdateAccountPasswordArgs struct { + Password string `json:"password"` +} + +type UpdateAccountDescArgs struct { + Desc string `json:"remark"` +} + +type UpdateAccountPrivilegesArgs struct { + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` +} + +type ListRoGroupResult struct { + RoGroups []RoGroup `json:"roGroups"` +} + +type VpcVo struct { + VpcId string `json:"vpcId"` + ShortId string `json:"shortId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + Status int `json:"status"` + CreateTime string `json:"createTime"` + Description string `json:"description"` + DefaultVpc bool `json:"defaultVpc"` + Ipv6Cidr string `json:"ipv6Cidr"` + AuxiliaryCidr []string `json:"auxiliaryCidr"` + Relay bool `json:"relay"` +} +type GetBackupListArgs struct { + Marker string + MaxKeys int +} +type GetSecurityIpsResult struct { + Etag string `json:"etag"` + SecurityIps []string `json:"securityIps"` +} + +// struct for rds + +type CreateRdsProxyArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + SourceInstanceId string `json:"sourceInstanceId"` + InstanceName string `json:"instanceName,omitempty"` + NodeAmount int `json:"nodeAmount"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []TagModel `json:"tags,omitempty"` +} + +type ResizeRdsArgs struct { + ClientToken string `json:"-"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + NodeAmount int `json:"nodeAmount,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + IsResizeNow bool `json:"isResizeNow,omitempty"` +} + +type OrderIdResponse struct { + OrderId string `json:"orderId"` +} + +type ModifySyncModeArgs struct { + SyncMode string `json:"syncMode"` +} + +type ModifyEndpointArgs struct { + Address string `json:"address"` +} + +type ModifyPublicAccessArgs struct { + PublicAccess bool `json:"publicAccess"` +} + +type AutoRenewArgs struct { + InstanceIds []string `json:"instanceIds"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` +} + +type RebootArgs struct { + IsRebootNow bool `json:"isRebootNow"` +} + +type SwitchArgs struct { + IsSwitchNow bool `json:"isSwitchNow"` +} + +type MaintainWindow struct { + MaintainTime MaintainTime `json:"maintentime"` +} + +type MaintainTime struct { + Period string `json:"period"` + StartTime string `json:"startTime"` + Duration int `json:"duration"` +} + +type RecycleInstance struct { + EngineVersion string `json:"engineVersion"` + VolumeCapacity int `json:"volumeCapacity"` + ApplicationType string `json:"applicationType"` + InstanceName string `json:"instanceName"` + PublicAccessStatus string `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceType string `json:"instanceType"` + Type string `json:"type"` + InstanceStatus string `json:"instanceStatus"` + MemoryCapacity float64 `json:"memoryCapacity"` + InstanceId string `json:"instanceId"` + Engine string `json:"engine"` + VpcId string `json:"vpcId"` + PubliclyAccessible bool `json:"publiclyAccessible"` + InstanceExpireTime string `json:"instanceExpireTime"` + DiskType string `json:"diskType"` + Region string `json:"region"` + CpuCount int `json:"cpuCount"` + UsedStorage float64 `json:"usedStorage"` + LongBBCId string `json:"longBBCId"` +} + +type RecyclerInstanceList struct { + ListResultWithMarker + Result []RecycleInstance `json:"result"` +} + +type BatchInstanceIds struct { + InstanceIds string `json:"instanceIds"` +} + +type SecurityGroup struct { + Name string `json:"name"` + SecurityGroupID string `json:"securityGroupId"` + Description string `json:"description"` + TenantID string `json:"tenantId"` + AssociateNum int `json:"associateNum"` + VpcID string `json:"vpcId"` + VpcShortID string `json:"vpcShortId"` + VpcName string `json:"vpcName"` + CreatedTime string `json:"createdTime"` + Version int `json:"version"` + DefaultSecurityGroup int `json:"defaultSecurityGroup"` +} + +type SecurityGroupArgs struct { + InstanceIds []string `json:"instanceIds"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type ListSecurityGroupResult struct { + Groups []SecurityGroupDetail `json:"groups"` +} + +type SecurityGroupRule struct { + PortRange string `json:"portRange"` + Protocol string `json:"protocol"` + RemoteGroupID string `json:"remoteGroupId"` + RemoteIP string `json:"remoteIP"` + Ethertype string `json:"ethertype"` + TenantID string `json:"tenantId"` + Name string `json:"name"` + ID string `json:"id"` + SecurityGroupRuleID string `json:"securityGroupRuleId"` + Direction string `json:"direction"` +} + +type SecurityGroupDetail struct { + SecurityGroupName string `json:"securityGroupName"` + SecurityGroupID string `json:"securityGroupId"` + SecurityGroupRemark string `json:"securityGroupRemark"` + Inbound []SecurityGroupRule `json:"inbound"` + Outbound []SecurityGroupRule `json:"outbound"` + VpcName string `json:"vpcName"` + VpcID string `json:"vpcId"` + ProjectID string `json:"projectId"` +} + +type ListLogArgs struct { + LogType string `json:"logType"` + Datetime string `json:"datetime"` +} + +type Log struct { + LogStartTime string `json:"logStartTime"` + LogEndTime string `json:"logEndTime"` + LogID string `json:"logId"` + LogSizeInBytes int `json:"logSizeInBytes"` +} + +type LogDetail struct { + Log + DownloadURL string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type GetLogArgs struct { + ValidSeconds int `json:"downloadValidTimeInSec"` +} + +type CreateTableHardLinkArgs struct { + TableName string `json:"tableName"` +} +type Disk struct { + Response struct { + Items []struct { + DiskPartition string `json:"DiskPartition"` + InstanceID string `json:"InstanceId"` + InstanceRole string `json:"InstanceRole"` + PhysicalIP string `json:"PhysicalIp"` + ReportTime string `json:"ReportTime"` + TotalSize int `json:"TotalSize"` + UsedSize int `json:"UsedSize"` + } `json:"Items"` + } `json:"Response"` +} + +type MachineInfo struct { + Response struct { + Items []struct { + InstanceID string `json:"instanceId"` + Role string `json:"role"` + CPUInCore int `json:"cpuInCore"` + FreeCPUInCore int `json:"freeCpuInCore"` + MemSizeInMB int `json:"memSizeInMB"` + FreeMemSizeInMB int `json:"freeMemSizeInMB"` + SizeInGB []struct { + FreeSizeInGB int `json:"freeSizeInGB"` + Label map[string]string `json:"label"` + Path string `json:"path"` + SizeInGB int `json:"sizeInGB"` + } `json:"sizeInGB"` + } `json:"Items"` + } `json:"Response"` +} + +type GetResidualResult struct { + Residual map[string]ResidualOfZone `json:"residual"` +} +type Slave struct { + DiskInGb float64 `json:"diskInGb"` + MemoryInGb float64 `json:"memoryInGb"` + CPUInCore int `json:"cpuInCore"` +} +type Single struct { + DiskInGb float64 `json:"diskInGb"` + MemoryInGb float64 `json:"memoryInGb"` + CPUInCore int `json:"cpuInCore"` +} +type HA struct { + DiskInGb float64 `json:"diskInGb"` + MemoryInGb float64 `json:"memoryInGb"` + CPUInCore int `json:"cpuInCore"` +} +type ResidualOfZone struct { + Slave Slave `json:"slave"` + Single Single `json:"single"` + HA HA `json:"HA"` +} +type GetFlavorCapacityArgs struct { + CpuInCore int `json:"CpuInCore,omitempty"` + MemoryInGb int64 `json:"memoryInGb,omitempty"` + DiskInGb int64 `json:"diskInGb,omitempty"` + Affinity int64 `json:"affinity,omitempty"` +} + +func NewDefaultGetFlavorCapacityArgs(cpuInCore int, memoryInGb, diskInGb int64) *GetFlavorCapacityArgs { + return &GetFlavorCapacityArgs{ + CpuInCore: cpuInCore, + MemoryInGb: memoryInGb, + DiskInGb: diskInGb, + Affinity: 10, + } +} + +type GetFlavorCapacityResult struct { + Capacity map[string]CapacityOfZone `json:"capacity"` +} +type CapacityOfZone struct { + Single int `json:"single"` + Slave int `json:"slave"` + HA int `json:"HA"` +} + +type GetTableAmountArgs struct { + InstanceId string `json:"instanceId"` + DbName string `json:"dbName"` + Pattern string `json:"pattern"` +} + +type TableAmountResult struct { + Tables map[string]int `json:"tables"` + TotalAmount int `json:"totalAmount"` + ReturnAmount int `json:"returnAmount"` +} + +type DatabaseDiskUsageResult struct { + Databases map[string]int `json:"databases"` + RestDisk int64 `json:"restDisk"` + UsedDisk int64 `json:"usedDisk"` +} + +type GetRecoverableDateTimeResult struct { + RecoverableDateTimes []RecoverableDateTime `json:"recoverableDateTimes"` +} +type RecoverableDateTime struct { + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` +} + +type RecoverInstanceArgs struct { + Datetime string `json:"datetime"` + RecoverData []RecoverData `json:"recoverData"` +} +type RecoverTable struct { + NewTableName string `json:"newTableName"` + TableName string `json:"tableName"` +} +type RecoverData struct { + RecoverTables []RecoverTable `json:"recoverTables"` + RestoreMode string `json:"restoreMode"` + DbName string `json:"dbName"` + NewDbName string `json:"newDbName"` +} + +type KillSessionArgs struct { + Role string `json:"role"` + SessionIds []int `json:"sessionIds"` +} +type KillSessionResult struct { + TaskID int `json:"taskId"` +} +type GetKillSessionTaskResult struct { + Tasks []KillSessionTask `json:"tasks"` +} +type KillSessionTask struct { + SessionID int `json:"sessionId"` + Status string `json:"status"` +} + +type MaintainTask struct { + TaskID string `json:"taskId"` + TaskName string `json:"taskName"` + TaskStatus string `json:"taskStatus"` + InstanceID string `json:"instanceId"` + InstanceName string `json:"instanceName"` + SupportedOperate []string `json:"supportedOperate"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Region string `json:"region"` +} + +type ProducedMaintainTaskResult struct { + Success bool `json:"success"` + Result MaintainTaskIdResult `json:"result"` +} + +type MaintainTaskIdResult struct { + TaskID string `json:"taskId"` +} + +type ListMaintainTaskResult struct { + ListResultWithMarker + Result []MaintainTask `json:"result"` +} + +type GetMaintainTaskListArgs struct { + Marker + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type MaintainTaskDetailList struct { + Tasks []MaintainTaskDetail `json:"tasks"` +} +type MaintainTaskDetail struct { + TaskID int `json:"taskId"` + InstanceID string `json:"instanceId"` + InstanceName string `json:"instanceName"` + AppID string `json:"appId"` + TaskName string `json:"taskName"` + TaskType string `json:"taskType"` + CreateTime string `json:"createTime"` + PickupTime string `json:"pickupTime"` + ErrNu int `json:"errNu"` + TaskStatus string `json:"taskStatus"` + TaskSpecialAction string `json:"taskSpecialAction"` + TaskSpecialActionTime string `json:"taskSpecialActionTime"` +} + +type AccessLog struct { + Downloadurl struct { + Bbc string `json:"bbc"` + Bos string `json:"bos"` + Mysql string `json:"mysql"` + } `json:"downloadurl"` +} + +type GetErrorLogsArgs struct { + InstanceId string `json:"instanceId"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Role string `json:"role"` + KeyWord string `json:"keyWord,omitempty"` +} + +type ErrorLog struct { + InstanceId string `json:"instanceId"` + LogLevel string `json:"logLevel"` + LogText string `json:"logText"` + ExecuteTime string `json:"executeTime"` +} + +type ErrorLogsResponse struct { + Count int `json:"count"` + ErrorLogs []ErrorLog `json:"errorLogs"` +} + +type GetSlowLogsArgs struct { + InstanceId string `json:"instanceId"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Role string `json:"role"` + DbName []string `json:"dbName,omitempty"` + UserName []string `json:"userName,omitempty"` + HostIp []string `json:"hostIp,omitempty"` + Sql string `json:"sql,omitempty"` +} + +type SlowLog struct { + InstanceId string `json:"instanceId"` + UserName string `json:"userName"` + DbName string `json:"dbName"` + HostIp string `json:"hostIp"` + QueryTime float64 `json:"queryTime"` + LockTime float64 `json:"lockTime"` + RowsExamined int `json:"rowsExamined"` + RowsSent int `json:"rowsSent"` + Sql string `json:"sql"` + ExecuteTime string `json:"executeTime"` +} + +type SlowLogsResponse struct { + Count int `json:"count"` + SlowLogs []SlowLog `json:"slowLogs"` +} + +type GetBackupStatusResponse struct { + IsBackuping bool `json:"isBackuping"` + SnapshotStartTime string `json:"snapshotStartTime"` +} + +type InstanceVersionRollBackArg struct { + WaitSwitch bool `json:"waitSwitch"` +} + +type InstanceVersionUpgradeArg struct { + IsUpgradeNow bool `json:"isUpgradeNow"` +} + +type InstanceSyncDelayResponse struct { + Success bool `json:"success"` + Result InstanceSyncDelay `json:"result"` +} + +type InstanceSyncDelay struct { + SyncDelay int `json:"sync_delay"` + SyncStatus string `json:"sync_status"` +} + +type InstanceSyncDelayReplicationArg struct { + Action string `json:"action"` +} + +type InstanceSyncDelayReplicationResponse struct { + Success bool `json:"success"` +} + +type AccessDetailItem struct { + BackupID string `json:"backupID"` + AccessDateTime string `json:"accessDateTime"` + AccessResult string `json:"accessResult"` + AccessSrcAddressType string `json:"accessSrcAddressType"` + AvailableZone string `json:"availableZone"` + AccessSrcAddress string `json:"accessSrcAddress"` + AccessOperationType string `json:"accessOperationType"` + StorageType string `json:"storageType"` + StorageAddress string `json:"storageAddress"` + Region string `json:"region"` + BackupName string `json:"backupName"` + AccessSrcAgent string `json:"accessSrcAgent"` + StorageID string `json:"storageID"` +} + +type Pagination struct { + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Marker string `json:"marker"` + TotalKeys int `json:"totalKeys"` +} +type BackupAccessDetail struct { + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` + DataType string `json:"dataType"` + BackupAccessDetails []AccessDetailItem `json:"backupAccessDetails"` + Pagination Pagination `json:"pagination"` +} + +type AccessDetailArgs struct { + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + DataType string `json:"dataType,omitempty"` +} diff --git a/bce-sdk-go/services/dns/client.go b/bce-sdk-go/services/dns/client.go new file mode 100644 index 0000000..b110a0f --- /dev/null +++ b/bce-sdk-go/services/dns/client.go @@ -0,0 +1,287 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 dns + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "http://dns.baidubce.com" + DEFAULT_MAX_PARALLEL = 10 + MULTIPART_ALIGN = 1 << 20 // 1MB + MIN_MULTIPART_SIZE = 1 << 20 // 1MB + DEFAULT_MULTIPART_SIZE = 12 * (1 << 20) // 12MB + MAX_PART_NUMBER = 10000 +) + +// Client of bcd service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient + + // Fileds that used in parallel operation for BOS service + MaxParallel int64 + MultipartSize int64 +} + +// NewClient make the bcd service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer), + DEFAULT_MAX_PARALLEL, DEFAULT_MULTIPART_SIZE} + return client, nil +} + +// AddLineGroup - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) AddLineGroup(body *AddLineGroupRequest, clientToken string) error { + return AddLineGroup(c, body, clientToken) +} + +// CreatePaidZone - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreatePaidZone(body *CreatePaidZoneRequest, clientToken string) error { + return CreatePaidZone(c, body, clientToken) +} + +// CreateRecord - +// +// PARAMS: +// - zoneName: 域名名称。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateRecord(zoneName string, body *CreateRecordRequest, clientToken string) error { + return CreateRecord(c, zoneName, body, clientToken) +} + +// CreateZone - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateZone(body *CreateZoneRequest, clientToken string) error { + return CreateZone(c, body, clientToken) +} + +// DeleteLineGroup - +// +// PARAMS: +// - lineId: 线路组id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteLineGroup(lineId string, clientToken string) error { + return DeleteLineGroup(c, lineId, clientToken) +} + +// DeleteRecord - +// +// PARAMS: +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteRecord(zoneName string, recordId string, clientToken string) error { + return DeleteRecord(c, zoneName, recordId, clientToken) +} + +// DeleteZone - +// +// PARAMS: +// - zoneName: 域名的名称。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteZone(zoneName string, clientToken string) error { + return DeleteZone(c, zoneName, clientToken) +} + +// ListLineGroup - +// +// PARAMS: +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串。 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000,缺省值为1000。 +// - body: body参数 +// +// RETURNS: +// - *ListLineGroupResponse: +// - error: the return error if any occurs +func (c *Client) ListLineGroup(body *ListLineGroupRequest) (*ListLineGroupResponse, error) { + return ListLineGroup(c, body.Marker, body.MaxKeys) +} + +// ListRecord - +// +// PARAMS: +// - zoneName: 域名的名称。 +// - rr: 主机记录,例如“www”。 +// - id: 解析记录id。 +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串。 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000。 +// - body: body参数 +// +// RETURNS: +// - *ListRecordResponse: +// - error: the return error if any occurs +func (c *Client) ListRecord(zoneName string, request *ListRecordRequest) (*ListRecordResponse, error) { + return ListRecord(c, zoneName, request.Rr, request.Id, request.Marker, request.MaxKeys) +} + +// ListZone - +// +// PARAMS: +// - name: 域名的名称,支持模糊搜索。 +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 +// - body: body参数 +// +// RETURNS: +// - *ListZoneResponse: +// - error: the return error if any occurs +func (c *Client) ListZone(body *ListZoneRequest) ( + *ListZoneResponse, error) { + return ListZone(c, body, body.Name, body.Marker, body.MaxKeys) +} + +// RenewZone - +// +// PARAMS: +// - name: 续费的域名。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) RenewZone(name string, body *RenewZoneRequest, clientToken string) error { + return RenewZone(c, name, body, clientToken) +} + +// UpdateLineGroup - +// +// PARAMS: +// - lineId: 线路组id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateLineGroup(lineId string, body *UpdateLineGroupRequest, + clientToken string) error { + return UpdateLineGroup(c, lineId, body, clientToken) +} + +// UpdateRecord - +// +// PARAMS: +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateRecord(zoneName string, recordId string, body *UpdateRecordRequest, + clientToken string) error { + return UpdateRecord(c, zoneName, recordId, body, clientToken) +} + +// UpdateRecordDisable - +// +// PARAMS: +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateRecordDisable(zoneName string, recordId string, clientToken string) error { + return UpdateRecordDisable(c, zoneName, recordId, clientToken) +} + +// UpdateRecordEnable - +// +// PARAMS: +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateRecordEnable(zoneName string, recordId string, clientToken string) error { + return UpdateRecordEnable(c, zoneName, recordId, clientToken) +} + +// UpgradeZone - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpgradeZone(body *UpgradeZoneRequest, clientToken string) error { + return UpgradeZone(c, body, clientToken) +} diff --git a/bce-sdk-go/services/dns/client_test.go b/bce-sdk-go/services/dns/client_test.go new file mode 100644 index 0000000..00b572a --- /dev/null +++ b/bce-sdk-go/services/dns/client_test.go @@ -0,0 +1,217 @@ +package dns + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + DNS_CLIENT *Client + + // set these values before start test + Region = "bj" + RR = "rr" + TYPE = "A" + VALUE = "1.2.3.5" + ZONE_NAME = "ccq.com" + ZONE_NAME1 = "sdkdns.com" + PREPAID = "Prepaid" + ProductVersion = "discount" + RECORD_ID = "48526" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DNS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateZone(t *testing.T) { + createArgs := &CreateZoneRequest{ + Name: ZONE_NAME1, + } + err := DNS_CLIENT.CreateZone(createArgs, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListZone(t *testing.T) { + listZoneRequest := &ListZoneRequest{ + Name: ZONE_NAME, + } + res, err := DNS_CLIENT.ListZone(listZoneRequest) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteZone(t *testing.T) { + err := DNS_CLIENT.DeleteZone(ZONE_NAME, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreatePaidZone(t *testing.T) { + createArgs := &CreatePaidZoneRequest{ + Names: []string{ZONE_NAME}, + ProductVersion: ProductVersion, + Billing: Billing{ + PaymentTiming: PREPAID, + Reservation: Reservation{ + ReservationLength: 1, + }, + }, + } + err := DNS_CLIENT.CreatePaidZone(createArgs, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpgradeZone(t *testing.T) { + createArgs := &UpgradeZoneRequest{ + Names: []string{ZONE_NAME1}, + Billing: Billing{ + PaymentTiming: PREPAID, + Reservation: Reservation{ + ReservationLength: 1, + }, + }, + } + err := DNS_CLIENT.UpgradeZone(createArgs, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RenewZone(t *testing.T) { + createArgs := &RenewZoneRequest{ + Billing: Billing{ + Reservation: Reservation{ + ReservationLength: 1, + }, + }, + } + err := DNS_CLIENT.RenewZone(ZONE_NAME1, createArgs, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateRecord(t *testing.T) { + createRecordRequest := &CreateRecordRequest{ + Rr: RR, + Type: TYPE, + Value: VALUE, + } + err := DNS_CLIENT.CreateRecord(ZONE_NAME, createRecordRequest, "") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListRecord(t *testing.T) { + listRecordRequest := &ListRecordRequest{} + res, err := DNS_CLIENT.ListRecord(ZONE_NAME, listRecordRequest) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateRecord(t *testing.T) { + updateRecordRequest := &UpdateRecordRequest{ + Rr: RR, + Type: TYPE, + Value: "1.1.1.1", + } + err := DNS_CLIENT.UpdateRecord(ZONE_NAME, RECORD_ID, updateRecordRequest, "") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateRecordEnable(t *testing.T) { + err := DNS_CLIENT.UpdateRecordEnable(ZONE_NAME, "48540", "") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateRecordDisable(t *testing.T) { + err := DNS_CLIENT.UpdateRecordDisable(ZONE_NAME, "48540", "") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteRecord(t *testing.T) { + err := DNS_CLIENT.DeleteRecord(ZONE_NAME, "48540", "") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateLineGroup(t *testing.T) { + addLineGroupRequest := &AddLineGroupRequest{ + Name: "ccq0826", + Lines: []string{"yunnan.ct"}, + } + err := DNS_CLIENT.AddLineGroup(addLineGroupRequest, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateLineGroup(t *testing.T) { + updateLineGroupRequest := &UpdateLineGroupRequest{ + Name: "ccq0826_1", + Lines: []string{"india.any"}, + } + err := DNS_CLIENT.UpdateLineGroup("6165", updateLineGroupRequest, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListLineGroup(t *testing.T) { + listLineGroupRequest := &ListLineGroupRequest{} + res, err := DNS_CLIENT.ListLineGroup(listLineGroupRequest) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteLineGroup(t *testing.T) { + err := DNS_CLIENT.DeleteLineGroup("6039", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/dns/dns.go b/bce-sdk-go/services/dns/dns.go new file mode 100644 index 0000000..64b0abb --- /dev/null +++ b/bce-sdk-go/services/dns/dns.go @@ -0,0 +1,606 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 dns + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" + "strings" +) + +// AddLineGroup - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func AddLineGroup(cli bce.Client, body *AddLineGroupRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/dns/customline" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreatePaidZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreatePaidZone(cli bce.Client, body *CreatePaidZoneRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/dns/zone/order" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名名称。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateRecord(cli bce.Client, zoneName string, body *CreateRecordRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/dns/zone/[zoneName]/record" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreateZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func CreateZone(cli bce.Client, body *CreateZoneRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/dns/zone" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteLineGroup - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - lineId: 线路组id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// +// RETURNS: +// - error: the return error if any occurs +func DeleteLineGroup(cli bce.Client, lineId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/dns/customline/[lineId]" + path = strings.Replace(path, "[lineId]", lineId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeleteRecord(cli bce.Client, zoneName string, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/dns/zone/[zoneName]/record/[recordId]" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名的名称。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeleteZone(cli bce.Client, zoneName string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/dns/zone/[zoneName]" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// ListLineGroup - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串。 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000,缺省值为1000。 +// - body: +// +// RETURNS: +// - *api.ListLineGroupResponse: +// - error: the return error if any occurs +func ListLineGroup(cli bce.Client, marker string, maxKeys int) (*ListLineGroupResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/dns/customline" + req.SetUri(path) + if "" != marker { + req.SetParam("marker", marker) + } + if 0 != maxKeys { + req.SetParam("maxKeys", strconv.Itoa(maxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListLineGroupResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名的名称。 +// - rr: 主机记录,例如“www”。 +// - id: 解析记录id。 +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串。 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000。 +// - body: +// +// RETURNS: +// - *api.ListRecordResponse: +// - error: the return error if any occurs +func ListRecord(cli bce.Client, zoneName string, rr string, id string, + marker string, maxKeys int) (*ListRecordResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/dns/zone/[zoneName]/record" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + req.SetUri(path) + if "" != rr { + req.SetParam("rr", rr) + } + if "" != id { + req.SetParam("id", id) + } + if "" != marker { + req.SetParam("marker", marker) + } + if 0 != maxKeys { + req.SetParam("maxKeys", strconv.Itoa(maxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListRecordResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: 域名的名称,支持模糊搜索。 +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 +// - body: +// +// RETURNS: +// - *api.ListZoneResponse: +// - error: the return error if any occurs +func ListZone(cli bce.Client, body *ListZoneRequest, name string, marker string, maxKeys int) ( + *ListZoneResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/dns/zone" + req.SetUri(path) + if "" != name { + req.SetParam("name", name) + } + if "" != marker { + req.SetParam("marker", marker) + } + if 0 != maxKeys { + req.SetParam("maxKeys", strconv.Itoa(maxKeys)) + } + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListZoneResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// RenewZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - name: 续费的域名。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func RenewZone(cli bce.Client, name string, body *RenewZoneRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/zone/order/[name]" + path = strings.Replace(path, "[name]", name, -1) + req.SetUri(path) + req.SetParam("purchaseReserved", "") + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateLineGroup - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - lineId: 线路组id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateLineGroup(cli bce.Client, lineId string, body *UpdateLineGroupRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/customline/[lineId]" + path = strings.Replace(path, "[lineId]", lineId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateRecord(cli bce.Client, zoneName string, recordId string, body *UpdateRecordRequest, + clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/zone/[zoneName]/record/[recordId]" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateRecordDisable - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateRecordDisable(cli bce.Client, zoneName string, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/zone/[zoneName]/record/[recordId]" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("disable", "") + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateRecordEnable - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneName: 域名名称。 +// - recordId: 解析记录id。 +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateRecordEnable(cli bce.Client, zoneName string, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/zone/[zoneName]/record/[recordId]" + path = strings.Replace(path, "[zoneName]", zoneName, -1) + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("enable", "") + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpgradeZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串。 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpgradeZone(cli bce.Client, body *UpgradeZoneRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/dns/zone/order" + req.SetUri(path) + req.SetParam("upgradeToDiscount", "") + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/dns/model.go b/bce-sdk-go/services/dns/model.go new file mode 100644 index 0000000..db77ba9 --- /dev/null +++ b/bce-sdk-go/services/dns/model.go @@ -0,0 +1,200 @@ +/* + * Copyright Baidu, Inc. + * + * 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 dns + +type AddLineGroupRequest struct { + Name string `json:"name"` + Lines []string `json:"lines"` +} + +type AddLineGroupRequestLines struct { +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation Reservation `json:"reservation"` +} + +type CreatePaidZoneRequest struct { + Names []string `json:"names"` + ProductVersion string `json:"productVersion"` + Billing Billing `json:"billing"` +} + +type CreateRecordRequest struct { + Rr string `json:"rr"` + Type string `json:"type"` + Value string `json:"value"` + Ttl *int32 `json:"ttl"` + Line *string `json:"line"` + Description *string `json:"description"` + Priority *int32 `json:"priority"` +} + +type CreateZoneRequest struct { + Name string `json:"name"` +} + +type DeleteRecordRequest struct { + Rr string `json:"rr"` + Type string `json:"type"` + Value string `json:"value"` + Ttl *int32 `json:"ttl"` + Description *string `json:"description"` + Priority *int32 `json:"priority"` +} + +type DeleteZoneRequest struct { + Names []string `json:"names"` + ProductVersion string `json:"productVersion"` + Billing Billing `json:"billing"` +} + +type DeleteZoneRequestNames struct { +} + +type Line struct { + Id string `json:"id"` + Name string `json:"name"` + Lines []string `json:"lines"` + RelatedZoneCount int32 `json:"relatedZoneCount"` + RelatedRecordCount int32 `json:"relatedRecordCount"` +} + +type ListLineGroupRequest struct { + Marker string + MaxKeys int +} + +type ListLineGroupResponse struct { + Marker *string `json:"marker"` + IsTruncated *bool `json:"isTruncated"` + NextMarker *string `json:"nextMarker"` + MaxKeys *int32 `json:"maxKeys"` + LineList []Line `json:"lineList"` +} + +type ListRecordRequest struct { + Rr string + Id string + Marker string + MaxKeys int +} + +type ListRecordResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int32 `json:"maxKeys"` + Records []Record `json:"records"` +} + +type ListRecordResponseRecords struct { +} + +type ListZoneRequest struct { + Name string + Marker string + MaxKeys int +} + +type ListZoneResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int32 `json:"maxKeys"` + Zones []Zone `json:"zones"` +} + +type ListZoneResponseZones struct { +} + +type Record struct { + Id string `json:"id"` + Rr string `json:"rr"` + Status string `json:"status"` + Type string `json:"type"` + Value string `json:"value"` + Ttl int32 `json:"ttl"` + Line string `json:"line"` + Description string `json:"description"` + Priority int32 `json:"priority"` +} + +type RenewZoneRequest struct { + Billing Billing `json:"billing"` +} + +type Reservation struct { + ReservationLength int32 `json:"reservationLength"` +} + +type TagModel struct { + TagKey *string `json:"tagKey"` + TagValue *string `json:"tagValue"` +} + +type UpdateLineGroupRequest struct { + Name string `json:"name"` + Lines []string `json:"lines"` +} + +type UpdateLineGroupRequestLines struct { +} + +type UpdateRecordDisableRequest struct { + Rr string `json:"rr"` + Type string `json:"type"` + Value string `json:"value"` + Ttl *int32 `json:"ttl"` + Description *string `json:"description"` + Priority *int32 `json:"priority"` +} + +type UpdateRecordEnableRequest struct { + Rr string `json:"rr"` + Type string `json:"type"` + Value string `json:"value"` + Ttl *int32 `json:"ttl"` + Description *string `json:"description"` + Priority *int32 `json:"priority"` +} + +type UpdateRecordRequest struct { + Rr string `json:"rr"` + Type string `json:"type"` + Value string `json:"value"` + Ttl *int32 `json:"ttl"` + Description *string `json:"description"` + Priority *int32 `json:"priority"` +} + +type UpgradeZoneRequest struct { + Names []string `json:"names"` + Billing Billing `json:"billing"` +} + +type UpgradeZoneRequestNames struct { +} + +type Zone struct { + Id string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + ProductVersion string `json:"productVersion"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + Tags []TagModel `json:"tags"` +} diff --git a/bce-sdk-go/services/doc/api/document.go b/bce-sdk-go/services/doc/api/document.go new file mode 100644 index 0000000..abb71f3 --- /dev/null +++ b/bce-sdk-go/services/doc/api/document.go @@ -0,0 +1,261 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// document.go - the document APIs definition supported by the DOC service + +// Package api defines all APIs supported by the DOC service of BCE. +package api + +import ( + "errors" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// RegisterDocument - register document in doc service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - regParam title and format of the document being registered +// +// RETURNS: +// - *RegDocumentResp: id and document location in bos +// - error: the return error if any occurs +func RegisterDocument(cli bce.Client, regParam *RegDocumentParam) (*RegDocumentResp, error) { + if regParam == nil { + return nil, errors.New("param cannot be nil") + } + playload, err := regParam.String() + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromString(playload) + if err != nil { + return nil, err + } + + req := &bce.BceRequest{} + req.SetUri("/v2/document") + req.SetParam("register", "") + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &RegDocumentResp{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// PublishDocument - publish document +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - documentId: id of document in doc service +// +// RETURNS: +// - error: the return error if any occurs +func PublishDocument(cli bce.Client, documentId string) error { + req := &bce.BceRequest{} + urlPath := fmt.Sprintf("/v2/document/%s", documentId) + req.SetUri(urlPath) + req.SetParam("publish", "") + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil + +} + +// QueryDocument - query document's status +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - documentId: id of document in doc service +// - queryParam: enable/disable https of coverl url +// +// RETURNS: +// - *QueryDocumentResp +// - error: the return error if any occurs +func QueryDocument(cli bce.Client, documentId string, queryParam *QueryDocumentParam) (*QueryDocumentResp, error) { + req := &bce.BceRequest{} + urlPath := fmt.Sprintf("/v2/document/%s", documentId) + req.SetUri(urlPath) + if queryParam != nil { + req.SetParam("https", strconv.FormatBool(queryParam.Https)) + } + req.SetMethod(http.GET) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &QueryDocumentResp{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// ReadDocument - get document token for client sdk +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - documentId: id of document in doc service +// - readParam: expiration time of the doc's html +// +// RETURNS: +// - *ReadDocumentResp +// - error: the return error if any occurs +func ReadDocument(cli bce.Client, documentId string, readParam *ReadDocumentParam) (*ReadDocumentResp, error) { + req := &bce.BceRequest{} + urlPath := fmt.Sprintf("/v2/document/%s", documentId) + req.SetUri(urlPath) + req.SetParam("read", "") + if readParam != nil { + req.SetParam("expireInSeconds", strconv.FormatInt(readParam.ExpireInSeconds, 10)) + } + req.SetMethod(http.GET) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ReadDocumentResp{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// GetImages - Get the list of images generated by the document conversion +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - documentId: id of document in doc service +// +// RETURNS: +// - *ImagesListResp +// - error: the return error if any occurs +func GetImages(cli bce.Client, documentId string) (*GetImagesResp, error) { + req := &bce.BceRequest{} + urlPath := fmt.Sprintf("/v2/document/%s", documentId) + req.SetUri(urlPath) + req.SetParam("getImages", "") + req.SetMethod(http.GET) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetImagesResp{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteDocument - delete document in doc service +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - documentId: id of document in doc service +// +// RETURNS: +// - error: the return error if any occurs +func DeleteDocument(cli bce.Client, documentId string) error { + req := &bce.BceRequest{} + urlPath := fmt.Sprintf("/v2/document/%s", documentId) + req.SetUri(urlPath) + req.SetMethod(http.DELETE) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// ListDocuments - list all documents +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - param: the optional arguments to list documents +// +// RETURNS: +// - *ListDocumentsResp: the result docments list structure +// - error: nil if ok otherwise the specific error +func ListDocuments(cli bce.Client, listParam *ListDocumentsParam) (*ListDocumentsResp, error) { + err := listParam.Check() + if err != nil { + return nil, err + } + + req := &bce.BceRequest{} + req.SetUri("/v2/document/") + req.SetMethod(http.GET) + if listParam.Status != "" { + req.SetParam("status", string(listParam.Status)) + } + if listParam.Marker != "" { + req.SetParam("marker", listParam.Marker) + } + if listParam.MaxSize != 0 { + req.SetParam("maxSize", strconv.FormatInt(listParam.MaxSize, 10)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &ListDocumentsResp{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/doc/api/model.go b/bce-sdk-go/services/doc/api/model.go new file mode 100644 index 0000000..3d16cd0 --- /dev/null +++ b/bce-sdk-go/services/doc/api/model.go @@ -0,0 +1,180 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +import ( + "encoding/json" + "errors" +) + +type StatusType string + +const ( + DOC_TARGET_H5 = "h5" + DOC_TARGET_IMAGE = "image" + + DOC_PUBLIC = "PUBLIC" + DOC_PRIVATE = "PRIVATE" + + DOC_STATUS_UPLOADING StatusType = "UPLOADING" + DOC_STATUS_PROCESSING StatusType = "PROCESSING" + DOC_STATUS_PUBLISHED StatusType = "PUBLISHED" + DOC_STATUS_FAILED StatusType = "FAILED" +) + +type RegDocumentParam struct { + Title string `json:"title"` // must + Format string `json:"format"` // must,doc, docx, ppt, pptx, xls, xlsx, vsd, pot, pps, rtf, wps, et, dps, pdf, txt, epub + TargetType string `json:"targetType"` // h5|image, default: h5 + Notification string `json:"notification"` // notification + Access string `json:"access"` // PUBLIC|PRIVATE, default: PUBLIC +} + +// String - 格式化为json格式 +func (d *RegDocumentParam) String() (string, error) { + if d.Title == "" || d.Format == "" { + return "", errors.New("tile and format cannot be empty") + } + if d.TargetType == "" || (d.TargetType != DOC_TARGET_H5 && d.TargetType != DOC_TARGET_IMAGE) { + d.TargetType = DOC_TARGET_H5 + } + if d.Access != DOC_PUBLIC && d.Access != DOC_PRIVATE { + d.Access = DOC_PUBLIC + } + + j, e := json.Marshal(d) + if e != nil { + return "", e + } + + // 如果 notification 为空,则去掉该参数,不然请求会报错 + if d.Notification == "" { + var m map[string]string + json.Unmarshal(j, &m) + delete(m, "notification") + j, _ = json.Marshal(m) + } + + return string(j), nil +} + +// RegDocumentResp - 注册文档请求响应 +type RegDocumentResp struct { + DocumentId string `json:"documentId"` + Bucket string `json:"bucket"` + Object string `json:"object"` + BosEndpoint string `json:"bosEndpoint"` +} + +type GetImagesResp struct { + Images []ImageResp `json:"images"` +} + +type ImageResp struct { + PageIndex int64 `json:"pageIndex"` + Url string `json:"url"` +} + +type QueryDocumentParam struct { + Https bool +} + +type QueryDocumentResp struct { + DocumentId string `json:"documentId"` + Title string `json:"title"` + Format string `json:"format"` + TargetType string `json:"targetType"` + Status string `json:"status"` + UploadInfo UploadInfoResp `json:"uploadInfo"` + PublishInfo PublishInfoResp `json:"publishInfo"` + Notification string `json:"notification"` + Access string `json:"access"` + CreateTime string `json:"createTime"` + Error DocumentErrorResp `json:"error"` +} + +type UploadInfoResp struct { + Bucket string `json:"bucket"` + Object string `json:"object"` + BosEndpoint string `json:"bosEndpoint"` +} + +type PublishInfoResp struct { + PageCount int `json:"pageCount"` + SizeInBytes int `json:"sizeInBytes"` + CoverUrl string `json:"coverUrl"` + PublishTime string `json:"publishTime"` +} + +type DocumentErrorResp struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type ReadDocumentParam struct { + ExpireInSeconds int64 +} + +type ReadDocumentResp struct { + DocumentId string `json:"documentId"` + DocId string `json:"docId"` + Host string `json:"host"` + Token string `json:"token"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` +} + +type ListDocumentsParam struct { + Status StatusType + Marker string + MaxSize int64 +} + +func (l *ListDocumentsParam) Check() error { + switch l.Status { + case DOC_STATUS_UPLOADING: + case DOC_STATUS_FAILED: + case DOC_STATUS_PROCESSING: + case DOC_STATUS_PUBLISHED: + case "": + default: + return errors.New("invalid DOC status") + } + if l.MaxSize > 200 || l.MaxSize < 0 { + return errors.New("invalid maxSize") + } + return nil +} + +type ListDocumentsResp struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker,omitempty"` + Docs []DocumentResp `json:"documents"` +} + +type DocumentResp struct { + DocumentId string `json:"documentId"` + Title string `json:"title"` + Format string `json:"format"` + TargetType string `json:"targetType"` + Status string `json:"status"` + Notification string `json:"notification"` + Access string `json:"access"` + CreateTime string `json:"createTime"` + Error DocumentErrorResp `json:"error"` +} diff --git a/bce-sdk-go/services/doc/client.go b/bce-sdk-go/services/doc/client.go new file mode 100644 index 0000000..d75aaa9 --- /dev/null +++ b/bce-sdk-go/services/doc/client.go @@ -0,0 +1,168 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for DOC service +// Package doc defines the DOC services of BCE. The supported APIs are all defined in sub-package + +package doc + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/doc/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "doc.bj.baidubce.com" +) + +// Client of DOC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// DocClientConfiguration defines the config components structure by user. +type DocClientConfiguration struct { + Ak string + Sk string + Endpoint string +} + +// NewClient make the DOC service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk string) (*Client, error) { + return NewClientWithConfig(&DocClientConfiguration{ + Ak: ak, + Sk: sk, + Endpoint: DEFAULT_SERVICE_DOMAIN, + }) +} + +func NewClientWithConfig(config *DocClientConfiguration) (*Client, error) { + var credentials *auth.BceCredentials + var err error + ak, sk, endpoint := config.Ak, config.Sk, config.Endpoint + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS, + RedirectDisabled: false} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// RegisterDocument - register document in doc service +// +// PARAMS: +// - regParam title and format of the document being registered +// +// RETURNS: +// - *api.RegDocumentResp: id and document location in bos +// - error: the return error if any occurs +func (c *Client) RegisterDocument(regParam *api.RegDocumentParam) (*api.RegDocumentResp, error) { + return api.RegisterDocument(c, regParam) +} + +// PublishDocument - publish document +// +// PARAMS: +// - documentId: id of document in doc service +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) PublishDocument(documentId string) error { + return api.PublishDocument(c, documentId) +} + +// QueryDocument - query document's status +// +// PARAMS: +// - documentId: id of document in doc service +// - queryParam: enable/disable https of coverl url +// +// RETURNS: +// - *api.QueryDocumentResp +// - error: the return error if any occurs +func (c *Client) QueryDocument(documentId string, queryParam *api.QueryDocumentParam) (*api.QueryDocumentResp, error) { + return api.QueryDocument(c, documentId, queryParam) +} + +// ReadDocument - get document token for client sdk +// +// PARAMS: +// - documentId: id of document in doc service +// - readParam: expiration time of the doc's html +// +// RETURNS: +// - *api.ReadDocumentResp +// - error: the return error if any occurs +func (c *Client) ReadDocument(documentId string, readParam *api.ReadDocumentParam) (*api.ReadDocumentResp, error) { + return api.ReadDocument(c, documentId, readParam) +} + +// GetImages - Get the list of images generated by the document conversion +// +// PARAMS: +// - documentId: id of document in doc service +// +// RETURNS: +// - *api.ImagesListResp +// - error: the return error if any occurs +func (c *Client) GetImages(documentId string) (*api.GetImagesResp, error) { + return api.GetImages(c, documentId) +} + +// DeleteDocument - delete document in doc service +// +// PARAMS: +// - documentId: id of document in doc service +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteDocument(documentId string) error { + return api.DeleteDocument(c, documentId) +} + +// ListDocuments - list all documents +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - param: the optional arguments to list documents +// +// RETURNS: +// - *ListDocumentsResp: the result docments list structure +// - error: nil if ok otherwise the specific error +func (c *Client) ListDocuments(listParam *api.ListDocumentsParam) (*api.ListDocumentsResp, error) { + return api.ListDocuments(c, listParam) +} diff --git a/bce-sdk-go/services/doc/client_test.go b/bce-sdk-go/services/doc/client_test.go new file mode 100644 index 0000000..d826ae4 --- /dev/null +++ b/bce-sdk-go/services/doc/client_test.go @@ -0,0 +1,172 @@ +package doc + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/services/bos" + "github.com/baidubce/bce-sdk-go/services/doc/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + DOC_CLIENT *Client + BOS_CLIENT *bos.Client +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DOC_CLIENT, _ = NewClient(confObj.AK, confObj.SK) + BOS_CLIENT, _ = bos.NewClient(confObj.AK, confObj.SK, "") + //log.SetLogHandler(log.STDERR | log.FILE) + //log.SetRotateType(log.ROTATE_SIZE) + log.SetLogLevel(log.WARN) + //log.SetLogHandler(log.STDERR) + //log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestGetDocHTML(t *testing.T) { + regParam := &api.RegDocumentParam{ + Title: "test.txt", + Format: "txt", + } + res, err := DOC_CLIENT.RegisterDocument(regParam) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + + etag, err := BOS_CLIENT.PutObjectFromString(res.Bucket, res.Object, "test\nline", nil) + ExpectEqual(t.Errorf, nil, err) + + t.Logf("%+v", etag) + + err = DOC_CLIENT.PublishDocument(res.DocumentId) + ExpectEqual(t.Errorf, nil, err) + + for { + time.Sleep(time.Duration(1) * time.Second) + qRes, err := DOC_CLIENT.QueryDocument(res.DocumentId, &api.QueryDocumentParam{Https: false}) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", qRes) + if qRes.Status == "PUBLISHED" { + break + } + } + rRes, err := DOC_CLIENT.ReadDocument(res.DocumentId, &api.ReadDocumentParam{ExpireInSeconds: 3600}) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "BCEDOC", rRes.Host) + t.Logf("%+v", rRes) +} + +func TestGetDocImages(t *testing.T) { + regParam := &api.RegDocumentParam{ + Title: "sudoku.pdf", + Format: "pdf", + TargetType: "image", + } + res, err := DOC_CLIENT.RegisterDocument(regParam) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + + etag, err := BOS_CLIENT.PutObjectFromFile(res.Bucket, res.Object, "./sudoku.pdf", nil) + ExpectEqual(t.Errorf, nil, err) + + t.Logf("%+v", etag) + + err = DOC_CLIENT.PublishDocument(res.DocumentId) + ExpectEqual(t.Errorf, nil, err) + + for { + time.Sleep(time.Duration(1) * time.Second) + qRes, err := DOC_CLIENT.QueryDocument(res.DocumentId, &api.QueryDocumentParam{Https: false}) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", qRes) + if qRes.Status == "PUBLISHED" { + break + } + } + rRes, err := DOC_CLIENT.GetImages(res.DocumentId) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", rRes) +} + +func TestListDocs(t *testing.T) { + listParam := &api.ListDocumentsParam{ + Status: api.DOC_STATUS_PUBLISHED, + MaxSize: 2, + } + res, err := DOC_CLIENT.ListDocuments(listParam) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 2, len(res.Docs)) + t.Logf("%+v", res) + + listParam.Marker = res.NextMarker + listParam.MaxSize = 3 + res, err = DOC_CLIENT.ListDocuments(listParam) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 3, len(res.Docs)) +} + +func TestDeleteDocs(t *testing.T) { + regParam := &api.RegDocumentParam{ + Title: "test.txt", + Format: "txt", + } + res, err := DOC_CLIENT.RegisterDocument(regParam) + ExpectEqual(t.Errorf, nil, err) + t.Logf("%+v", res) + + err = DOC_CLIENT.DeleteDocument(res.DocumentId) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/dts/client.go b/bce-sdk-go/services/dts/client.go new file mode 100644 index 0000000..883bc13 --- /dev/null +++ b/bce-sdk-go/services/dts/client.go @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for DTS service + +// Package dts defines the DTS services of BCE. The supported APIs are all defined in sub-package +package dts + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + DEFAULT_ENDPOINT = "dts.baidubce.com" + REQUEST_DTS_URL = "/task" +) + +// Client of DTS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getDtsUri() string { + return URI_PREFIX + REQUEST_DTS_URL +} + +func getDtsUriWithTaskId(taskId string) string { + return URI_PREFIX + REQUEST_DTS_URL + "/" + taskId +} diff --git a/bce-sdk-go/services/dts/client_test.go b/bce-sdk-go/services/dts/client_test.go new file mode 100644 index 0000000..3e46fe2 --- /dev/null +++ b/bce-sdk-go/services/dts/client_test.go @@ -0,0 +1,260 @@ +package dts + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + DTS_CLIENT *Client + DTS_ID string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 1; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + DTS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateDts(t *testing.T) { + args := &CreateDtsArgs{ + ProductType: "postpay", + Type: "migration", + Standard: "Large", + SourceInstanceType: "public", + TargetInstanceType: "public", + CrossRegionTag: 0, + DirectionType: "single", + } + result, err := DTS_CLIENT.CreateDts(args) + ExpectEqual(t.Errorf, nil, err) + + DTS_ID = result.DtsTasks[0].DtsId +} + +func TestClient_GetDetail(t *testing.T) { + //result, err := DTS_CLIENT.GetDetail("dtsmris3cu2k0uw3fuo0") + result, err := DTS_CLIENT.GetDetail("dtsbukshhbsvd6yo7z96") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "unConfig", result.Status) +} + +func TestClient_ListDts(t *testing.T) { + var count = 0 + args := &ListDtsArgs{ + Type: "migration", + MaxKeys: 10, + } + for true { + result, err := DTS_CLIENT.ListDts(args) + ExpectEqual(t.Errorf, nil, err) + args.Marker = result.NextMarker + for _, e := range result.Task { + fmt.Println("dtsId: ", e.DtsId) + } + count += len(result.Task) + if !result.IsTruncated { + break + } + } + fmt.Println("count: ", count) +} + +func TestClient_ListDtsWithPage(t *testing.T) { + var count = 0 + args := &ListDtsWithPageArgs{ + Types: []string{"bidirect"}, + Filters: []ListFilter{ + { + Keyword: "he", + KeywordType: "taskName", + }, + }, + PageNo: 1, + PageSize: 10, + Order: "desc", + OrderBy: "createTime", + } + for true { + result, err := DTS_CLIENT.ListDtsWithPage(args) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Result { + fmt.Println("dtsId: ", e.DtsId) + } + count += len(result.Result) + if len(result.Result) < 10 { + break + } + } + fmt.Println("count: ", count) +} + +func TestClient_DeleteDts(t *testing.T) { + err := DTS_CLIENT.DeleteDts("dtsbbpvtjcb6ploqexvf") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ConfigDts(t *testing.T) { + args := &ConfigArgs{ + Type: "migration", + TaskName: "go-sdk-1", + DataType: []string{"increment"}, + SrcConnection: Connection{ + InstanceType: "bcerds", + DbType: "mysql", + InstanceId: "rdsm9744332", + Region: "bj", + FieldWhitelist: "", + FieldBlacklist: "", + }, + DstConnection: Connection{ + InstanceType: "bcerds", + DbType: "mysql", + InstanceId: "rdsmiu336698", + Region: "bj", + SqlType: "I,U", + }, + SchemaMapping: []Schema{ + { + Type: "table", + Src: "hello.user", + Dst: "hello.user", + Where: "", + }, + }, + Granularity: "dbtb", + InitPosition: InitPosition{ + Type: "binlog", + Position: "", + }, + } + result, err := DTS_CLIENT.ConfigDts("dtsmro61533558", args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println("result dtsId: ", result.DtsId) +} + +func TestClient_PreCheck(t *testing.T) { + result, err := DTS_CLIENT.PreCheck("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) + fmt.Println("result success: ", result.Success) + fmt.Println("result message: ", result.Message) +} + +func TestClient_GetPreCheck(t *testing.T) { + result, err := DTS_CLIENT.GetPreCheck("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) + fmt.Println("result success: ", result.Success) + for _, e := range result.Result { + fmt.Println("name: ", e.Name, "status: ", e.Status, "Message: ", e.Message, + "Subscription: ", e.Subscription) + } +} + +func TestClient_SkipPreCheck(t *testing.T) { + response, err := DTS_CLIENT.SkipPreCheck("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) + fmt.Println("response success: ", response.Success) + fmt.Println("response result: ", response.Result) +} + +func TestClient_StartDts(t *testing.T) { + err := DTS_CLIENT.StartDts("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_PauseDts(t *testing.T) { + err := DTS_CLIENT.PauseDts("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ShutdownDts(t *testing.T) { + err := DTS_CLIENT.ShutdownDts("dtsmro61533558") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSchema(t *testing.T) { + args := &GetSchemaArgs{ + Connection: Connection{ + InstanceType: "bcerds", + DbType: "mysql", + InstanceId: "rdsm9744332", + Region: "bj", + FieldWhitelist: "", + FieldBlacklist: "", + }, + } + response, err := DTS_CLIENT.GetSchema(args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println("response success: ", response.Success) + fmt.Println("response result: ", response.Result) +} + +func TestClient_UpdateTaskName(t *testing.T) { + args := &UpdateTaskNameArgs{ + TaskName: "go-sdkkk", + } + err := DTS_CLIENT.UpdateTaskName("dtsbe35xxxx8xzw365", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ResizeTaskStandard(t *testing.T) { + args := &ResizeTaskStandardArgs{ + Standard: "Xlarge", + } + response, err := DTS_CLIENT.ResizeTaskStandard("dtsbexxxxxzw365", args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println("response orderId: ", response.OrderId) +} diff --git a/bce-sdk-go/services/dts/dts.go b/bce-sdk-go/services/dts/dts.go new file mode 100644 index 0000000..c242786 --- /dev/null +++ b/bce-sdk-go/services/dts/dts.go @@ -0,0 +1,379 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// dts.go - the dts APIs definition supported by the DTS service +package dts + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateDts - create a dtsTask with the specific parameters +// +// PARAMS: +// - args: the arguments to create a dtsTask +// +// RETURNS: +// - *CreateDtsResult: the result of create dtsTask, contains new dtsTask's ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateDts(args *CreateDtsArgs) (*CreateDtsResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.ProductType == "" { + return nil, fmt.Errorf("unset ProductType") + } + + if args.Type == "" { + return nil, fmt.Errorf("unset Type") + } + + if args.Standard == "" { + return nil, fmt.Errorf("unset Standard") + } + + if args.SourceInstanceType == "" { + return nil, fmt.Errorf("unset SourceInstanceType") + } + + if args.TargetInstanceType == "" { + return nil, fmt.Errorf("unset TargetInstanceType") + } + + result := &CreateDtsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DeleteDts - delete a dtsTask +// +// PARAMS: +// - taskId: the specific taskId +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDts(taskId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getDtsUriWithTaskId(taskId)). + Do() +} + +// GetDetail - get a specific dtsTask's detail +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - *DtsTaskMeta: the specific dtsTask's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetDetail(taskId string) (*DtsTaskMeta, error) { + result := &DtsTaskMeta{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDtsUriWithTaskId(taskId)). + WithResult(result). + Do() + + return result, err +} + +// ListDts - list all dtsTask with the specific type +// +// PARAMS: +// - args: the arguments to list all dtsTask with the specific type +// +// RETURNS: +// - *ListDtsResult: the result of list all dtsTask, contains all dtsTask' detail +// - error: nil if success otherwise the specific error +func (c *Client) ListDts(args *ListDtsArgs) (*ListDtsResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Type == "" { + return nil, fmt.Errorf("unset type") + } + + if args.MaxKeys <= 0 { + args.MaxKeys = 10 + } + if args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListDtsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUri()+"/list"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListDtsWithPage - list all dtsTask with page +// +// PARAMS: +// - args: the arguments to list all dtsTask with page +// +// RETURNS: +// - *ListDtsResult: the result of list all dtsTask, contains all dtsTask' detail +// - error: nil if success otherwise the specific error +func (c *Client) ListDtsWithPage(args *ListDtsWithPageArgs) (*ListDtsWithPageResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Types == nil || len(args.Types) == 0 { + return nil, fmt.Errorf("unset type") + } + + if args.PageNo <= 0 { + args.PageNo = 1 + } + if args.PageSize > 100 { + args.PageSize = 100 + } + + result := &ListDtsWithPageResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUri()+"/listWithPage"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// PreCheck - precheck a dtsTask +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PreCheck(taskId string) (*PreCheckResult, error) { + result := &PreCheckResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUriWithTaskId(taskId) + "/precheck"). + WithResult(result). + Do() + return result, err +} + +// GetPreCheck - get a precheck result +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - *GetPreCheckResult: the specific dtsTask's precheck result +// - error: nil if success otherwise the specific error +func (c *Client) GetPreCheck(taskId string) (*GetPreCheckResult, error) { + result := &GetPreCheckResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDtsUriWithTaskId(taskId) + "/precheck"). + WithResult(result). + Do() + + return result, err +} + +// SkipPreCheck - skip precheck of a dts task +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SkipPreCheck(taskId string) (*SkipPreCheckResponse, error) { + result := &SkipPreCheckResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDtsUriWithTaskId(taskId)). + WithQueryParam("skipPrecheck", ""). + WithResult(result). + Do() + return result, err +} + +// ConfigDts - config a dtsTask with the specific parameters +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// - args: the arguments to config a dtsTask +// +// RETURNS: +// - *ConfigDtsResult: the result of config dtsTask, contains the dtsTask's ID +// - error: nil if success otherwise the specific error +func (c *Client) ConfigDts(taskId string, args *ConfigArgs) (*ConfigDtsResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.TaskName == "" { + return nil, fmt.Errorf("unset TaskName") + } + + if args.DataType == nil { + return nil, fmt.Errorf("unset DataType") + } + + if args.SchemaMapping == nil { + return nil, fmt.Errorf("unset SchemaMapping") + } + + result := &ConfigDtsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUriWithTaskId(taskId)+"/config"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// StartDts - start a dtsTask +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StartDts(taskId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUriWithTaskId(taskId) + "/start"). + Do() +} + +// PauseDts - pause a dtsTask +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PauseDts(taskId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUriWithTaskId(taskId) + "/pause"). + Do() +} + +// ShutdownDts - shutdown a dtsTask +// +// PARAMS: +// - taskId: the specific dtsTask's ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ShutdownDts(taskId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUriWithTaskId(taskId) + "/shutdown"). + Do() +} + +// GetSchema - get schema +// +// PARAMS: +// - args: connection param +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetSchema(args *GetSchemaArgs) (*GetSchemaResponse, error) { + result := &GetSchemaResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getDtsUri()+"/schema"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateTaskName - update task name +// +// PARAMS: +// - args: update task name param +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateTaskName(taskId string, args *UpdateTaskNameArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDtsUriWithTaskId(taskId)). + WithQueryParam("name", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ResizeTaskStandard - resize task standard +// +// PARAMS: +// - args: resize task standard param +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeTaskStandard(taskId string, args *ResizeTaskStandardArgs) (*ResizeTaskStandardResponse, error) { + result := &ResizeTaskStandardResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getDtsUriWithTaskId(taskId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("standard", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) GetVpcs(region string) (*DtsVpcsResult, error) { + result := &DtsVpcsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getDtsUri()+"/vpc"). + WithQueryParam("region", region). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/dts/model.go b/bce-sdk-go/services/dts/model.go new file mode 100644 index 0000000..e0a4c8c --- /dev/null +++ b/bce-sdk-go/services/dts/model.go @@ -0,0 +1,278 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package dts + +type CreateDtsArgs struct { + ClientToken string `json:"-"` + ProductType string `json:"productType"` + Type string `json:"type"` + Standard string `json:"standard"` + SourceInstanceType string `json:"sourceInstanceType"` + TargetInstanceType string `json:"targetInstanceType"` + CrossRegionTag int `json:"crossRegionTag"` + DirectionType string `json:"directionType"` + OrderInfo OrderInfo `json:"orderInfo"` +} + +type CreateDtsResult struct { + DtsTasks []DtsId `json:"dtsTasks"` +} + +type DtsId struct { + DtsId string `json:"dtsId"` +} + +type ConfigDtsResult struct { + DtsId string `json:"dtsId"` +} + +type DtsTaskMeta struct { + DtsId string `json:"dtsId"` + TaskName string `json:"taskName"` + Status string `json:"status"` + DataType []string `json:"dataType"` + Region string `json:"region"` + CreateTime string `json:"createTime"` + SrcConnection Connection `json:"srcConnection"` + DstConnection Connection `json:"dstConnection"` + SchemaMapping []Schema `json:"schemaMapping,omitempty"` + RunningTime int `json:"runningTime"` + SubStatus []SubStatus `json:"subStatus,omitempty"` + DynamicInfo DynamicInfo `json:"dynamicInfo,omitempty"` + Errmsg string `json:"errmsg,omitempty"` + SdkRealtimeProgress string `json:"sdkRealtimeProgress,omitempty"` + Granularity string `json:"granularity,omitempty"` + SubDataScope SubDataScope `json:"subDataScope,omitempty"` + PayInfo PayInfo `json:"payInfo,omitempty"` + LockStatus string `json:"lockStatus,omitempty"` + DtsIdPos string `json:"dtsIdPos,omitempty"` + DtsIdNeg string `json:"dtsIdNeg,omitempty"` + DtsTaskPos *DtsTaskMeta `json:"dtsTaskPos"` + DtsTaskNeg *DtsTaskMeta `json:"dtsTaskNeg"` +} + +type Connection struct { + Region string `json:"region"` + DbType string `json:"dbType"` + DbUser string `json:"dbUser"` + DbPass string `json:"dbPass"` + DbPort int `json:"dbPort"` + DbHost string `json:"dbHost"` + InstanceId string `json:"instanceId"` + DbServer string `json:"dbServer,omitempty"` + InstanceType string `json:"instanceType"` + InstanceShortId string `json:"instanceShortId,omitempty"` + FieldWhitelist string `json:"field_whitelist,omitempty"` + FieldBlacklist string `json:"field_blacklist,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` + SqlType string `json:"sqlType,omitempty"` + VpcId string `json:"vpcId"` + VpcName string `json:"vpcName"` + VpcCidr string `json:"vpcCidr"` + VpcShortId string `json:"vpcShortId"` +} + +type Schema struct { + Type string `json:"type"` + Src string `json:"src"` + Dst string `json:"dst"` + Where string `json:"where"` +} + +type SubStatus struct { + S string `json:"s"` + B string `json:"b"` + I string `json:"i"` +} + +type DynamicInfo struct { + Schema []SchemaInfo `json:"schema"` + Base []SchemaInfo `json:"base"` + Increment Increment `json:"increment"` +} + +type Increment struct { + Delay int64 `json:"delay"` + Position string `json:"position"` + SyncStatus string `json:"syncStatus"` +} + +type SchemaInfo struct { + Current string `json:"current"` + Count string `json:"count"` + Speed string `json:"speed"` + ExpectFinishTime string `json:"expectFinishTime"` +} + +type SubDataScope struct { + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type PayInfo struct { + ProductType string `json:"productType"` + SourceInstanceType string `json:"sourceInstanceType"` + TargetInstanceType string `json:"targetInstanceType"` + CrossRegionTag int `json:"crossRegionTag"` + CreateTime int `json:"createTime"` + Standard string `json:"standard"` + EndTime string `json:"endTime"` +} + +type ListDtsArgs struct { + Type string `json:"type"` + Status string `json:"status,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + Keyword string `json:"keyword,omitempty"` + KeywordType string `json:"keywordType,omitempty"` +} + +type ListDtsWithPageArgs struct { + Types []string `json:"types"` + Filters []ListFilter `json:"filters"` + Order string `json:"order"` + OrderBy string `json:"orderBy"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type ListFilter struct { + KeywordType string `json:"keywordType"` + Keyword string `json:"keyword"` +} + +type ListDtsResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Task []DtsTaskMeta `json:"task"` +} + +type ListDtsWithPageResult struct { + OrderBy string `json:"orderBy"` + Order string `json:"order"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` + Result []DtsTaskMeta `json:"result"` +} + +type CheckResult struct { + Name string `json:"name"` + Status string `json:"status"` + Message string `json:"message"` + Subscription string `json:"subscription"` +} + +type GetPreCheckResult struct { + Success bool `json:"success"` + Result []CheckResult `json:"result"` +} + +type ConfigArgs struct { + Type string `json:"type,omitempty"` + DtsId string `json:"dtsId,omitempty"` + TaskName string `json:"taskName"` + DataType []string `json:"dataType"` + SrcConnection Connection `json:"srcConnection"` + DstConnection Connection `json:"dstConnection"` + SchemaMapping []Schema `json:"schemaMapping"` + Granularity string `json:"granularity,omitempty"` + ProductType string `json:"productType,omitempty"` + QueueType string `json:"queueType,omitempty"` + InitPosition InitPosition `json:"initPosition,omitempty"` + NetType string `json:"netType,omitempty"` + Admin string `json:"admin,omitempty"` +} + +type InitPosition struct { + Type string `json:"type"` + Position string `json:"position"` +} + +type PreCheckResult struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +type SkipPreCheckResponse struct { + Success bool `json:"success"` + Result string `json:"result"` +} + +type GetSchemaArgs struct { + Connection Connection `json:"connection"` +} + +type GetSchemaResponse struct { + Success bool `json:"success"` + Result GetSchemaResult `json:"result"` +} + +type GetSchemaResult struct { + SchemaAll map[string]ObjectInDb `json:"schemaAll"` +} + +type ObjectInDb struct { + Tables []string `json:"tables"` + Views []string `json:"views"` + Procedures []string `json:"procedures"` + Functions []string `json:"functions"` +} + +type UpdateTaskNameArgs struct { + TaskName string `json:"taskName"` +} + +type ResizeTaskStandardArgs struct { + ClientToken string `json:"-"` + Standard string `json:"standard"` +} + +type ResizeTaskStandardResponse struct { + OrderId string `json:"orderId"` +} + +type OrderInfo struct { + Src Src `json:"src"` + Dst Dst `json:"dst"` +} + +type Src struct { + InstanceType string `json:"instanceType"` + DbType string `json:"dbType"` + SliceNum string `json:"sliceNum"` +} + +type Dst struct { + InstanceType string `json:"instanceType"` + DbType string `json:"dbType"` +} + +type DtsVpcsResult struct { + Vpcs []VpcVo `json:"vpcs"` +} + +type VpcVo struct { + VpcId string `json:"vpcId"` + ShortId string `json:"shortId"` + Name string `json:"name"` + Cidr string `json:"cidr"` +} diff --git a/bce-sdk-go/services/eccr/api.go b/bce-sdk-go/services/eccr/api.go new file mode 100644 index 0000000..c6e21a0 --- /dev/null +++ b/bce-sdk-go/services/eccr/api.go @@ -0,0 +1,589 @@ +package eccr + +import ( + "encoding/base64" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +type Interface interface { + ListInstances(args *ListInstancesArgs) (*ListInstancesResponse, error) + GetInstanceDetail(instanceID string) (*GetInstanceDetailResponse, error) + CreateInstance(args *CreateInstanceArgs) (*CreateInstanceResponse, error) + RenewInstance(orderType string, args *RenewInstanceArgs) (*RenewInstanceResponse, error) + UpdateInstance(instanceID string, args *UpdateInstanceArgs) (*UpdateInstanceResponse, error) + UpgradeInstance(instanceID string, args *UpgradeInstanceArgs) (*UpgradeInstanceResponse, error) + ListPrivateNetworks(instanceID string) (*ListPrivateNetworksResponse, error) + CreatePrivateNetwork(instanceID string, args *CreatePrivateNetworkArgs) (map[string]string, error) + DeletePrivateNetwork(instanceID string, args *DeletePrivateNetworkArgs) error + ListPublicNetworks(instanceID string) (*ListPublicNetworksResponse, error) + UpdatePublicNetwork(instanceID string, args *UpdatePublicNetworkArgs) error + DeletePublicNetworkWhitelist(instanceID string, args *DeletePublicNetworkWhitelistArgs) error + AddPublicNetworkWhitelist(instanceID string, args *AddPublicNetworkWhitelistArgs) error + ResetPassword(instanceID string, args *ResetPasswordArgs) (map[string]string, error) + CreateTemporaryToken(instanceID string, args *CreateTemporaryTokenArgs) (*CreateTemporaryTokenResponse, error) + CreateRegistry(instanceID string, args *CreateRegistryArgs) (*CreateRegistryResponse, error) + GetRegistryDetail(instanceID, registryID string) (*RegistryResponse, error) + ListRegistries(instanceID string, args *ListRegistriesArgs) (*ListRegistriesResponse, error) + CheckHealthRegistry(instanceID string, args *RegistryRequestArgs) error + UpdateRegistry(instanceID, registryID string, args *RegistryRequestArgs) (*RegistryResponse, error) + DeleteRegistry(instanceID, registryID string) error + ListBuildRepositoryTask(instanceID, projectName, repositoryName string, args *ListBuildRepositoryTaskArgs) (*ListBuildRepositoryTaskResponse, error) + CreateBuildRepositoryTask(instanceID, projectName, repositoryName string, args *BuildRepositoryTaskArgs) (*BuildRepositoryTaskResponse, error) + GetBuildRepositoryTask(instanceID, projectName, repositoryName, imageBuildID string) (*BuildRepositoryTaskResult, error) + DeleteBuildRepositoryTask(instanceID, projectName, repositoryName, imageBuildID string) error + BatchDeleteBuildRepositoryTask(instanceID, projectName, repositoryName string, args *BatchDeleteBuildRepositoryTaskArgs) error +} + +// ListInstances - list all instance with the specific parameters +// +// PARAMS: +// - ListInstancesArgs: the arguments to list all instance +// +// RETURNS: +// - ListInstancesResponse: the result of list Instance +// - error: nil if success otherwise the specific error +func (c *Client) ListInstances(args *ListInstancesArgs) (*ListInstancesResponse, error) { + + result := &ListInstancesResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceListURI()). + WithQueryParamFilter("keywordType", args.KeywordType). + WithQueryParamFilter("keyword", args.Keyword). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithQueryParamFilter("acrossregion", args.Acrossregion). + WithResult(result). + Do() + + return result, err +} + +// GetInstanceDetail - get a specific instance detail info +// +// PARAMS: +// - instanceID: the specific instance ID +// +// RETURNS: +// - *GetInstanceDetailResponse: the result of get instance detail info +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceDetail(instanceID string) (*GetInstanceDetailResponse, error) { + + result := &GetInstanceDetailResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceURI(instanceID)). + WithResult(result). + Do() + + return result, err +} + +// CreateInstance - create instance with the specific parameters +// +// PARAMS: +// - CreateInstanceArgs: the arguments to crate Instance +// +// RETURNS: +// - CreateInstanceResponse: the result of create Instance +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *CreateInstanceArgs) (*CreateInstanceResponse, error) { + result := &CreateInstanceResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getInstanceCreateURI()). + WithResult(result). + WithBody(args). + Do() + + return result, err +} + +// RenewInstance - create instance with the specific parameters +// +// PARAMS: +// - orderType: the operation type, value requires renew +// - ConfirmOrderRequest: the arguments to crate Instance +// +// RETURNS: +// - CreateInstanceResponse: the result of create Instance +// - error: nil if success otherwise the specific error +func (c *Client) RenewInstance(orderType string, args *RenewInstanceArgs) (*RenewInstanceResponse, error) { + result := &RenewInstanceResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithQueryParamFilter("orderType", orderType). + WithURL(getInstanceRenewURI()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateInstance - update instance info +// +// PARAMS: +// - instanceId: the specific instance ID +// - UpdateInstanceArgs: parameters required to update instance info +// +// RETURNS: +// - *UpdateInstanceResponse: the result of updated instance info +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstance(instanceID string, args *UpdateInstanceArgs) (*UpdateInstanceResponse, error) { + result := &UpdateInstanceResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpgradeInstance - upgrade instance by specific parameters +// +// PARAMS: +// - instanceId: the specific instance ID +// - UpgradeInstanceArgs: parameters required to upgrade instance information +// +// RETURNS: +// +// = UpgradeInstanceResponse: the result of upgrade instance +// - error: nil if success otherwise the specific error +func (c *Client) UpgradeInstance(instanceID string, args *UpgradeInstanceArgs) (*UpgradeInstanceResponse, error) { + result := &UpgradeInstanceResponse{} + + err := bce.NewRequestBuilder(c).WithMethod(http.PUT). + WithURL(getInstanceUpgradeURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListPrivateNetworks - list all Privatelinks in an instance with the specific parameters +// +// PARAMS: +// - instanceID: the specific instance ID +// +// RETURNS: +// - *ListPrivateNetworksResponse: the result of list Privatelinks +// - error: nil if success otherwise the specific error +func (c *Client) ListPrivateNetworks(instanceID string) (*ListPrivateNetworksResponse, error) { + result := &ListPrivateNetworksResponse{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getPrivateNetworkListResponseURI(instanceID)). + WithResult(result). + Do() + + return result, err +} + +// CreatePrivateNetwork - create private Network with the specific parameters +// +// PARAMS: +// - instanceID: the specific instance ID +// - CreateInstanceArgs: the arguments to crate private network +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreatePrivateNetwork(instanceID string, args *CreatePrivateNetworkArgs) (map[string]string, error) { + result := make(map[string]string) + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getPrivateNetworkResponseURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DeletePrivateNetwork - delete private Network with the specific parameters +// +// PARAMS: +// - instanceID: the specific instance ID +// - DeletePrivateNetworkArgs: the arguments to delete private network +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeletePrivateNetwork(instanceID string, args *DeletePrivateNetworkArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getPrivateNetworkResponseURI(instanceID)). + WithBody(args). + Do() + + return err +} + +// ListPublicNetworks - list all Publiclinks in an instance with the specific parameters +// +// PARAMS: +// - instanceID: the specific instance ID +// +// RETURNS: +// - *ListPublicNetworksResponse: the result of list Publiclinks +// - error: nil if success otherwise the specific error +func (c *Client) ListPublicNetworks(instanceID string) (*ListPublicNetworksResponse, error) { + result := &ListPublicNetworksResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getPublicNetworkResponseURI(instanceID)). + WithResult(result).Do() + + return result, err +} + +// UpdatePublicNetwork - update Publiclink +// +// PARAMS: +// - instanceId: the specific instance ID +// - UpdatePublicNetworkArgs: parameters required to update publiclink info +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdatePublicNetwork(instanceID string, args *UpdatePublicNetworkArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getPublicNetworkResponseURI(instanceID)). + WithBody(args). + Do() + + return err +} + +// DeletePublicNetworkWhitelist - delete Publiclink white list +// +// PARAMS: +// - instanceId: the specific instance ID +// - DeletePublicNetworkWhiteListArgs: delete publiclinks list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeletePublicNetworkWhitelist(instanceID string, args *DeletePublicNetworkWhitelistArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getPublicNetworkWhitelistURI(instanceID)). + WithBody(args). + Do() + return err +} + +// AddPublicNetworkWhitelist - add Publiclink white list +// +// PARAMS: +// - instanceId: the specific instance ID +// - DeletePublicNetworkWhiteListArgs: the arguments to delete publiclinks withlist +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AddPublicNetworkWhitelist(instanceID string, args *AddPublicNetworkWhitelistArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getPublicNetworkWhitelistURI(instanceID)). + WithBody(args). + Do() + + return err +} + +// ResetPassword - reset login password +// +// PARAMS: +// - instanceId: the specific instance ID +// - ResetPasswordArgs: the arguments to reset password +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - map[string]string: the result of reset password +func (c *Client) ResetPassword(instanceID string, args *ResetPasswordArgs) (map[string]string, error) { + result := make(map[string]string) + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceCredentialURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateTemporaryToken - create temporary token +// +// PARAMS: +// - instanceID: the specific instance ID +// - CreateTemporaryTokenArgs: the arguments to crate temporary token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateTemporaryToken(instanceID string, args *CreateTemporaryTokenArgs) (*CreateTemporaryTokenResponse, error) { + result := &CreateTemporaryTokenResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getInstanceCredentialURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateRegistry - create registry +// +// PARAMS: +// - instanceID: the specific instance ID +// - CreateRegistryArgs: the arguments to crate registry +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - CreateRegistryResponse: the result od create registry +func (c *Client) CreateRegistry(instanceID string, args *CreateRegistryArgs) (*CreateRegistryResponse, error) { + result := &CreateRegistryResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getInstanceRegistryURI(instanceID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetRegistryDetail - get a specific registry detail info +// +// PARAMS: +// - instanceID: the specific instance ID +// - registryID: the specific registry ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - RegistryResponse: the result of create registry +func (c *Client) GetRegistryDetail(instanceID, registryID string) (*RegistryResponse, error) { + result := &RegistryResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceRegistryIDURI(instanceID, registryID)). + WithResult(result). + Do() + + return result, err +} + +// ListRegistries - get a registry list of instance +// +// PARAMS: +// - instanceID: the specific instance ID +// - ListRegistriesArgs: parameters required to list registry information +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - ListRegistriesResponse: the result of list registry +func (c *Client) ListRegistries(instanceID string, args *ListRegistriesArgs) (*ListRegistriesResponse, error) { + result := &ListRegistriesResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParamFilter("registryName", args.RegistryName). + WithQueryParamFilter("registryType", args.RegistryType). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithURL(getInstanceRegistryURI(instanceID)). + WithResult(result). + Do() + + return result, err +} + +// CheckHealthRegistry - check if the registry is healthy +// +// PARAMS: +// - instanceID: the specific instance ID +// - RegistryRequestArgs: parameters required to check registry +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CheckHealthRegistry(instanceID string, args *RegistryRequestArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getCheckHealthRegistryURI(instanceID)). + WithBody(args). + Do() + + return err +} + +// UpdateRegistry - update the registry info +// +// PARAMS: +// - instanceID: the specific instance ID +// - registryID: the specific registry ID +// - RegistryRequestArgs: parameters required to update registry +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - RegistryResponse: the result of update registry +func (c *Client) UpdateRegistry(instanceID, registryID string, args *RegistryRequestArgs) (*RegistryResponse, error) { + result := &RegistryResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceRegistryIDURI(instanceID, registryID)). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) DeleteRegistry(instanceID, registryID string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getInstanceRegistryIDURI(instanceID, registryID)). + Do() + + return err +} + +// ListBuildRepositoryTask - list the build task info +// +// PARAMS: +// - instanceID: the specific instance ID +// - projectName: the specific project Name +// - repositoryName: the specific registry Name +// - ListBuildRepositoryTaskArgs: parameters required to get task list +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - ListBuildRepositoryTaskResponse: the result of build task list + +func (c *Client) ListBuildRepositoryTask(instanceID, projectName, repositoryName string, args *ListBuildRepositoryTaskArgs) ( + *ListBuildRepositoryTaskResponse, error) { + result := &ListBuildRepositoryTaskResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParamFilter("keywordType", args.KeywordType). + WithQueryParamFilter("keyword", args.Keyword). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithURL(getImageBuildURI(instanceID, projectName, base64.RawURLEncoding.EncodeToString([]byte(repositoryName)))). + WithResult(result). + Do() + + return result, err +} + +// CreateBuildRepositoryTask - create the build task +// +// PARAMS: +// - instanceID: the specific instance ID +// - projectName: the specific project Name +// - repositoryName: the specific registry Name +// - BuildRepositoryTaskArgs: parameters required to create build task +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - BuildRepositoryTaskResponse: the result of build task + +func (c *Client) CreateBuildRepositoryTask(instanceID, projectName, repositoryName string, args *BuildRepositoryTaskArgs) ( + *BuildRepositoryTaskResponse, error) { + result := &BuildRepositoryTaskResponse{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getImageBuildURI(instanceID, projectName, base64.RawURLEncoding.EncodeToString([]byte(repositoryName)))). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetBuildRepositoryTask - get the build task +// +// PARAMS: +// - instanceID: the specific instance ID +// - projectName: the specific project Name +// - repositoryName: the specific registry Name +// - imageBuildID: the specific image build ID +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - BuildRepositoryTaskResult: the result of build task + +func (c *Client) GetBuildRepositoryTask(instanceID, projectName, repositoryName, imageBuildID string) (*BuildRepositoryTaskResult, error) { + result := &BuildRepositoryTaskResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getImageBuildInfoURI(instanceID, projectName, base64.RawURLEncoding.EncodeToString([]byte(repositoryName)), imageBuildID)). + WithResult(result). + Do() + + return result, err +} + +// DeleteBuildRepositoryTask - batch delete the build task +// +// PARAMS: +// - instanceID: the specific instance ID +// - projectName: the specific project Name +// - repositoryName: the specific registry Name +// +// RETURNS: +// - error: nil if success otherwise the specific error + +func (c *Client) DeleteBuildRepositoryTask(instanceID, projectName, repositoryName, imageBuildID string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getImageBuildInfoURI(instanceID, projectName, base64.RawURLEncoding.EncodeToString([]byte(repositoryName)), imageBuildID)). + Do() + + return err +} + +// BatchDeleteBuildRepositoryTask - batch delete the build task +// +// PARAMS: +// - instanceID: the specific instance ID +// - projectName: the specific project Name +// - repositoryName: the specific registry Name +// - BatchDeleteBuildRepositoryTaskArgs: parameters required to batch delete build task +// +// RETURNS: +// - error: nil if success otherwise the specific error + +func (c *Client) BatchDeleteBuildRepositoryTask(instanceID, projectName, repositoryName string, args *BatchDeleteBuildRepositoryTaskArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithBody(args). + WithURL(getImageBuildURI(instanceID, projectName, base64.RawURLEncoding.EncodeToString([]byte(repositoryName)))). + Do() + + return err +} diff --git a/bce-sdk-go/services/eccr/client.go b/bce-sdk-go/services/eccr/client.go new file mode 100644 index 0000000..dcba6ed --- /dev/null +++ b/bce-sdk-go/services/eccr/client.go @@ -0,0 +1,107 @@ +package eccr + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_ENDPOINT = "ccr." + bce.DEFAULT_REGION + ".baidubce.com" + + URI_PREFIX = bce.URI_PREFIX + "v1" + + REQUEST_INSTANCE_URL = "/instances" + + REQUEST_PRIVATELINK_URL = "/privatelinks" + + REQUEST_PUBLICLINK_URL = "/publiclinks" + + REQUEST_PUBLICLINK_WITHLIST_URL = "/whitelist" + + REQUEST_CREDENTIAL_URL = "/credential" + + REQUEST_REGISTRY_URL = "/registries" + + REQUEST_REPOSITORIES_URL = "/repositories" + + REQUEST_PROJECT_URL = "/projects" + + REQUEST_IMAGEBUILD_URL = "/imagebuilds" +) + +// Client ccr enterprise interface.Interface +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getInstanceListURI() string { + return URI_PREFIX + REQUEST_INSTANCE_URL +} + +func getInstanceURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID +} + +func getInstanceCreateURI() string { + return URI_PREFIX + REQUEST_INSTANCE_URL +} + +func getInstanceRenewURI() string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/renew" +} + +func getInstanceUpgradeURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + "/upgrade" +} + +func getPrivateNetworkListResponseURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PRIVATELINK_URL +} + +func getPrivateNetworkResponseURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PRIVATELINK_URL +} + +func getPublicNetworkResponseURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PUBLICLINK_URL +} + +func getPublicNetworkWhitelistURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PUBLICLINK_URL + REQUEST_PUBLICLINK_WITHLIST_URL +} + +func getInstanceCredentialURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_CREDENTIAL_URL +} + +func getInstanceRegistryURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_REGISTRY_URL +} + +func getInstanceRegistryIDURI(instanceID, registryID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_REGISTRY_URL + "/" + registryID +} + +func getCheckHealthRegistryURI(instanceID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_REGISTRY_URL + "/ping" +} + +func getImageBuildURI(instanceID, projectName, repositoryName string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PROJECT_URL + "/" + projectName + + REQUEST_REPOSITORIES_URL + "/" + repositoryName + REQUEST_IMAGEBUILD_URL +} + +func getImageBuildInfoURI(instanceID, projectName, repositoryName, imageBuildID string) string { + return URI_PREFIX + REQUEST_INSTANCE_URL + "/" + instanceID + REQUEST_PROJECT_URL + "/" + projectName + + REQUEST_REPOSITORIES_URL + "/" + repositoryName + REQUEST_IMAGEBUILD_URL + "/" + imageBuildID +} diff --git a/bce-sdk-go/services/eccr/client_test.go b/bce-sdk-go/services/eccr/client_test.go new file mode 100644 index 0000000..3809e0e --- /dev/null +++ b/bce-sdk-go/services/eccr/client_test.go @@ -0,0 +1,475 @@ +package eccr + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + CCR_CLIENT *Client + CCR_INSTANCE_ID string + CCR_REGISTRY_ID string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func TestMain(m *testing.M) { + setup() + code := m.Run() + os.Exit(code) +} + +func setup() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fmt.Println(conf) + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + if err := decoder.Decode(confObj); err != nil { + log.Fatal("decode config obj err:", err) + os.Exit(1) + } + + log.SetLogLevel(log.WARN) + + CCR_CLIENT, err = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + if err != nil { + log.Fatal(err) + } + + log.Info("Setup Complete") +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_ListInstances(t *testing.T) { + args := &ListInstancesArgs{ + KeywordType: "clusterName", + Keyword: "", + PageNo: 1, + PageSize: 10, + } + resp, err := CCR_CLIENT.ListInstances(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_GetInstanceDetail(t *testing.T) { + + resp, err := CCR_CLIENT.GetInstanceDetail(CCR_INSTANCE_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_CreateInstance(t *testing.T) { + + billing := Billing{ + ReservationTimeUnit: "MONTH", + ReservationTime: 1, + AutoRenew: false, + AutoRenewTimeUnit: "MONTH", + AutoRenewTime: 1, + } + + args := &CreateInstanceArgs{ + Type: "BASIC", + Name: "instanceName", + Bucket: "", + PaymentTiming: "prepay", + Billing: billing, + PaymentMethod: []PaymentMethod{}, + } + + resp, err := CCR_CLIENT.CreateInstance(args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_RenewInstance(t *testing.T) { + + orderType := "RENEW" + args := &RenewInstanceArgs{ + Items: []Item{}, + PaymentMethod: []PaymentMethod{}, + } + + resp, err := CCR_CLIENT.RenewInstance(orderType, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_UpdateInstance(t *testing.T) { + + args := &UpdateInstanceArgs{ + Name: "InstanceName", + } + + resp, err := CCR_CLIENT.UpdateInstance(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + + fmt.Println("Response:", string(s)) +} + +func TestClient_UpgradeInstance(t *testing.T) { + + args := &UpgradeInstanceArgs{ + Type: "STANDARD", + PaymentMethod: []PaymentMethod{}, + } + + resp, err := CCR_CLIENT.UpgradeInstance(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_ListPrivateNetworks(t *testing.T) { + + resp, err := CCR_CLIENT.ListPrivateNetworks(CCR_INSTANCE_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_CreatePrivateNetwork(t *testing.T) { + + args := &CreatePrivateNetworkArgs{ + VpcID: "VpcID", + SubnetID: "SubnetID", + IPAddress: "", + IPType: "", + AutoDNSResolve: false, + } + + resp, err := CCR_CLIENT.CreatePrivateNetwork(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_DeletePrivateNetwork(t *testing.T) { + + args := &DeletePrivateNetworkArgs{ + VpcID: "VpcID", + SubnetID: "SubnetID", + } + + err := CCR_CLIENT.DeletePrivateNetwork(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + fmt.Println("Delete Private Network Test Passed") +} + +func TestClient_ListPublicNetworks(t *testing.T) { + + resp, err := CCR_CLIENT.ListPublicNetworks(CCR_INSTANCE_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_UpdatePublicNetwork(t *testing.T) { + args := &UpdatePublicNetworkArgs{ + Action: "open", + } + + err := CCR_CLIENT.UpdatePublicNetwork(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + fmt.Println("Update Public Network Test Passed") + +} + +func TestClient_DeletePublicNetworkWhitelist(t *testing.T) { + args := &DeletePublicNetworkWhitelistArgs{ + Items: []string{"PublicNetworkWhiteListArgs"}, + } + + err := CCR_CLIENT.DeletePublicNetworkWhitelist(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + fmt.Println("Delete Public Network Whitelist Test Passed") +} + +func TestClient_AddPublicNetworkWhitelist(t *testing.T) { + args := &AddPublicNetworkWhitelistArgs{ + IPAddr: "cidrv4", + Description: "", + } + + err := CCR_CLIENT.AddPublicNetworkWhitelist(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + fmt.Println("Add Public Network Whitelist Test Passed") +} + +func TestClient_ResetPassword(t *testing.T) { + args := &ResetPasswordArgs{ + Password: "Password", + } + + resp, err := CCR_CLIENT.ResetPassword(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_CreateTemporaryToken(t *testing.T) { + args := &CreateTemporaryTokenArgs{ + Duration: 10, + } + + resp, err := CCR_CLIENT.CreateTemporaryToken(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_CreateRegistry(t *testing.T) { + args := &CreateRegistryArgs{ + Credential: &RegistryCredential{}, + Description: "", + Insecure: false, + Name: "", + Type: "harbor", + URL: "https://baidu.com", + } + + resp, err := CCR_CLIENT.CreateRegistry(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_GetRegistryDetail(t *testing.T) { + + resp, err := CCR_CLIENT.GetRegistryDetail(CCR_INSTANCE_ID, CCR_REGISTRY_ID) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:" + string(s)) +} + +func TestClient_ListRegistries(t *testing.T) { + args := &ListRegistriesArgs{ + RegistryName: "", + RegistryType: "", + PageNo: 1, + PageSize: 10, + } + + resp, err := CCR_CLIENT.ListRegistries(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.Marshal(resp) + fmt.Println("Response:" + string(s)) +} + +func TestClient_CheckHealthRegistry(t *testing.T) { + args := &RegistryRequestArgs{ + Credential: &RegistryCredential{}, + Description: "", + Insecure: false, + Name: "", + Type: "harbor", + URL: "https://registry.baidubce.com", + } + + err := CCR_CLIENT.CheckHealthRegistry(CCR_INSTANCE_ID, args) + + ExpectEqual(t.Errorf, nil, err) + fmt.Println("Check Health Registry Test Passed") +} + +func TestClient_UpdateRegistry(t *testing.T) { + args := &RegistryRequestArgs{ + Credential: &RegistryCredential{}, + Description: "", + Insecure: true, + Name: "", + Type: "harbor", + URL: "https://registry.baidubce.com", + } + + resp, err := CCR_CLIENT.UpdateRegistry(CCR_INSTANCE_ID, CCR_REGISTRY_ID, args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_DeleteRegistry(t *testing.T) { + + err := CCR_CLIENT.DeleteRegistry(CCR_INSTANCE_ID, CCR_REGISTRY_ID) + + ExpectEqual(t.Errorf, nil, err) + + fmt.Println("Delete Registry Test Passed") +} + +func TestClient_CreateBuildRepositoryTask(t *testing.T) { + args := &BuildRepositoryTaskArgs{ + TagName: "v1.0", + IsLatest: false, + Dockerfile: "from busybox \n yum install pip", + FromType: "dcokerfile", + } + + resp, err := CCR_CLIENT.CreateBuildRepositoryTask(CCR_INSTANCE_ID, "test", "pip", args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_ListBuildRepositoryTaskArgs(t *testing.T) { + args := &ListBuildRepositoryTaskArgs{ + KeywordType: "tag", + Keyword: "v1.0", + PageNo: 1, + PageSize: 10, + } + + resp, err := CCR_CLIENT.ListBuildRepositoryTask(CCR_INSTANCE_ID, "test", "pip", args) + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_GetBuildRepositoryTask(t *testing.T) { + resp, err := CCR_CLIENT.GetBuildRepositoryTask(CCR_INSTANCE_ID, "test", "pip", "1") + + ExpectEqual(t.Errorf, nil, err) + + s, _ := json.MarshalIndent(resp, "", "\t") + fmt.Println("Response:", string(s)) +} + +func TestClient_BatchDeleteBuildRepositoryTask(t *testing.T) { + args := &BatchDeleteBuildRepositoryTaskArgs{ + Items: []string{"1", "2"}, + } + err := CCR_CLIENT.BatchDeleteBuildRepositoryTask(CCR_INSTANCE_ID, "test", "pip", args) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteBuildRepositoryTask(t *testing.T) { + err := CCR_CLIENT.DeleteBuildRepositoryTask(CCR_INSTANCE_ID, "test", "pip", "1") + + ExpectEqual(t.Errorf, nil, err) +} + +func Test_getImageBuildURI(t *testing.T) { + type args struct { + instanceID string + projectName string + repositoryName string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "image build uri", + args: args{ + instanceID: "test_instance", + projectName: "test_project", + repositoryName: "test_repository", + }, + want: fmt.Sprintf(`/v1/projects/%s/repositories/%s/imageBuilds/%s`, "test_instance", "test_project", "test_repository"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getImageBuildURI(tt.args.instanceID, tt.args.projectName, tt.args.repositoryName); got != tt.want { + t.Errorf("getImageBuildURI() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/bce-sdk-go/services/eccr/model.go b/bce-sdk-go/services/eccr/model.go new file mode 100644 index 0000000..a00c705 --- /dev/null +++ b/bce-sdk-go/services/eccr/model.go @@ -0,0 +1,381 @@ +package eccr + +import ( + "time" +) + +type PagedListOption struct { + PageNo int + PageSize int + KeywordType string + Keyword string +} + +type PageInfo struct { + Total int `json:"total"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type ListInstancesArgs struct { + PageNo int + PageSize int + KeywordType string + Keyword string + Acrossregion string +} + +type ListInstancesResponse struct { + PageInfo `json:",inline"` + Instances []*InstanceInfo `json:"instances"` +} + +type InstanceInfo struct { + ID string `json:"id"` + InstanceType string `json:"instanceType"` + Name string `json:"name"` + Status string `json:"status"` + CreateTime time.Time `json:"createTime"` + Region string `json:"region"` + PublicURL string `json:"publicURL"` + ExpireTime time.Time `json:"expireTime"` +} + +type InstanceStatistic struct { + Repo int64 `json:"repo"` + Chart int64 `json:"chart"` + Namespace int64 `json:"namespace"` + Storage int64 `json:"storage"` +} + +type UserQuota struct { + Repo int64 `json:"repo"` + Chart int64 `json:"chart"` + Namespace int64 `json:"namespace"` +} + +type GetInstanceDetailResponse struct { + Info *InstanceInfo `json:"info,omitempty"` + Statistic InstanceStatistic `json:"statistic,omitempty"` + Quota UserQuota `json:"quota,omitempty"` + Bucket string `json:"bucket,omitempty"` + Region string `json:"region,omitempty"` +} + +type CreateInstanceArgs struct { + Type string `json:"type" binding:"required,oneof=BASIC STANDARD ADVANCED"` + Name string `json:"name" binding:"required,max=256,min=1"` + Bucket string `json:"bucket"` + + PaymentTiming string `json:"paymentTiming" binding:"required,oneof=prepay"` + Billing Billing `json:"billing"` + + PaymentMethod []PaymentMethod `json:"paymentMethod"` +} + +type PaymentMethod struct { + Type string `json:"type"` + Values []string `json:"values"` +} + +type Billing struct { + ReservationTimeUnit string `json:"reservationTimeUnit" binding:"required,oneof=MONTH YEAR"` + ReservationTime int `json:"reservationTime" binding:"required,oneof=1 2 3 4 5 6 7 8 9"` + AutoRenew bool `json:"autoRenew"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit" binding:"oneof=MONTH YEAR"` + AutoRenewTime int `json:"autoRenewTime" binding:"required,oneof=1 2 3 4 5 6 7 8 9"` +} + +type CreateInstanceResponse struct { + InstanceID string `json:"instanceID"` + OrderID string `json:"orderID"` +} + +type RenewInstanceArgs struct { + Items []Item `json:"items"` + // Payment information + PaymentMethod []PaymentMethod `json:"paymentMethod"` +} + +type RenewInstanceResponse struct { + OrderId string `json:"orderId"` +} + +type Item struct { + Config Config `json:"config"` + // Payment information + PaymentMethod []PaymentMethod `json:"paymentMethod"` +} +type Config struct { + // renewal time + Duration int `json:"duration"` + InstanceId string `json:"instanceId"` + // Product name + ServiceType string `json:"serviceType"` + // renew time unit 'year' | 'month' | 'day'; default `month` + TimeUnit string `json:"timeUnit"` + // Whether it is an order that expires uniformly. default(no): no parameter, yes: 1 + UnionExpireOrderFlag string `json:"unionExpireOrderFlag"` + // UUID + UUID string `json:"uuid"` +} + +type UpdateInstanceArgs struct { + Name string `json:"name,omitempty" binding:"required,max=256,min=1"` +} + +type UpdateInstanceResponse struct { + ID string `json:"id"` + InstanceType string `json:"instanceType"` + Name string `json:"name"` + Status string `json:"status"` + CreateTime time.Time `json:"createTime"` + Region string `json:"region"` + PublicURL string `json:"publicURL"` + ExpireTime time.Time `json:"expireTime"` +} + +type UpgradeInstanceArgs struct { + Type string `json:"type" binding:"required,oneof=BASIC STANDARD ADVANCED"` + + PaymentMethod []PaymentMethod `json:"paymentMethod"` +} + +type UpgradeInstanceResponse struct { + OrderID string `json:"orderID"` +} + +type ListPrivateNetworksResponse struct { + Domain string `json:"domain"` + Items []PrivateNetworksItems `json:"items"` +} + +type PrivateNetworksItems struct { + VpcID string `json:"vpcID,omitempty"` + SubnetID string `json:"subnetID,omitempty"` + ServiceNetID string `json:"serviceNetID,omitempty"` + Status string `json:"status,omitempty"` + IPAddress string `json:"ipAddress,omitempty"` + ResourceSource string `json:"resourceSource,omitempty"` +} + +type CreatePrivateNetworkArgs struct { + VpcID string `json:"vpcID,omitempty" binding:"required"` + SubnetID string `json:"subnetID,omitempty" binding:"required"` + IPAddress string `json:"ipAddress,omitempty"` + IPType string `json:"ipType,omitempty"` + AutoDNSResolve bool `json:"autoDnsResolve,omitempty"` +} + +type DeletePrivateNetworkArgs struct { + VpcID string `json:"vpcID,omitempty"` + SubnetID string `json:"subnetID,omitempty"` +} + +type PublicNetworkInfoWhitelist struct { + IPAddr string `json:"ipAddr,omitempty" binding:"required,cidrv4|ipv4"` + Description string `json:"description"` +} + +type ListPublicNetworksResponse struct { + Domain string `json:"domain"` + Status string `json:"status"` + Whitelist []PublicNetworkInfoWhitelist `json:"whitelist"` +} + +type UpdatePublicNetworkArgs struct { + Action string `json:"action,omitempty" enums:"open,close" binding:"required,oneof=open close"` +} + +type DeletePublicNetworkWhitelistArgs struct { + Items []string `json:"items,omitempty" binding:"required"` +} + +type AddPublicNetworkWhitelistArgs struct { + IPAddr string `json:"ipAddr,omitempty" binding:"required,cidrv4|ipv4"` + Description string `json:"description"` +} + +type ResetPasswordArgs struct { + Password string `json:"password,omitempty" binding:"required,password"` +} + +type CreateTemporaryTokenArgs struct { + Duration int `json:"duration,omitempty" binding:"required,min=1,max=24"` +} + +type CreateTemporaryTokenResponse struct { + Password string `json:"password,omitempty"` +} + +type RegistryCredential struct { + + // Access key, e.g. user name when credential type is 'basic'. + AccessKey string `json:"accessKey"` + + // Access secret, e.g. password when credential type is 'basic'. + AccessSecret string `json:"accessSecret,omitempty"` + + // Credential type, such as 'basic', 'oauth'. + Type string `json:"type"` +} + +type CreateRegistryArgs struct { + // credential + Credential *RegistryCredential `json:"credential"` + + // Description of the registry. + Description string `json:"description"` + + // Whether or not the certificate will be verified when Harbor tries to access the server. + Insecure bool `json:"insecure"` + + // The registry name. + Name string `json:"name"` + + // Type of the registry, e.g. 'harbor'. + Type string `json:"type" binding:"required,oneof=harbor docker-hub docker-registry baidu-ccr"` + + // The registry URL string. + URL string `json:"url"` +} + +type CreateRegistryResponse struct { + // The create time of the policy. + CreationTime string `json:"creation_time,omitempty"` + + // credential + Credential *RegistryCredential `json:"credential,omitempty"` + + // Description of the registry. + Description string `json:"description,omitempty"` + + // The registry ID. + ID int64 `json:"id,omitempty"` + + // Whether or not the certificate will be verified when Harbor tries to access the server. + Insecure bool `json:"insecure,omitempty"` + + // The registry name. + Name string `json:"name,omitempty"` + + // Health status of the registry. + Status string `json:"status,omitempty"` + + // Type of the registry, e.g. 'harbor'. + Type string `json:"type,omitempty"` + + // The update time of the policy. + UpdateTime string `json:"update_time,omitempty"` + + // The registry URL string. + URL string `json:"url,omitempty"` +} + +type RegistryResponse struct { + // id + ID int64 `json:"id"` + + // creation time + CreationTime string `json:"creationTime"` + + // credential + Credential *RegistryCredential `json:"credential"` + + // description + Description string `json:"description"` + + // insecure + Insecure bool `json:"insecure"` + + // name + Name string `json:"name"` + + // status + Status string `json:"status"` + + // type + Type string `json:"type"` + + // update time + UpdateTime string `json:"updateTime"` + + // url + URL string `json:"url"` +} + +type ListRegistriesArgs struct { + RegistryName string `json:"registryName"` + RegistryType string `json:"registryType"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type ListRegistriesResponse struct { + PageInfo `json:",inline"` + Items []*RegistryResponse `json:"items"` +} + +type RegistryRequestArgs struct { + // credential + Credential *RegistryCredential `json:"credential"` + + // Description of the registry. + Description string `json:"description"` + + // Whether or not the certificate will be verified when Harbor tries to access the server. + Insecure bool `json:"insecure"` + + // The registry name. + Name string `json:"name"` + + // Type of the registry, e.g. 'harbor'. + Type string `json:"type" binding:"required,oneof=harbor docker-hub docker-registry baidu-ccr"` + + // The registry URL string. + URL string `json:"url"` +} + +type ListBuildRepositoryTaskArgs struct { + KeywordType string `json:"keywordType"` + Keyword string `json:"keyword"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +// ListBuildRepositoryTaskResponse list repository task response +type ListBuildRepositoryTaskResponse struct { + PageInfo `json:",inline"` + Items []*BuildRepositoryTaskResult `json:"items"` +} + +// BuildRepositoryTaskResult build repository task result +type BuildRepositoryTaskResult struct { + ID string `json:"id"` + TagName string `json:"tagName"` + IsLatest bool `json:"isLatest"` + Status string `json:"status"` + FromType string `json:"fromType"` + Dockerfile string `json:"dockerfile"` + CreateBy string `json:"createBy"` + CreateAt time.Time `json:"createAt"` + Image string `json:"image"` +} + +// BuildRepositoryTaskArgs build repository task request +type BuildRepositoryTaskArgs struct { + TagName string `json:"tagName"` + IsLatest bool `json:"isLatest"` + FromType string `json:"fromType"` + Dockerfile string `json:"dockerfile"` +} + +// BuildRepositoryTaskResponse build repository task response +type BuildRepositoryTaskResponse struct { + ID string `json:"id"` +} + +// BatchDeleteBuildRepositoryTaskArgs delete task request +type BatchDeleteBuildRepositoryTaskArgs struct { + Items []string `json:"items"` +} diff --git a/bce-sdk-go/services/eip/client.go b/bce-sdk-go/services/eip/client.go new file mode 100644 index 0000000..8bead1c --- /dev/null +++ b/bce-sdk-go/services/eip/client.go @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for EIP service + +// Package eip defines the EIP services of BCE. The supported APIs are all defined in sub-package +package eip + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "eip." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_EIP_URL = "/eip" + + REQUEST_RECYCLE_EIP_URL = "/eip/recycle" + + REQUEST_EIP_CLUSTER_URL = "/eipcluster" + + REQUEST_EIP_TP_URL = "/eiptp" + + REQUEST_EIP_GROUP_URL = "/eipgroup" + + REQUEST_EIP_BP_URL = "/eipbp" +) + +// Client of EIP service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getEipUri() string { + return URI_PREFIX + REQUEST_EIP_URL +} + +func getEipUriWithEip(eip string) string { + return URI_PREFIX + REQUEST_EIP_URL + "/" + eip +} + +func getRecycleEipUri() string { + return URI_PREFIX + REQUEST_RECYCLE_EIP_URL +} + +func getRecycleEipUriWithEip(eip string) string { + return URI_PREFIX + REQUEST_RECYCLE_EIP_URL + "/" + eip +} + +func getEipClusterUri() string { + return URI_PREFIX + REQUEST_EIP_CLUSTER_URL +} + +func getEipClusterUriWithId(clusterId string) string { + return URI_PREFIX + REQUEST_EIP_CLUSTER_URL + "/" + clusterId +} + +func getEipTpUri() string { + return URI_PREFIX + REQUEST_EIP_TP_URL +} + +func getEipTpUriWithId(id string) string { + return URI_PREFIX + REQUEST_EIP_TP_URL + "/" + id +} + +func getEipGroupUri() string { + return URI_PREFIX + REQUEST_EIP_GROUP_URL +} + +func getEipGroupUriWithId(id string) string { + return URI_PREFIX + REQUEST_EIP_GROUP_URL + "/" + id +} + +func getEipBpUrl() string { + return URI_PREFIX + REQUEST_EIP_BP_URL +} + +func getEipBpUrlWithId(id string) string { + return URI_PREFIX + REQUEST_EIP_BP_URL + "/" + id +} diff --git a/bce-sdk-go/services/eip/client_test.go b/bce-sdk-go/services/eip/client_test.go new file mode 100644 index 0000000..83238f2 --- /dev/null +++ b/bce-sdk-go/services/eip/client_test.go @@ -0,0 +1,434 @@ +package eip + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + EIP_CLIENT *Client + EIP_ADDRESS string + CLUSTER_ID string + // set this value before start test + BCC_TEST_ID = "" + EIP_TP_ID = "" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + EIP_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateEip(t *testing.T) { + args := &CreateEipArgs{ + Name: "sdk-eip", + BandWidthInMbps: 1, + Billing: &Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByTraffic", + }, + ClientToken: getClientToken(), + } + result, err := EIP_CLIENT.CreateEip(args) + ExpectEqual(t.Errorf, nil, err) + + EIP_ADDRESS = result.Eip +} + +func TestClient_BatchCreateEip(t *testing.T) { + args := &BatchCreateEipArgs{ + Name: "sdk-eip", + BandWidthInMbps: 1, + Billing: &Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByBandwidth", + }, + Count: 254, + RouteType: "BGP", + //Idc: "bjdd_cu", + Continuous: true, + ClientToken: getClientToken(), + } + result, err := EIP_CLIENT.BatchCreateEip(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_ResizeEip(t *testing.T) { + args := &ResizeEipArgs{ + NewBandWidthInMbps: 2, + ClientToken: getClientToken(), + } + err := EIP_CLIENT.ResizeEip(EIP_ADDRESS, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindEip(t *testing.T) { + args := &BindEipArgs{ + InstanceType: "BCC", + InstanceId: BCC_TEST_ID, + ClientToken: getClientToken(), + } + err := EIP_CLIENT.BindEip(EIP_ADDRESS, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnBindEip(t *testing.T) { + err := EIP_CLIENT.UnBindEip(EIP_ADDRESS, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_PurchaseReservedEip(t *testing.T) { + args := &PurchaseReservedEipArgs{ + Billing: &Billing{ + Reservation: &Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, + } + err := EIP_CLIENT.PurchaseReservedEip(EIP_ADDRESS, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEip(t *testing.T) { + args := &ListEipArgs{} + EIP_CLIENT.ListEip(args) +} + +func TestClient_DeleteEip(t *testing.T) { + err := EIP_CLIENT.DeleteEip(EIP_ADDRESS, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_OptionalDeleteEip(t *testing.T) { + err := EIP_CLIENT.OptionalDeleteEip(EIP_ADDRESS, getClientToken(), false) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEipCluster(t *testing.T) { + args := &ListEipArgs{} + result, err := EIP_CLIENT.ListEipCluster(args) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.ClusterList { + ExpectEqual(t.Errorf, "zone-A|zone-C", e.ClusterAz) + ExpectEqual(t.Errorf, "c-76a34e7b", e.ClusterId) + ExpectEqual(t.Errorf, "su-eip-pdd", e.ClusterName) + ExpectEqual(t.Errorf, "su", e.ClusterRegion) + } +} + +func TestClient_GetEipCluster(t *testing.T) { + result, err := EIP_CLIENT.GetEipCluster(CLUSTER_ID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "zone-A|zone-C", result.ClusterAz) + ExpectEqual(t.Errorf, "c-76a34e7b", result.ClusterId) + ExpectEqual(t.Errorf, "su-eip-pdd", result.ClusterName) + ExpectEqual(t.Errorf, "su", result.ClusterRegion) + ExpectEqual(t.Errorf, 240000000000, result.NetworkInBps) + ExpectEqual(t.Errorf, 240000000000, result.NetworkOutBps) + ExpectEqual(t.Errorf, 48000000, result.NetworkInPps) + ExpectEqual(t.Errorf, 48000000, result.NetworkOutPps) +} + +func TestClient_DirectEip(t *testing.T) { + EIP_CLIENT.DirectEip(EIP_ADDRESS, getClientToken()) +} + +func TestClient_UnDirectEip(t *testing.T) { + EIP_CLIENT.UnDirectEip(EIP_ADDRESS, getClientToken()) +} + +func TestClient_ListRecycleEip(t *testing.T) { + args := &ListRecycleEipArgs{ + Marker: "", + MaxKeys: 1, + } + result, err := EIP_CLIENT.ListRecycleEip(args) + ExpectEqual(t.Errorf, nil, err) + fmt.Println(result) +} + +func TestClient_RestoreRecycleEip(t *testing.T) { + err := EIP_CLIENT.RestoreRecycleEip(EIP_ADDRESS, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteRecycleEip(t *testing.T) { + err := EIP_CLIENT.DeleteRecycleEip(EIP_ADDRESS, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateEipTp(t *testing.T) { + args := &CreateEipTpArgs{ + ReservationLength: 1, + Capacity: "10G", + DeductPolicy: "TimeDurationPackage", + PackageType: "WebOutBytes", + ClientToken: getClientToken(), + } + result, err := EIP_CLIENT.CreateEipTp(args) + ExpectEqual(t.Errorf, nil, err) + EIP_TP_ID = result.Id +} + +func TestClient_ListEipTp(t *testing.T) { + args := &ListEipTpArgs{ + Id: "tp-R4E8cWijbf", + DeductPolicy: "FullTimeDurationPackage", + Status: "RUNNING", + } + _, err := EIP_CLIENT.ListEipTp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetEipTp(t *testing.T) { + _, err := EIP_CLIENT.GetEipTp(EIP_TP_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateEipGroup(t *testing.T) { + args := &CreateEipGroupArgs{ + Name: "sdk-eipGroup", + BandWidthInMbps: 500, + Billing: &Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByBandwidth", + }, + EipCount: 254, + RouteType: "BGP", + Continuous: true, + ClientToken: getClientToken(), + } + result, err := EIP_CLIENT.CreateEipGroup(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_ResizeEipGroupBandWidth(t *testing.T) { + args := &ResizeEipGroupArgs{ + BandWidthInMbps: 22, + ClientToken: getClientToken(), + } + EIP_CLIENT.ResizeEipGroupBandWidth("eg-2b1ef0db", args) +} + +func TestClient_EipGroupAddEipCount(t *testing.T) { + args := &GroupAddEipCountArgs{ + EipAddCount: 1, + ClientToken: getClientToken(), + } + EIP_CLIENT.EipGroupAddEipCount("eg-2b1ef0db", args) +} + +func TestClient_ReleaseEipGroupIps(t *testing.T) { + Eips := []string{"100.88.3.216"} + args := &ReleaseEipGroupIpsArgs{ + ReleaseIps: Eips, + ClientToken: getClientToken(), + } + EIP_CLIENT.ReleaseEipGroupIps("eg-2b1ef0db", args) +} + +func TestClient_RenameEipGroup(t *testing.T) { + args := &RenameEipGroupArgs{ + Name: "new_SDK_111", + ClientToken: getClientToken(), + } + EIP_CLIENT.RenameEipGroup("eg-2b1ef0db", args) +} + +func TestClient_ListEipGroup(t *testing.T) { + result, err := EIP_CLIENT.ListEipGroup(nil) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_EipGroupDetail(t *testing.T) { + result, err := EIP_CLIENT.EipGroupDetail("eg-2b1ef0db") + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_EipGroupMoveIn(t *testing.T) { + Ips := []string{"100.88.1.231"} + args := &EipGroupMoveInArgs{ + Eips: Ips, + ClientToken: getClientToken(), + } + err := EIP_CLIENT.EipGroupMoveIn("eg-2b1ef0db", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EipGroupMoveOut(t *testing.T) { + args := &EipGroupMoveOutArgs{ + MoveOutEips: []MoveOutEip{ + { + Eip: "100.88.11.128", + BandWidthInMbps: 11, + Billing: &Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByBandwidth", + }, + }, + }, + ClientToken: getClientToken(), + } + err := EIP_CLIENT.EipGroupMoveOut("eg-2b1ef0db", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EipGroupPurchaseReserved(t *testing.T) { + args := &EipGroupPurchaseReservedArgs{ + Billing: &Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByBandwidth", + }, + ClientToken: getClientToken(), + } + err := EIP_CLIENT.EipGroupPurchaseReserved("eg-2b1ef0db", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateEipBp(t *testing.T) { + args := &CreateEipBpArgs{ + Name: "sdk-eipBp", + Eip: "100.88.3.216", + BandwidthInMbps: 10, + Type: "BandwidthPackage", + AutoReleaseTime: "2023-12-13T20:38:43Z", + ClientToken: getClientToken(), + } + _, err := EIP_CLIENT.CreateEipBp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteEipBp(t *testing.T) { + id := "bw-7fP4jhXO" + clientToken := getClientToken() + err := EIP_CLIENT.DeleteEipBp(id, clientToken) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetEipBp(t *testing.T) { + id := "bw-7fP4jhXO" + clientToken := getClientToken() + _, err := EIP_CLIENT.GetEipBp(id, clientToken) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEipBp(t *testing.T) { + args := &ListEipBpArgs{ + Id: "bw-7fP4jhXO", + Name: "sdk-eipBp", + Type: "BandwidthPackage", + Marker: "", + MaxKeys: 1000, + BindType: "eip", + } + _, err := EIP_CLIENT.ListEipBp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ResizeEipBp(t *testing.T) { + args := &ResizeEipBpArgs{ + BandwidthInMbps: 10, + ClientToken: getClientToken(), + } + id := "bw-7fP4jhXO" + err := EIP_CLIENT.ResizeEipBp(id, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEipBp(t *testing.T) { + args := &UpdateEipBpNameArgs{ + Name: "sdk-eipBp", + ClientToken: getClientToken(), + } + id := "bw-7fP4jhXO" + err := EIP_CLIENT.UpdateEipBpName(id, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEipBpAutoReleaseTime(t *testing.T) { + args := &UpdateEipBpAutoReleaseTimeArgs{ + AutoReleaseTime: "2023-12-13T20:38:43Z", + ClientToken: getClientToken(), + } + id := "bw-7fP4jhXO" + err := EIP_CLIENT.UpdateEipBpAutoReleaseTime(id, args) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/eip/config.json b/bce-sdk-go/services/eip/config.json new file mode 100644 index 0000000..b666209 --- /dev/null +++ b/bce-sdk-go/services/eip/config.json @@ -0,0 +1,5 @@ +{ + "AK":"ak", + "SK":"sk", + "Endpoint":"endpoint" +} diff --git a/bce-sdk-go/services/eip/eip.go b/bce-sdk-go/services/eip/eip.go new file mode 100644 index 0000000..64a4cc4 --- /dev/null +++ b/bce-sdk-go/services/eip/eip.go @@ -0,0 +1,500 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// eip.go - the eip APIs definition supported by the EIP service +package eip + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateEip - create an EIP with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eip +// +// RETURNS: +// - *CreateEipResult: the result of create EIP, contains new EIP's address +// - error: nil if success otherwise the specific error +func (c *Client) CreateEip(args *CreateEipArgs) (*CreateEipResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create eip argments") + } + + if args.Billing == nil { + return nil, fmt.Errorf("please set billing") + } + + result := &CreateEipResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getEipUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// BatchCreateEip - create EIPs with the specific parameters +// +// PARAMS: +// - args: the arguments to create eips +// +// RETURNS: +// - *BatchCreateEipResult: the result of create EIP, contains new EIP's address +// - error: nil if success otherwise the specific error +func (c *Client) BatchCreateEip(args *BatchCreateEipArgs) (*BatchCreateEipResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create eip argments") + } + + if args.Billing == nil { + return nil, fmt.Errorf("please set billing") + } + + result := &BatchCreateEipResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getEipUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("action", "batch"). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ResizeEip - resize an EIP with the specific parameters +// +// PARAMS: +// - eip: the specific EIP +// - args: the arguments to resize an EIP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeEip(eip string, args *ResizeEipArgs) error { + if args == nil { + return fmt.Errorf("please set resize eip argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + WithBody(args). + Do() +} + +// BindEip - bind an EIP to an instance with the specific parameters +// +// PARAMS: +// - eip: the specific EIP +// - args: the arguments to bind an EIP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindEip(eip string, args *BindEipArgs) error { + if args == nil { + return fmt.Errorf("please set bind eip argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("bind", ""). + WithBody(args). + Do() +} + +// UnBindEip - unbind an EIP +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindEip(eip, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("unbind", ""). + Do() +} + +// DeleteEip - delete an EIP +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEip(eip, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// OptionalDeleteEip - optionally delete an EIP +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// - releaseToRecycle: the parameter confirms whether to put the specific EIP in the recycle bin (true) or directly delete it (false) +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) OptionalDeleteEip(eip string, clientToken string, releaseToRecycle bool) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("releaseToRecycle", strconv.FormatBool(releaseToRecycle)). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ListEip - list all EIP with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eip +// +// RETURNS: +// - *ListEipResult: the result of list all eip, contains new EIP's ID +// - error: nil if success otherwise the specific error +func (c *Client) ListEip(args *ListEipArgs) (*ListEipResult, error) { + if args == nil { + args = &ListEipArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListEipResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipUri()). + WithQueryParamFilter("eip", args.Eip). + WithQueryParamFilter("instanceType", args.InstanceType). + WithQueryParamFilter("instanceId", args.InstanceId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParamFilter("status", args.Status). + WithResult(result). + Do() + + return result, err +} + +// ListRecycleEip - list all EIP in the recycle bin with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eip in the recycle bin +// +// RETURNS: +// - *ListRecycleEipResult: the result of list all eip in the recycle bin +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycleEip(args *ListRecycleEipArgs) (*ListRecycleEipResult, error) { + if args == nil { + args = &ListRecycleEipArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListRecycleEipResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRecycleEipUri()). + WithQueryParamFilter("eip", args.Eip). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// RestoreRecycleEip - restore the specific EIP in the recycle bin +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RestoreRecycleEip(eip string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRecycleEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("restore", ""). + Do() +} + +// DeleteRecycleEip - delete the specific EIP in the recycle bin +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecycleEip(eip string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRecycleEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// PurchaseReservedEip - purchase reserve an eip with the specific parameters +// +// PARAMS: +// - eip: the specific EIP +// - args: the arguments to purchase reserve an eip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) PurchaseReservedEip(eip string, args *PurchaseReservedEipArgs) error { + if args == nil { + return fmt.Errorf("please set purchase reserved eip argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("purchaseReserved", ""). + WithBody(args). + Do() +} + +// StartAutoRenew - start auto renew an eip +// +// PARAMS: +// - eip: the specific EIP +// - args: the arguments to start auto renew an eip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StartAutoRenew(eip string, args *StartAutoRenewArgs) error { + if args == nil { + return fmt.Errorf("please set eip auto renew argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParam("startAutoRenew", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// StopAutoRenew - stop eip auto renew +// +// PARAMS: +// - eip: the specific EIP +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StopAutoRenew(eip string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParam("stopAutoRenew", ""). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ListEipCluster - list all EIP Cluster with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eip cluster +// RETURNS: +// - *ListClusterResult: the result of list all eip cluster +// - error: nil if success otherwise the specific error + +func (c *Client) ListEipCluster(args *ListEipArgs) (*ListClusterResult, error) { + if args == nil { + args = &ListEipArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListClusterResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipClusterUri()). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetEipCluster - get the eip cluster detail with the clusterId +// +// PARAMS: +// - clusterId: the specific clusterId +// RETURNS: +// - *ClusterDetail: the result of eip cluster detail +// - error: nil if success otherwise the specific error + +func (c *Client) GetEipCluster(clusterId string) (*ClusterDetail, error) { + if len(clusterId) == 0 { + return nil, fmt.Errorf("please set clusterId argment") + } + result := &ClusterDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipClusterUriWithId(clusterId)). + WithResult(result). + Do() + + return result, err +} + +// DirectEip - turn on EIP pass through with the specific parameters +// +// PARAMS: +// - eip: the specific EIP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DirectEip(eip, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("direct", ""). + Do() +} + +// UnDirectEip - turn off EIP pass through with the specific parameters +// +// PARAMS: +// - eip: the specific EIP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnDirectEip(eip, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipUriWithEip(eip)). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("unDirect", ""). + Do() +} + +// CreateEipTp - create an EIP TP with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eiptp +// +// RETURNS: +// - *CreateEipTpResult: the created eiptp id. +// - error: nil if success otherwise the specific error +func (c *Client) CreateEipTp(args *CreateEipTpArgs) (*CreateEipTpResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create eip tp argments") + } + if len(args.Capacity) == 0 { + return nil, fmt.Errorf("please set capacity argment") + } + result := &CreateEipTpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getEipTpUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListEipTp - list all EIP TPs with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eiptps +// +// RETURNS: +// - *ListEipTpResult: the result of listing all eiptps +// - error: nil if success otherwise the specific error +func (c *Client) ListEipTp(args *ListEipTpArgs) (*ListEipTpResult, error) { + if args == nil { + args = &ListEipTpArgs{} + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListEipTpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipTpUri()). + WithQueryParamFilter("id", args.Id). + WithQueryParamFilter("deductPolicy", args.DeductPolicy). + WithQueryParamFilter("status", args.Status). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetEipTp - get the EIP TP detail with the id +// +// PARAMS: +// - id: the specific eiptp id +// RETURNS: +// - *EipTpDetail: the result of eiptp detail +// - error: nil if success otherwise the specific error + +func (c *Client) GetEipTp(id string) (*EipTpDetail, error) { + if len(id) == 0 { + return nil, fmt.Errorf("please set eiptp id argment") + } + result := &EipTpDetail{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipTpUriWithId(id)). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/eip/eipbp.go b/bce-sdk-go/services/eip/eipbp.go new file mode 100644 index 0000000..4c0bc25 --- /dev/null +++ b/bce-sdk-go/services/eip/eipbp.go @@ -0,0 +1,188 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 eip + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateEipBp - create an EIP BP with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eipbp +// +// RETURNS: +// - *CreateEipBpResult: the result of create EIP BP, contains new EIP BP's id +// - error: nil if success otherwise the specific error +func (c *Client) CreateEipBp(args *CreateEipBpArgs) (*CreateEipBpResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create eipbp argments") + } + + result := &CreateEipBpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getEipBpUrl()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ResizeEIpBp - resize an EIP BP with the specific parameters +// +// PARAMS: +// - id: the id of EIP BP +// - args: the arguments to resize an EIP BP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeEipBp(id string, args *ResizeEipBpArgs) error { + if args == nil { + return fmt.Errorf("please set resize eipbp argments") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipBpUrlWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + WithBody(args). + Do() + + return err +} + +// GetEipBp - get the EIP BP detail with the id +// +// PARAMS: +// - id: the specific eipbp id +// - clientToken: the specific client token +// +// RETURNS: +// - *EipBpDetail: the result of eipbp detail +// - error: nil if success otherwise the specific error +func (c *Client) GetEipBp(id, clientToken string) (*EipBpDetail, error) { + + result := &EipBpDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipBpUrlWithId(id)). + WithQueryParamFilter("clientToken", clientToken). + WithResult(result). + Do() + + return result, err +} + +// ListEipBp - list all EIP BP with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eipBp +// +// RETURNS: +// - *EipBpListResult: the result of list all eipBp +// - error: nil if success otherwise the specific error +func (c *Client) ListEipBp(args *ListEipBpArgs) (*ListEipBpResult, error) { + if args == nil { + return nil, fmt.Errorf("please set list eipbp argments") + } + + result := &ListEipBpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipBpUrl()). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParamFilter("id", args.Id). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("bindType", args.BindType). + WithResult(result). + Do() + + return result, err +} + +// UpdateEipBpAutoReleaseTime - update the auto release time of an EIP BP +// +// PARAMS: +// - id: the specific eipbp id +// - args: the arguments to update eipbp auto release time +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEipBpAutoReleaseTime(id string, args *UpdateEipBpAutoReleaseTimeArgs) error { + if args == nil { + return fmt.Errorf("please set update eipbp autoReleaseTime argments") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipBpUrlWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("retime", ""). + WithBody(args). + Do() + + return err +} + +// UpdateEipBpName - update the Name of an EIP BP +// +// PARAMS: +// - id: the specific eipbp id +// - args: the arguments to update eipbp name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEipBpName(id string, args *UpdateEipBpNameArgs) error { + if args == nil { + return fmt.Errorf("please set update eipbp name argments") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipBpUrlWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("rename", ""). + WithBody(args). + Do() + + return err +} + +// DeleteEipBp - delete an EIP BP with the specific id +// +// PARAMS: +// - id: the specific eipbp id +// - clientToken: the specific client token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEipBp(id, clientToken string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getEipBpUrlWithId(id)). + WithQueryParamFilter("clientToken", clientToken). + Do() + + return err +} diff --git a/bce-sdk-go/services/eip/eipgroup.go b/bce-sdk-go/services/eip/eipgroup.go new file mode 100644 index 0000000..643c451 --- /dev/null +++ b/bce-sdk-go/services/eip/eipgroup.go @@ -0,0 +1,273 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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 eip + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateEipGroup - create an EIP_GROUP with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eipGroup +// +// RETURNS: +// - *CreateEipGroupResult: the result of create EIP_GROUP, contains new EIP_GROUP's id +// - error: nil if success otherwise the specific error +func (c *Client) CreateEipGroup(args *CreateEipGroupArgs) (*CreateEipGroupResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create eip argments") + } + + if args.Billing == nil { + return nil, fmt.Errorf("please set billing") + } + + result := &CreateEipGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getEipGroupUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ResizeEipGroupBandWidth - resize an EIP_GROUP with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to resize an EIP_GROUP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeEipGroupBandWidth(id string, args *ResizeEipGroupArgs) error { + if args == nil { + return fmt.Errorf("please set resize argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + WithBody(args). + Do() +} + +// EipGroupAddEipCount - increase EIP_GROUP's ip count with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to increase EIP_GROUP's ip count +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EipGroupAddEipCount(id string, args *GroupAddEipCountArgs) error { + if args == nil { + return fmt.Errorf("please set resize argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + WithBody(args). + Do() +} + +// ReleaseEipGroupIps - release EIP_GROUP's ips with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to release EIP_GROUP's ips +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReleaseEipGroupIps(id string, args *ReleaseEipGroupIpsArgs) error { + if args == nil { + return fmt.Errorf("please set resize argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + WithBody(args). + Do() +} + +// RenameEipGroup - rename EIP_GROUP's name with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to rename EIP_GROUP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameEipGroup(id string, args *RenameEipGroupArgs) error { + if args == nil { + return fmt.Errorf("please set rename argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("update", ""). + WithBody(args). + Do() +} + +// DeleteEipGroup - delete an EIP_GROUP +// +// PARAMS: +// - id: the specific EIP_GROUP's id +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEipGroup(id, clientToken string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ListEipGroup - list all EIP_GROUP with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eipGroup +// +// RETURNS: +// - *ListEipGroupResult: the result of list all eipGroup +// - error: nil if success otherwise the specific error +func (c *Client) ListEipGroup(args *ListEipGroupArgs) (*ListEipGroupResult, error) { + if args == nil { + args = &ListEipGroupArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListEipGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipGroupUri()). + WithQueryParamFilter("id", args.Id). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("status", args.Status). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// EipGroupDetail - get EIP_GROUP detail +// +// PARAMS: +// - id: the eipGroup's id +// +// RETURNS: +// - *EipGroupModel: the result of list all eip in the recycle bin +// - error: nil if success otherwise the specific error +func (c *Client) EipGroupDetail(id string) (*EipGroupModel, error) { + result := &EipGroupModel{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getEipGroupUriWithId(id)). + WithResult(result). + Do() + + return result, err +} + +// EipGroupMoveOut - move eips out of EIP_GROUP with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to move out EIP_GROUP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EipGroupMoveOut(id string, args *EipGroupMoveOutArgs) error { + if args == nil { + return fmt.Errorf("please set argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("move_out", ""). + WithBody(args). + Do() +} + +// EipGroupMoveIn - move eips into to EIP_GROUP with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to move in EIP_GROUP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EipGroupMoveIn(id string, args *EipGroupMoveInArgs) error { + if args == nil { + return fmt.Errorf("please set argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("move_in", ""). + WithBody(args). + Do() +} + +// EipGroupPurchaseReserved - purchase reserved EIP_GROUP with the specific parameters +// +// PARAMS: +// - id: the eipGroup's id +// - args: the arguments to purchase reserved EIP_GROUP +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EipGroupPurchaseReserved(id string, args *EipGroupPurchaseReservedArgs) error { + if args == nil { + return fmt.Errorf("please set argments") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getEipGroupUriWithId(id)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("purchaseReserved", ""). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/eip/model.go b/bce-sdk-go/services/eip/model.go new file mode 100644 index 0000000..952019a --- /dev/null +++ b/bce-sdk-go/services/eip/model.go @@ -0,0 +1,393 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model +package eip + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type Reservation struct { + ReservationLength int `json:"reservationLength,omitempty"` + ReservationTimeUnit string `json:"reservationTimeUnit,omitempty"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming,omitempty"` + BillingMethod string `json:"billingMethod,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type CreateEipArgs struct { + Name string `json:"name,omitempty"` + BandWidthInMbps int `json:"bandwidthInMbps"` + Billing *Billing `json:"billing"` + Tags []model.TagModel `json:"tags"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + RouteType string `json:"routeType,omitempty"` + Idc string `json:"idc,omitempty"` + ClientToken string `json:"-"` +} + +type BatchCreateEipArgs struct { + Name string `json:"name,omitempty"` + BandWidthInMbps int `json:"bandwidthInMbps"` + Billing *Billing `json:"billing"` + Tags []model.TagModel `json:"tags"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + RouteType string `json:"routeType,omitempty"` + Idc string `json:"idc,omitempty"` + Continuous bool `json:"continuous,omitempty"` + Count int `json:"count,omitempty"` + ClientToken string `json:"-"` +} + +type CreateEipResult struct { + Eip string `json:"eip"` +} + +type BatchCreateEipResult struct { + Eips []string `json:"eips"` +} + +type ResizeEipArgs struct { + NewBandWidthInMbps int `json:"newBandwidthInMbps"` + ClientToken string `json:"-"` +} + +type BindEipArgs struct { + InstanceType string `json:"instanceType"` + InstanceId string `json:"instanceId"` + ClientToken string `json:"-"` +} + +type ListEipArgs struct { + Eip string + InstanceType string + InstanceId string + Marker string + MaxKeys int + Status string +} + +type ListEipResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + EipList []EipModel `json:"eipList"` +} + +type EipModel struct { + Name string `json:"name"` + Eip string `json:"eip"` + EipId string `json:"eipId"` + Status string `json:"status"` + EipInstanceType string `json:"eipInstanceType"` + InstanceType string `json:"instanceType"` + InstanceId string `json:"instanceId"` + ShareGroupId string `json:"shareGroupId"` + ClusterId string `json:"clusterId"` + BandWidthInMbps int `json:"bandwidthInMbps"` + PaymentTiming string `json:"paymentTiming"` + BillingMethod string `json:"billingMethod"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + Tags []model.TagModel `json:"tags"` +} + +type ListRecycleEipArgs struct { + Eip string + Name string + Marker string + MaxKeys int +} + +type ListRecycleEipResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + EipList []RecycleEipModel `json:"eipList"` +} + +type RecycleEipModel struct { + Name string `json:"name"` + Eip string `json:"eip"` + EipId string `json:"eipId"` + Status string `json:"status"` + RouteType string `json:"routeType"` + BandWidthInMbps int `json:"bandwidthInMbps"` + PaymentTiming string `json:"paymentTiming"` + BillingMethod string `json:"billingMethod"` + RecycleTime string `json:"recycleTime"` + ScheduledDeleteTime string `json:"scheduledDeleteTime"` +} + +type PurchaseReservedEipArgs struct { + Billing *Billing `json:"billing"` + ClientToken string `json:"clientToken"` +} + +type StartAutoRenewArgs struct { + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + ClientToken string `json:"-"` +} + +type ListClusterResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + ClusterList []ClusterModel `json:"clusterList"` +} + +type ClusterModel struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + ClusterRegion string `json:"clusterRegion"` + ClusterAz string `json:"clusterAz"` +} + +type ClusterDetail struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + ClusterRegion string `json:"clusterRegion"` + ClusterAz string `json:"clusterAz"` + NetworkInBps int64 `json:"networkInBps"` + NetworkOutBps int64 `json:"networkOutBps"` + NetworkInPps int64 `json:"networkInPps"` + NetworkOutPps int64 `json:"networkOutPps"` +} + +type Package struct { + Id string `json:"id,omitempty"` + DeductPolicy string `json:"deductPolicy,omitempty"` + PackageType string `json:"packageType,omitempty"` + Status string `json:"status,omitempty"` + Capacity string `json:"capacity,omitempty"` + UsedCapacity string `json:"usedCapacity,omitempty"` + ActiveTime string `json:"activeTime"` + ExpireTime string `json:"expireTime"` + CreateTime string `json:"createTime"` +} + +type CreateEipTpArgs struct { + ReservationLength int `json:"reservationLength,omitempty"` + Capacity string `json:"capacity,omitempty"` + DeductPolicy string `json:"deductPolicy,omitempty"` + PackageType string `json:"packageType,omitempty"` + ClientToken string `json:"-"` +} + +type CreateEipTpResult struct { + Id string `json:"id,omitempty"` +} + +type ListEipTpArgs struct { + Id string `json:"id,omitempty"` + DeductPolicy string `json:"deductPolicy,omitempty"` + Status string `json:"status,omitempty"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListEipTpResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + PackageList []Package `json:"packageList"` +} + +type EipTpDetail struct { + Id string `json:"id,omitempty"` + DeductPolicy string `json:"deductPolicy,omitempty"` + PackageType string `json:"packageType,omitempty"` + Status string `json:"status,omitempty"` + Capacity string `json:"capacity,omitempty"` + UsedCapacity string `json:"usedCapacity,omitempty"` + ActiveTime string `json:"activeTime,omitempty"` + ExpireTime string `json:"expireTime,omitempty"` + CreateTime string `json:"createTime,omitempty"` +} + +type CreateEipGroupArgs struct { + Name string `json:"name,omitempty"` + EipCount int `json:"eipCount"` + BandWidthInMbps int `json:"bandwidthInMbps"` + Billing *Billing `json:"billing"` + Tags []model.TagModel `json:"tags"` + RouteType string `json:"routeType,omitempty"` + Idc string `json:"idc,omitempty"` + Continuous bool `json:"continuous,omitempty"` + ClientToken string `json:"-"` +} + +type CreateEipGroupResult struct { + Id string `json:"id"` +} + +type ResizeEipGroupArgs struct { + BandWidthInMbps int `json:"bandwidthInMbps"` + ClientToken string `json:"-"` +} + +type GroupAddEipCountArgs struct { + EipAddCount int `json:"eipAddCount"` + ClientToken string `json:"-"` +} + +type ReleaseEipGroupIpsArgs struct { + ReleaseIps []string `json:"releaseIps"` + ClientToken string `json:"-"` +} + +type RenameEipGroupArgs struct { + Name string `json:"name"` + ClientToken string `json:"-"` +} + +type ListEipGroupArgs struct { + Id string + Name string + Marker string + MaxKeys int + Status string +} + +type ListEipGroupResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + EipGroup []EipGroupModel `json:"eipgroups"` +} + +type EipGroupModel struct { + Name string `json:"name"` + Status string `json:"status"` + Id string `json:"id"` + BandWidthInMbps int `json:"bandwidthInMbps"` + DefaultDomesticBandwidth int `json:"defaultDomesticBandwidth"` + BwShortId string `json:"bwShortId"` + BwBandwidthInMbps int `json:"bwBandwidthInMbps"` + DomesticBwShortId string `json:"domesticBwShortId"` + DomesticBwBandwidthInMbps int `json:"domesticBwBandwidthInMbps"` + PaymentTiming string `json:"paymentTiming"` + BillingMethod string `json:"billingMethod"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + Region string `json:"region"` + RouteType string `json:"routeType"` + Eips []EipModel `json:"eips"` +} + +type EipGroupMoveOutArgs struct { + MoveOutEips []MoveOutEip `json:"moveOutEips"` + ClientToken string `json:"-"` +} + +type MoveOutEip struct { + Eip string `json:"eip"` + BandWidthInMbps int `json:"bandwidthInMbps"` + Billing *Billing `json:"billing"` +} + +type EipGroupMoveInArgs struct { + Eips []string `json:"eips"` + ClientToken string `json:"-"` +} + +type EipGroupPurchaseReservedArgs struct { + Billing *Billing `json:"billing"` + ClientToken string `json:"-"` +} + +type CreateEipBpArgs struct { + Name string `json:"name"` + Eip string `json:"eip"` + EipGroupId string `json:"eipGroupId"` + BandwidthInMbps int `json:"bandwidthInMbps"` + Type string `json:"type"` + AutoReleaseTime string `json:"autoReleaseTime"` + ClientToken string `json:"-"` +} + +type CreateEipBpResult struct { + Id string `json:"id"` +} + +type ResizeEipBpArgs struct { + BandwidthInMbps int `json:"bandwidthInMbps"` + ClientToken string `json:"-"` +} + +type EipBpDetail struct { + Name string `json:"name"` + Id string `json:"id"` + BindType string `json:"bindType"` + BandwidthInMbps int `json:"bandwidthInMbps"` + InstanceId string `json:"instanceId"` + Eips []string `json:"eips"` + InstanceBandwidthInMbps int `json:"instanceBandwidthInMbps"` + CreateTime string `json:"createTime"` + AutoReleaseTime string `json:"autoReleaseTime"` + Type string `json:"type"` + Region string `json:"region"` +} + +type ListEipBpArgs struct { + Id string `json:"id"` + Name string `json:"name"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + BindType string `json:"bindType"` + Type string `json:"type"` +} + +type ListEipBpResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + EipGroup []EipBpList `json:"bpList"` +} + +type EipBpList struct { + Name string `json:"name"` + Id string `json:"id"` + BindType string `json:"bindType"` + BandwidthInMbps int `json:"bandwidthInMbps"` + InstanceId string `json:"instanceId"` + Eips []string `json:"eips"` + CreateTime string `json:"createTime"` + AutoReleaseTime string `json:"autoReleaseTime"` + Type string `json:"type"` + Region string `json:"region"` +} + +type UpdateEipBpAutoReleaseTimeArgs struct { + AutoReleaseTime string `json:"autoReleaseTime"` + ClientToken string `json:"-"` +} + +type UpdateEipBpNameArgs struct { + Name string `json:"name"` + ClientToken string `json:"-"` +} diff --git a/bce-sdk-go/services/endpoint/client.go b/bce-sdk-go/services/endpoint/client.go new file mode 100644 index 0000000..c928435 --- /dev/null +++ b/bce-sdk-go/services/endpoint/client.go @@ -0,0 +1,51 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 endpoint + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_ENDPOINT_URL = "/endpoint" +) + +// Client of VPC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForEndpoint() string { + return URI_PREFIX + REQUEST_ENDPOINT_URL +} + +func getURLForEndpointId(endpointId string) string { + return getURLForEndpoint() + "/" + endpointId +} diff --git a/bce-sdk-go/services/endpoint/client_test.go b/bce-sdk-go/services/endpoint/client_test.go new file mode 100644 index 0000000..483f645 --- /dev/null +++ b/bce-sdk-go/services/endpoint/client_test.go @@ -0,0 +1,175 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 endpoint + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + ENDPOINT_CLIENT *Client + + region string + + EndpointId string +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + VPCEndpoint string `json:"Endpoint"` +} + +const ( + PAYMENT_TIMING_POSTPAID PaymentTimingType = "Postpaid" + + VPC_ID string = "vpc-q1hcnhf7nmve" + SUBNET_ID string = "sbn-crqu2vxzj049" + SERVICE string = "77.uservice-a7f5795b.beijing.baidubce.com" +) + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + ENDPOINT_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.VPCEndpoint) + + region = confObj.VPCEndpoint[4:6] + +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_GetService(t *testing.T) { + result, err := ENDPOINT_CLIENT.GetServices() + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_CreateEndpoint(t *testing.T) { + args := &CreateEndpointArgs{ + VpcId: VPC_ID, + Name: "go-sdk-create-1", + SubnetId: SUBNET_ID, + Service: "77.uservice-a7f5795b.beijing.baidubce.com", + Description: "go sdk test", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + ClientToken: getClientToken(), + } + result, err := ENDPOINT_CLIENT.CreateEndpoint(args) + ExpectEqual(t.Errorf, nil, err) + EndpointId := result.Id + log.Debug(EndpointId) +} + +func TestClient_DeleteEndpoint(t *testing.T) { + err := ENDPOINT_CLIENT.DeleteEndpoint("endpoint-f0f191f0", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEndpoint(t *testing.T) { + args := &UpdateEndpointArgs{ + ClientToken: getClientToken(), + Name: "go-sdk-2", + Description: "go sdk 2", + } + err := ENDPOINT_CLIENT.UpdateEndpoint("endpoint-3c7e02cb", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEndpoints(t *testing.T) { + args := &ListEndpointArgs{ + VpcId: VPC_ID, + SubnetId: SUBNET_ID, + } + res, err := ENDPOINT_CLIENT.ListEndpoints(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestClient_GetEndpointDetail(t *testing.T) { + result, err := ENDPOINT_CLIENT.GetEndpointDetail("endpoint-3c7e02cb") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} +func TestClient_UpdateEndpointNSG(t *testing.T) { + args := &UpdateEndpointNSGArgs{ + SecurityGroupIds: []string{"g-wmxijt06y5um"}, + } + err := ENDPOINT_CLIENT.UpdateEndpointNormalSecurityGroup("endpoint-3c7e02cb", args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_UpdateEndpointESG(t *testing.T) { + args := &UpdateEndpointESGArgs{ + EnterpriseSecurityGroupIds: []string{"esg-nhwrebdqi4q2"}, + } + err := ENDPOINT_CLIENT.UpdateEndpointEnterpriseSecurityGroup("endpoint-3c7e02cb", args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/endpoint/endpoint.go b/bce-sdk-go/services/endpoint/endpoint.go new file mode 100644 index 0000000..3e772f3 --- /dev/null +++ b/bce-sdk-go/services/endpoint/endpoint.go @@ -0,0 +1,198 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// endpoint.go - the endpoint APIs definition supported by the endpoint service +package endpoint + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// GetServices - get the public services +// RETURNS: +// - *ListServiceResult: the result of public service +// - error: nil if success otherwise the specific error +func (c *Client) GetServices() (*ListServiceResult, error) { + result := &ListServiceResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEndpoint() + "/publicService"). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// CreateEndpoint - create an endpoint with the specific parameters +// +// PARAMS: +// - args: the arguments to create an endpoint +// +// RETURNS: +// - *CreateEndpointResult: the result of create endpoint +// - error: nil if success otherwise the specific error +func (c *Client) CreateEndpoint(args *CreateEndpointArgs) (*CreateEndpointResult, error) { + if args == nil { + return nil, fmt.Errorf("The createEndpointArgs cannot be nil.") + } + + result := &CreateEndpointResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEndpoint()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// DeleteEndpoint - delete an endpoint +// +// PARAMS: +// - endpointId: the specific endpointId +// - clientToken: optional parameter, an Idempotent Token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEndpoint(endpointId string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEndpointId(endpointId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// UpdateEndpoint - update an endpoint +// +// PARAMS: +// - endpointId: the specific endpointId +// - UpdateEndpointArgs: the arguments to update an endpoint +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEndpoint(endpointId string, args *UpdateEndpointArgs) error { + if args == nil { + return fmt.Errorf("The updateEndpointArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEndpointId(endpointId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// ListEndpoints - list all endpoint with the specific parameters +// +// PARAMS: +// - args: the arguments to list all endpoint +// +// RETURNS: +// - *ListEndpointResult: the result of list all endpoint +// - error: nil if success otherwise the specific error +func (c *Client) ListEndpoints(args *ListEndpointArgs) (*ListEndpointResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListEndpointArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListEndpointResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEndpoint()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithQueryParam("name", args.Name). + WithQueryParam("ipAddress", args.IpAddress). + WithQueryParam("status", args.Status). + WithQueryParam("subnetId", args.SubnetId). + WithQueryParam("service", args.Service). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetEndpointDetail - get the endpoint detail +// +// PARAMS: +// - endpointId: the specific endpointId +// +// RETURNS: +// - *Endpoint: the endpoint +// - error: nil if success otherwise the specific error +func (c *Client) GetEndpointDetail(endpointId string) (*Endpoint, error) { + result := &Endpoint{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEndpointId(endpointId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateEndpointNormalSecurityGroup - update normal security group bound to the endpoint +// +// PARAMS: +// - endpointId: the specific endpointId +// - args: the arguments to update a normal security group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEndpointNormalSecurityGroup(endpointId string, args *UpdateEndpointNSGArgs) error { + if args == nil { + return fmt.Errorf("The UpdateEndpointNSGArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEndpointId(endpointId)). + WithMethod(http.PUT). + WithQueryParam("bindSg", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// UpdateEndpointEnterpriseSecurityGroup - update enterprise security group bound to the endpoint +// +// PARAMS: +// - endpointId: the specific endpointId +// - args: the arguments to update an enterprise security group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEndpointEnterpriseSecurityGroup(endpointId string, args *UpdateEndpointESGArgs) error { + if args == nil { + return fmt.Errorf("The UpdateEndpointESGArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEndpointId(endpointId)). + WithMethod(http.PUT). + WithQueryParam("bindEsg", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/endpoint/model.go b/bce-sdk-go/services/endpoint/model.go new file mode 100644 index 0000000..7b26f88 --- /dev/null +++ b/bce-sdk-go/services/endpoint/model.go @@ -0,0 +1,96 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 endpoint + +type Endpoint struct { + EndpointId string `json:"endpointId"` + Name string `json:"name"` + IpAddress string `json:"ipAddress"` + Status string `json:"status"` + Service string `json:"service"` + SubnetId string `json:"subnetId"` + Description string `json:"description"` + CreateTime string `json:"createTime"` + VpcId string `json:"vpcId"` + ProductType string `json:"productType"` +} + +type ListEndpointArgs struct { + VpcId string + Name string + IpAddress string + Status string + SubnetId string + Service string + Marker string + MaxKeys int +} + +type ListEndpointResult struct { + Endpoints []Endpoint `json:"endpoints"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type ListServiceResult struct { + Services []string `json:"services"` +} + +type UpdateEndpointArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` +} + +type UpdateEndpointNSGArgs struct { + SecurityGroupIds []string `json:"securityGroupIds"` + ClientToken string `json:"-"` +} +type UpdateEndpointESGArgs struct { + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` + ClientToken string `json:"-"` +} + +type CreateEndpointArgs struct { + ClientToken string `json:"-"` + VpcId string `json:"vpcId"` + Name string `json:"name"` + SubnetId string `json:"subnetId"` + Service string `json:"service"` + Description string `json:"description,omitempty"` + IpAddress string `json:"ipAddress,omitempty"` + Billing *Billing `json:"billing"` +} + +type CreateEndpointResult struct { + Id string `json:"id"` + IpAddress string `json:"ipAddress"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type ( + PaymentTimingType string +) + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} diff --git a/bce-sdk-go/services/eni/client.go b/bce-sdk-go/services/eni/client.go new file mode 100644 index 0000000..cb801eb --- /dev/null +++ b/bce-sdk-go/services/eni/client.go @@ -0,0 +1,51 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 eni + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENI = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_ENI_URL = "/eni" +) + +// Client of ENI service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENI + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForEni() string { + return URI_PREFIX + REQUEST_ENI_URL +} + +func getURLForEniId(eniId string) string { + return getURLForEni() + "/" + eniId +} diff --git a/bce-sdk-go/services/eni/client_test.go b/bce-sdk-go/services/eni/client_test.go new file mode 100644 index 0000000..8fb4532 --- /dev/null +++ b/bce-sdk-go/services/eni/client_test.go @@ -0,0 +1,310 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 eni + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + ENI_CLIENT *Client + region string +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +const ( + VPC_ID string = "vpc-q1hcnhf7nmve" + SUBNET_ID string = "sbn-cxwi5hmf8ugx" +) + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + ENI_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + region = confObj.Endpoint[4:6] + +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateEni(t *testing.T) { + args := &CreateEniArgs{ + Name: "GO_SDK_TEST_CREATE", + SubnetId: "sbn-56s25qecigix", + EnterpriseSecurityGroupIds: []string{ + "esg-rn49gxbin4x7", + }, + PrivateIpSet: []PrivateIp{ + { + Primary: true, + PrivateIpAddress: "", + }, + }, + Ipv6PrivateIpSet: []PrivateIp{ + { + Primary: false, + PrivateIpAddress: "2400:da00:e003:0:1d2:100:0:15", + }, + }, + Description: "go sdk test", + ClientToken: getClientToken(), + } + result, err := ENI_CLIENT.CreateEni(args) + ExpectEqual(t.Errorf, nil, err) + EniId := result.EniId + log.Debug(EniId) +} + +func TestClient_UpdateEni(t *testing.T) { + args := &UpdateEniArgs{ + EniId: "eni-mmwvvbvfjch3", + ClientToken: getClientToken(), + Name: "hzb_3_1", + Description: "go sdk 2", + } + err := ENI_CLIENT.UpdateEni(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteEni(t *testing.T) { + args := &DeleteEniArgs{ + EniId: "eni-darynwwu5xk0", + ClientToken: getClientToken(), + } + err := ENI_CLIENT.DeleteEni(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEnis(t *testing.T) { + args := &ListEniArgs{ + VpcId: VPC_ID, + //InstanceId: "i-FodWXDUY", + Name: "eni", + } + res, err := ENI_CLIENT.ListEni(args) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestClient_GetEniDetail(t *testing.T) { + result, err := ENI_CLIENT.GetEniDetail("eni-wbi6thg2p6vj") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_AddPrivateIp(t *testing.T) { + args := &EniPrivateIpArgs{ + EniId: "eni-6c6b8dt4m8rt", + ClientToken: getClientToken(), + PrivateIpAddress: "192.168.0.32", + } + result, err := ENI_CLIENT.AddPrivateIp(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_BatchAddPrivateIp(t *testing.T) { + args := &EniBatchPrivateIpArgs{ + EniId: "eni-gay686fr93e3", + ClientToken: getClientToken(), + PrivateIpAddresses: []string{ + "192.168.0.28", + "192.168.0.29", + }, + } + result, err := ENI_CLIENT.BatchAddPrivateIp(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_BatchAddPrivateIpCrossSubnet(t *testing.T) { + args := &EniBatchAddPrivateIpCrossSubnetArgs{ + EniId: "eni-gay686fr93e3", + ClientToken: getClientToken(), + SubnetId: "sbn-5vhut3bxmumi", + PrivateIpAddressCount: 2, + } + result, err := ENI_CLIENT.BatchAddPrivateIpCrossSubnet(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_DeletePrivateIp(t *testing.T) { + args := &EniPrivateIpArgs{ + EniId: "eni-6c6b8dt4m8rt", + ClientToken: getClientToken(), + PrivateIpAddress: "192.168.0.32", + } + err := ENI_CLIENT.DeletePrivateIp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BatchDeletePrivateIp(t *testing.T) { + args := &EniBatchPrivateIpArgs{ + EniId: "eni-6c6b8dt4m8rt", + ClientToken: getClientToken(), + PrivateIpAddresses: []string{ + "192.168.0.34", + "192.168.0.35", + }, + } + err := ENI_CLIENT.BatchDeletePrivateIp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AttachEniInstance(t *testing.T) { + args := &EniInstance{ + EniId: "eni-pw4tqi2f9gvq", + ClientToken: getClientToken(), + InstanceId: "i-KdT8ptiu", + } + err := ENI_CLIENT.AttachEniInstance(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DetachEniInstance(t *testing.T) { + args := &EniInstance{ + EniId: "eni-pw4tqi2f9gvq", + ClientToken: getClientToken(), + InstanceId: "i-KdT8ptiu", + } + err := ENI_CLIENT.DetachEniInstance(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindEniPublicIp(t *testing.T) { + args := &BindEniPublicIpArgs{ + EniId: "eni-mmwvvbvfjch3", + ClientToken: getClientToken(), + PrivateIpAddress: "192.168.0.54", + PublicIpAddress: "100.88.8.55", + } + err := ENI_CLIENT.BindEniPublicIp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnBindEniPublicIp(t *testing.T) { + args := &UnBindEniPublicIpArgs{ + EniId: "eni-mmwvvbvfjch3", + ClientToken: getClientToken(), + PublicIpAddress: "100.88.8.55", + } + err := ENI_CLIENT.UnBindEniPublicIp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEniSecurityGroup(t *testing.T) { + args := &UpdateEniSecurityGroupArgs{ + EniId: "eni-mmwvvbvfjch3", + ClientToken: getClientToken(), + SecurityGroupIds: []string{ + "g-eqhqsbs84yww", + "g-8bfifey0s4h3", + }, + } + err := ENI_CLIENT.UpdateEniSecurityGroup(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEniEnterpriseSecurityGroup(t *testing.T) { + args := &UpdateEniEnterpriseSecurityGroupArgs{ + EniId: "eni-7c9yzhkfn9c2", + ClientToken: getClientToken(), + EnterpriseSecurityGroupIds: []string{ + "esg-e28mnj5vbrv5", + }, + } + err := ENI_CLIENT.UpdateEniEnterpriseSecurityGroup(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetEniIPQuota(t *testing.T) { + args := &EniQuoteArgs{EniId: "eni-bd1ivf6ui4di"} + result, err := ENI_CLIENT.GetEniQuota(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_GetEniBindQuota(t *testing.T) { + args := &EniQuoteArgs{InstanceId: "i-0v5XVO8M"} + result, err := ENI_CLIENT.GetEniQuota(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} diff --git a/bce-sdk-go/services/eni/config.json b/bce-sdk-go/services/eni/config.json new file mode 100644 index 0000000..5a6634f --- /dev/null +++ b/bce-sdk-go/services/eni/config.json @@ -0,0 +1,5 @@ +{ + "AK":"", + "SK":"", + "Endpoint":"" +} diff --git a/bce-sdk-go/services/eni/eni.go b/bce-sdk-go/services/eni/eni.go new file mode 100644 index 0000000..030b5ee --- /dev/null +++ b/bce-sdk-go/services/eni/eni.go @@ -0,0 +1,440 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// eni.go - the eni APIs definition supported by the eni service +package eni + +import ( + "fmt" + "strconv" + "strings" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateEni - create an eni with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eni +// +// RETURNS: +// - *CreateEniResult: the result of create eni +// - error: nil if success otherwise the specific error +func (c *Client) CreateEni(args *CreateEniArgs) (*CreateEniResult, error) { + if args == nil { + return nil, fmt.Errorf("The createEniArgs cannot be nil.") + } + + result := &CreateEniResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEni()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// UpdateEni - update an eni +// +// PARAMS: +// - UpdateEniArgs: the arguments to update an eni +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEni(args *UpdateEniArgs) error { + if args == nil { + return fmt.Errorf("The updateEniArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("modifyAttribute", ""). + Do() +} + +// DeleteEni - delete an eni +// +// PARAMS: +// - DeleteEniArgs: the arguments to delete an eni +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEni(args *DeleteEniArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// ListEnis - list all eni with the specific parameters +// +// PARAMS: +// - args: the arguments to list all eni +// +// RETURNS: +// - *ListEniResult: the result of list all eni +// - error: nil if success otherwise the specific error +func (c *Client) ListEni(args *ListEniArgs) (*ListEniResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListEniArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListEniResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForEni()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithQueryParamFilter("instanceId", args.InstanceId). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + + if len(args.PrivateIpAddress) != 0 { + builder.WithQueryParam("privateIpAddress", + strings.Replace(strings.Trim(fmt.Sprint(args.PrivateIpAddress), "[]"), " ", ",", -1)) + } + + err := builder.WithResult(result).Do() + + return result, err +} + +// GetEniDetail - get the eni detail +// +// PARAMS: +// - eniId: the specific eniId +// +// RETURNS: +// - *Eni: the eni +// - error: nil if success otherwise the specific error +func (c *Client) GetEniDetail(eniId string) (*Eni, error) { + if eniId == "" { + return nil, fmt.Errorf("The eniId cannot be empty.") + } + + result := &Eni{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(eniId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// AddPrivateIp - add private ip +// +// PARAMS: +// - args: the arguments to add private ip +// +// RETURNS: +// - *AddPrivateIpResult: the private ip +// - error: nil if success otherwise the specific error +func (c *Client) AddPrivateIp(args *EniPrivateIpArgs) (*AddPrivateIpResult, error) { + if args == nil { + return nil, fmt.Errorf("The EniPrivateIpArgs cannot be nil.") + } + + result := &AddPrivateIpResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)+"/privateIp"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// BatchAddPrivateIp - batch add private ips +// +// PARAMS: +// - args: the arguments to batch add private ips, property PrivateIpAddresses or PrivateIpAddressCount is required; +// when PrivateIpAddressCount is set, private ips will be auto allocated, +// and if you want assign private ips, please just set PrivateIpAddresses; +// +// RETURNS: +// - *BatchAddPrivateIpResult: the private ips +// - error: nil if success otherwise the specific error +func (c *Client) BatchAddPrivateIp(args *EniBatchPrivateIpArgs) (*BatchAddPrivateIpResult, error) { + if args == nil { + return nil, fmt.Errorf("The EniBatchPrivateIpArgs cannot be nil.") + } + + result := &BatchAddPrivateIpResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)+"/privateIp/batchAdd"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// BatchAddPrivateIpCrossSubnet - batch add private ips that support cross subnet, white list function +// +// PARAMS: +// - args: the arguments to batch add private ips, property PrivateIps or PrivateIpAddressCount is required; +// when PrivateIpAddressCount is set, private ips in subnet assigned by 'SubnetId' property will be auto allocated; +// if you want assign private ips, please just set PrivateIps, and you can also assgin subnet with property 'PrivateIpArgs.SubnetId'; +// +// RETURNS: +// - *BatchAddPrivateIpResult: the private ips +// - error: nil if success otherwise the specific error +func (c *Client) BatchAddPrivateIpCrossSubnet(args *EniBatchAddPrivateIpCrossSubnetArgs) (*BatchAddPrivateIpResult, error) { + if args == nil { + return nil, fmt.Errorf("The EniBatchAddPrivateIpCrossSubnetArgs cannot be nil.") + } + + result := &BatchAddPrivateIpResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)+"/privateIp/batchAddCrossSubnet"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// DeletePrivateIp - delete private ip +// +// PARAMS: +// - args: the arguments to delete private ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeletePrivateIp(args *EniPrivateIpArgs) error { + if args == nil { + return fmt.Errorf("The EniPrivateIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)+"/privateIp/"+args.PrivateIpAddress). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// BatchDeletePrivateIp - batch delete private ip +// +// PARAMS: +// - args: the arguments to batch delete private ipa +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchDeletePrivateIp(args *EniBatchPrivateIpArgs) error { + if args == nil { + return fmt.Errorf("The EniBatchPrivateIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)+"/privateIp/batchDel"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// AttachEniInstance - eni attach instance +// +// PARAMS: +// - args: the arguments to attach instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AttachEniInstance(args *EniInstance) error { + if args == nil { + return fmt.Errorf("The EniInstance cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("attach", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// DetachEniInstance - eni detach instance +// +// PARAMS: +// - args: the arguments to detach instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DetachEniInstance(args *EniInstance) error { + if args == nil { + return fmt.Errorf("The EniInstance cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("detach", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// BindEniPublicIp - eni bind public ip +// +// PARAMS: +// - args: the arguments to bind public ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindEniPublicIp(args *BindEniPublicIpArgs) error { + if args == nil { + return fmt.Errorf("The BindEniPublicIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("bind", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// UnBindEniPublicIp - eni unbind public ip +// +// PARAMS: +// - args: the arguments to unbind public ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindEniPublicIp(args *UnBindEniPublicIpArgs) error { + if args == nil { + return fmt.Errorf("The UnBindEniPublicIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("unBind", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// UpdateEniSecurityGroup - update eni sg +// +// PARAMS: +// - args: the arguments to update eni sg +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEniSecurityGroup(args *UpdateEniSecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("The UpdateEniSecurityGroupArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("bindSg", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// UpdateEniEnterpriseSecurityGroup - update eni enterprise security group +// +// PARAMS: +// - args: the arguments to update eni enterprise security group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEniEnterpriseSecurityGroup(args *UpdateEniEnterpriseSecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("The UpdateEniEnterpriseSecurityGroupArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(args.EniId)). + WithMethod(http.PUT). + WithQueryParam("bindEsg", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +func (c *Client) GetEniQuota(args *EniQuoteArgs) (*EniQuoteInfo, error) { + + result := &EniQuoteInfo{} + request := bce.NewRequestBuilder(c). + WithURL(getURLForEni() + "/quota"). + WithMethod(http.GET) + if args.EniId != "" { + request.WithQueryParam("eniId", args.EniId) + } + if args.InstanceId != "" { + request.WithQueryParam("instanceId", args.InstanceId) + } + err := request.WithResult(result).Do() + + return result, err +} + +// GetEniStatus - get an eni status +// +// PARAMS: +// - eniId: the arguments to get an eni status +// +// RETURNS: +// - *EniStatusInfo: the result of get an eni status +// - error: nil if success otherwise the specific error +func (c *Client) GetEniStatus(eniId string) (*EniStatusInfo, error) { + result := &EniStatusInfo{} + request := bce.NewRequestBuilder(c). + WithURL(getURLForEniId(eniId) + "/status"). + WithMethod(http.GET) + err := request.WithResult(result).Do() + + return result, err +} diff --git a/bce-sdk-go/services/eni/model.go b/bce-sdk-go/services/eni/model.go new file mode 100644 index 0000000..b4dc353 --- /dev/null +++ b/bce-sdk-go/services/eni/model.go @@ -0,0 +1,165 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 eni + +type CreateEniArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + SubnetId string `json:"subnetId"` + InstanceId string `json:"instanceId,omitempty"` + SecurityGroupIds []string `json:"securityGroupIds"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` + PrivateIpSet []PrivateIp `json:"privateIpSet"` + Ipv6PrivateIpSet []PrivateIp `json:"ipv6PrivateIpSet,omitempty"` + Description string `json:"description,omitempty"` +} + +type CreateEniResult struct { + EniId string `json:"eniId"` +} + +type UpdateEniArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` +} + +type DeleteEniArgs struct { + EniId string + ClientToken string +} + +type ListEniArgs struct { + VpcId string + InstanceId string + Name string + Marker string + MaxKeys int + PrivateIpAddress []string `json:"privateIpAddress,omitempty"` +} + +type ListEniResult struct { + Eni []Eni `json:"enis"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type Eni struct { + EniId string `json:"eniId"` + Name string `json:"name"` + ZoneName string `json:"zoneName"` + Description string `json:"description"` + InstanceId string `json:"instanceId"` + MacAddress string `json:"macAddress"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Status string `json:"status"` + PrivateIpSet []PrivateIp `json:"privateIpSet"` + Ipv6PrivateIpSet []PrivateIp `json:"ipv6PrivateIpSet"` + SecurityGroupIds []string `json:"securityGroupIds"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` + CreatedTime string `json:"createdTime"` +} + +type PrivateIp struct { + PublicIpAddress string `json:"publicIpAddress"` + Primary bool `json:"primary"` + PrivateIpAddress string `json:"privateIpAddress"` +} + +type EniPrivateIpArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + IsIpv6 bool `json:"isIpv6,omitempty"` + PrivateIpAddress string `json:"privateIpAddress"` +} + +type EniBatchPrivateIpArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + IsIpv6 bool `json:"isIpv6,omitempty"` + PrivateIpAddresses []string `json:"privateIpAddresses"` + PrivateIpAddressCount int `json:"privateIpAddressCount,omitempty"` +} + +type EniBatchAddPrivateIpCrossSubnetArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + SubnetId string `json:"subnetId"` + IsIpv6 bool `json:"isIpv6,omitempty"` + PrivateIps []PrivateIpArgs `json:"privateIps"` + PrivateIpAddressCount int `json:"privateIpAddressCount,omitempty"` +} + +type PrivateIpArgs struct { + PrivateIpAddress string `json:"privateIpAddress"` + SubnetId string `json:"subnetId"` +} + +type AddPrivateIpResult struct { + PrivateIpAddress string `json:"privateIpAddress"` +} + +type BatchAddPrivateIpResult struct { + PrivateIpAddresses []string `json:"privateIpAddresses"` +} + +type EniInstance struct { + EniId string `json:"-"` + InstanceId string `json:"instanceId"` + ClientToken string `json:"-"` +} + +type BindEniPublicIpArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + PrivateIpAddress string `json:"privateIpAddress"` + PublicIpAddress string `json:"publicIpAddress"` +} + +type UnBindEniPublicIpArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + PublicIpAddress string `json:"publicIpAddress"` +} + +type UpdateEniSecurityGroupArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type UpdateEniEnterpriseSecurityGroupArgs struct { + EniId string `json:"-"` + ClientToken string `json:"-"` + EnterpriseSecurityGroupIds []string `json:"enterpriseSecurityGroupIds"` +} + +type EniQuoteArgs struct { + EniId string `json:"-"` + InstanceId string `json:"-"` +} + +type EniQuoteInfo struct { + TotalQuantity int `json:"totalQuantity"` + AvailableQuantity int `json:"availableQuantity"` +} + +type EniStatusInfo struct { + Status string `json:"status"` +} diff --git a/bce-sdk-go/services/esg/client.go b/bce-sdk-go/services/esg/client.go new file mode 100644 index 0000000..3ff48f3 --- /dev/null +++ b/bce-sdk-go/services/esg/client.go @@ -0,0 +1,55 @@ +/* + * Copyright Baidu, Inc. + * + * 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 esg + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENI = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_ENI_URL = "/enterprise/security" +) + +// Client of ESG service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENI + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForEsg() string { + return URI_PREFIX + REQUEST_ENI_URL +} + +func getURLForEsgId(esgId string) string { + return getURLForEsg() + "/" + esgId +} + +func getURLForEsgRuleId(esgRuleId string) string { + return getURLForEsg() + "/rule/" + esgRuleId +} diff --git a/bce-sdk-go/services/esg/client_test.go b/bce-sdk-go/services/esg/client_test.go new file mode 100644 index 0000000..dadb92b --- /dev/null +++ b/bce-sdk-go/services/esg/client_test.go @@ -0,0 +1,191 @@ +/* + * Copyright Baidu, Inc. + * + * 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 esg + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/model" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + ESG_CLIENT *Client + + region string +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + ESG_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + region = confObj.Endpoint[4:6] +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateEsg(t *testing.T) { + args := &CreateEsgArgs{ + Name: "esgGoSdkTest", + Rules: []EnterpriseSecurityGroupRule{ + { + Action: "deny", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "udp", + Remark: "go sdk test", + SourceIp: "all", + }, + { + Action: "allow", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "icmp", + Remark: "go sdk test", + SourceIp: "all", + }, + }, + Desc: "go sdk test", + ClientToken: getClientToken(), + Tags: []model.TagModel{ + { + TagKey: "test", + TagValue: "", + }, + }, + } + result, err := ESG_CLIENT.CreateEsg(args) + ExpectEqual(t.Errorf, nil, err) + EnterpriseSecurityGroupId := result.EnterpriseSecurityGroupId + log.Debug(EnterpriseSecurityGroupId) +} + +func TestClient_ListEsgs(t *testing.T) { + args := &ListEsgArgs{} + res, err := ESG_CLIENT.ListEsg(args) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestClient_DeleteEsg(t *testing.T) { + args := &DeleteEsgArgs{ + EnterpriseSecurityGroupId: "esg-s91awqpw73un", + ClientToken: getClientToken(), + } + err := ESG_CLIENT.DeleteEsg(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateEsgRules(t *testing.T) { + args := &CreateEsgRuleArgs{ + Rules: []EnterpriseSecurityGroupRule{ + { + Action: "deny", + Direction: "ingress", + Ethertype: "IPv4", + PortRange: "1-65535", + Priority: 1000, + Protocol: "udp", + Remark: "go sdk test", + SourceIp: "all", + }, + }, + EnterpriseSecurityGroupId: "esg-v99qnxx7uh83", + ClientToken: getClientToken(), + } + err := ESG_CLIENT.CreateEsgRules(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteEsgRule(t *testing.T) { + args := &DeleteEsgRuleArgs{ + EnterpriseSecurityGroupRuleId: "esgr-ak7b51zzgptc", + } + err := ESG_CLIENT.DeleteEsgRule(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateEsgRule(t *testing.T) { + args := &UpdateEsgRuleArgs{ + Priority: 900, + Remark: "go sdk test update", + ClientToken: getClientToken(), + EnterpriseSecurityGroupRuleId: "esgr-ahm3xxi11s20", + } + err := ESG_CLIENT.UpdateEsgRule(args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/esg/config.json b/bce-sdk-go/services/esg/config.json new file mode 100644 index 0000000..5a6634f --- /dev/null +++ b/bce-sdk-go/services/esg/config.json @@ -0,0 +1,5 @@ +{ + "AK":"", + "SK":"", + "Endpoint":"" +} diff --git a/bce-sdk-go/services/esg/esg.go b/bce-sdk-go/services/esg/esg.go new file mode 100644 index 0000000..0563a5e --- /dev/null +++ b/bce-sdk-go/services/esg/esg.go @@ -0,0 +1,154 @@ +/* + * Copyright Baidu, Inc. + * + * 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 esg + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateEsg - create an esg with the specific parameters +// +// PARAMS: +// - args: the arguments to create an esg +// +// RETURNS: +// - *CreateEsgResult: the result of create esg +// - error: nil if success otherwise the specific error +func (c *Client) CreateEsg(args *CreateEsgArgs) (*CreateEsgResult, error) { + if args == nil { + return nil, fmt.Errorf("The createEsgArgs cannot be nil.") + } + + result := &CreateEsgResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEsg()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListEsg - list all esg with the specific parameters +// +// PARAMS: +// - args: the arguments to list all esg +// +// RETURNS: +// - *ListEsgResult: the result of list all esg +// - error: nil if success otherwise the specific error +func (c *Client) ListEsg(args *ListEsgArgs) (*ListEsgResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListEsgArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListEsgResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForEsg()). + WithMethod(http.GET). + WithQueryParamFilter("instanceId", args.InstanceId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + + err := builder.WithResult(result).Do() + + return result, err +} + +// DeleteEsg - delete an esg +// +// PARAMS: +// - DeleteEsgArgs: the arguments to delete an esg +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEsg(args *DeleteEsgArgs) error { + if args == nil { + return fmt.Errorf("The deleteEsgArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEsgId(args.EnterpriseSecurityGroupId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// CreateEsgRules - create esg rules +// +// PARAMS: +// - CreateEsgRuleArgs: the arguments to create esg rules +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateEsgRules(args *CreateEsgRuleArgs) error { + if args == nil { + return fmt.Errorf("The createEsgRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEsgId(args.EnterpriseSecurityGroupId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("authorizeRule", ""). + Do() +} + +// DeleteEsgRule - delete an esg rule +// +// PARAMS: +// - DeleteEsgArgs: the arguments to delete an esg rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEsgRule(args *DeleteEsgRuleArgs) error { + if args == nil { + return fmt.Errorf("The deleteEsgRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEsgRuleId(args.EnterpriseSecurityGroupRuleId)). + WithMethod(http.DELETE). + Do() +} + +// UpdateEsgRule - update esg rule +// +// PARAMS: +// - CreateEsgRuleArgs: the arguments to update esg rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEsgRule(args *UpdateEsgRuleArgs) error { + if args == nil { + return fmt.Errorf("The updateEsgRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForEsgRuleId(args.EnterpriseSecurityGroupRuleId)). + WithMethod(http.PUT). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/esg/model.go b/bce-sdk-go/services/esg/model.go new file mode 100644 index 0000000..f792856 --- /dev/null +++ b/bce-sdk-go/services/esg/model.go @@ -0,0 +1,94 @@ +/* + * Copyright Baidu, Inc. + * + * 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 esg + +import "github.com/baidubce/bce-sdk-go/model" + +type CreateEsgArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Desc string `json:"desc"` + Rules []EnterpriseSecurityGroupRule `json:"rules"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +type CreateEsgResult struct { + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId"` +} + +type EnterpriseSecurityGroup struct { + Id string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` + Rules []EnterpriseSecurityGroupRule `json:"rules"` + Tags []model.TagModel `json:"tags"` + CreatedTime string `json:"createdTime"` +} + +type EnterpriseSecurityGroupRule struct { + EnterpriseSecurityGroupRuleId string `json:"enterpriseSecurityGroupRuleId"` + Remark string `json:"remark"` + Direction string `json:"direction"` + Ethertype string `json:"ethertype"` + PortRange string `json:"portRange"` + Protocol string `json:"protocol"` + SourceIp string `json:"sourceIp"` + DestIp string `json:"destIp"` + Action string `json:"action"` + Priority int `json:"priority"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} + +type ListEsgArgs struct { + InstanceId string + Marker string + MaxKeys int +} + +type ListEsgResult struct { + EnterpriseSecurityGroups []EnterpriseSecurityGroup `json:"enterpriseSecurityGroups"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type DeleteEsgArgs struct { + EnterpriseSecurityGroupId string + ClientToken string +} + +type CreateEsgRuleArgs struct { + ClientToken string `json:"-"` + EnterpriseSecurityGroupId string `json:"enterpriseSecurityGroupId"` + Rules []EnterpriseSecurityGroupRule `json:"rules"` +} + +type DeleteEsgRuleArgs struct { + EnterpriseSecurityGroupRuleId string +} + +type UpdateEsgRuleArgs struct { + ClientToken string + EnterpriseSecurityGroupRuleId string `json:"-"` + Remark string `json:"remark"` + PortRange string `json:"portRange"` + Protocol string `json:"protocol"` + SourceIp string `json:"sourceIp"` + DestIp string `json:"destIp"` + Action string `json:"action"` + Priority int `json:"priority"` +} diff --git a/bce-sdk-go/services/et/client.go b/bce-sdk-go/services/et/client.go new file mode 100644 index 0000000..c9bb0ed --- /dev/null +++ b/bce-sdk-go/services/et/client.go @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for ET service + +// Package et defines the et services of BCE. +// The supported APIs are all defined in different files. +package et + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_ET_URL = "/et" + REQUEST_ET_CHANNEL_URL = "/channel" + REQUEST_ET_CHANNEL_ROUTE_URL = "/route" + REQUEST_ET_CHANNEL_ROUTE_RULE_URL = "/rule" +) + +// Client of ET service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForEt() string { + return URI_PREFIX + REQUEST_ET_URL +} + +func getURLForEtId(etId string) string { + return getURLForEt() + "/" + etId +} + +func getURLForEtChannel(etId string) string { + return getURLForEtId(etId) + REQUEST_ET_CHANNEL_URL +} + +func getURLForEtChannelId(etId string, etChannelId string) string { + return getURLForEtChannel(etId) + "/" + etChannelId +} + +func getURLForEtChannelRoute(etId string, etChannelId string) string { + return getURLForEtChannelId(etId, etChannelId) + REQUEST_ET_CHANNEL_ROUTE_URL +} + +func getURLForEtChannelRouteRule(etId string, etChannelId string) string { + return getURLForEtChannelRoute(etId, etChannelId) + REQUEST_ET_CHANNEL_ROUTE_RULE_URL +} + +func getURLForEtChannelRouteRuleId(etId string, etChannelId string, routeRuleId string) string { + return getURLForEtChannelRouteRule(etId, etChannelId) + "/" + routeRuleId +} diff --git a/bce-sdk-go/services/et/client_test.go b/bce-sdk-go/services/et/client_test.go new file mode 100644 index 0000000..0c689dc --- /dev/null +++ b/bce-sdk-go/services/et/client_test.go @@ -0,0 +1,298 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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 et + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + EtClient *Client +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + EtClient, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) +} + +// TestClient_GetEtChannel tests GetEtChannel method of EtClient +func TestClient_GetEtChannel(t *testing.T) { + args := &GetEtChannelArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-xxxxxxxxxxxx", + } + result, err := EtClient.GetEtChannel(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + log.Debug(string(r)) +} + +// TestClient_RecommitEtChannel 测试客户端的RecommitEtChannel方法 +func TestClient_RecommitEtChannel(t *testing.T) { + args := &RecommitEtChannelArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-xxxxxxxxxxxx", + EtChannelId: "dedicatedconn-xxxxxxxxxxxx", + Result: RecommitEtChannelResult{ // recommit et channel result + AuthorizedUsers: []string{"xxxxxxxxxxxxxxxxxx"}, // authorized users + Description: "test Description", // description + BaiduAddress: "Your BaiduAddress", // baidu address + Name: "test Name", // name + Networks: []string{"Your Networks"}, // networks + CustomerAddress: "Your CustomerAddress", // customer address + RouteType: "Your RouteType", // route type + VlanId: "1", // vlan id + Status: "Your Status", // status + EnableIpv6: 0, // enable ipv6 + BaiduIpv6Address: "Your BaiduIpv6Address", // baidu ipv6 address + Ipv6Networks: []string{"Your Ipv6Networks"}, // ipv6 networks + CustomerIpv6Address: "Your CustomerIpv6Address", // customer ipv6 address + }, + } + err := EtClient.RecommitEtChannel(args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_UpdateEtChannel 测试客户端的更新ET通道函数 +func TestClient_UpdateEtChannel(t *testing.T) { + args := &UpdateEtChannelArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-xxxxxxxxxxxx", + EtChannelId: "dedicatedconn-xxxxxxxxxxxx", + Result: UpdateEtChannelResult{ // update et channel result + Name: "testname", // name + Description: "testdecription", // description + }, + } + err := EtClient.UpdateEtChannel(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteEtChannel(t *testing.T) { + args := &DeleteEtChannelArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-xxxxxxxxxxxx", + EtChannelId: "dedicatedconn-xxxxxxxxxxxx", + } + err := EtClient.DeleteEtChannel(args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_EnableEtChannelIPv6 测试函数 +func TestClient_EnableEtChannelIPv6(t *testing.T) { + args := &EnableEtChannelIPv6Args{ + ClientToken: getClientToken(), + EtId: "dcphy-xxxxxxxxxxxx", + EtChannelId: "dedicatedconn-xxxxxxxxxxxx", + Result: EnableEtChannelIPv6Result{ // enable et channel ipv6 result + BaiduIpv6Address: "2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:0001", // baidu ipv6 address + Ipv6Networks: []string{"2001:xxxx:xxxx:xxxx::/64"}, // ipv6 networks + CustomerIpv6Address: "2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:0002", // customer ipv6 address + }, + } + err := EtClient.EnableEtChannelIPv6(args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_DisableEtChannelIPv6 测试函数 +func TestClient_DisableEtChannelIPv6(t *testing.T) { + args := &DisableEtChannelIPv6Args{ + ClientToken: getClientToken(), + EtId: "dcphy-tm25m1reihvw", + EtChannelId: "dedicatedconn-ybffmxnpygcx", + } + err := EtClient.DisableEtChannelIPv6(args) + ExpectEqual(t.Errorf, nil, err) +} + +// getClientToken 函数返回一个长度为32的字符串作为客户端令牌。 +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateEtDcphy(t *testing.T) { + args := &CreateEtDcphyArgs{ + ClientToken: getClientToken(), + Name: "test_InitEt", + Isp: "ISP_CMCC", + IntfType: "1G", + ApType: "SINGLE", + ApAddr: "BJYZ", + UserName: "test", + UserPhone: "18266666666", + UserEmail: "18266666666@baidu.com", + UserIdc: "北京|市辖区|东城区|百度科技园K2", + } + + r, err := EtClient.CreateEtDcphy(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(r.Id) != 0) +} + +func TestClient_UpdateEtDcphy(t *testing.T) { + args := &UpdateEtDcphyArgs{ + Name: "test_Update", + Description: "new", + UserName: "testUpdate", + UserPhone: "18266666667", + UserEmail: "18266666667@baidu.com", + } + + err := EtClient.UpdateEtDcphy("dcphy-23451", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListEtDcphy(t *testing.T) { + args := &ListEtDcphyArgs{ + Marker: "your marker", + MaxKeys: 10000, + Status: "established", + } + + r, err := EtClient.ListEtDcphy(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(r.Ets) != 0) +} + +func TestClient_ListEtDcphyDetail(t *testing.T) { + r, err := EtClient.ListEtDcphyDetail("dcphy-23451") + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(r.Name) != 0) +} + +func TestClient_CreateEtChannel(t *testing.T) { + args := &CreateEtChannelArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-234r5", + Description: "test", + BaiduAddress: "172.1.1.1/24", + Name: "testChannel", + Networks: []string{"192.168.0.0/16"}, + CustomerAddress: "172.1.1.2/24", + RouteType: "static-route", + VlanId: 100, + EnableIpv6: 1, + BaiduIpv6Address: "2400:da00:e003:0:1eb:200::1/88", + CustomerIpv6Address: "2400:da00:e003:0:0:200::1/88", + Ipv6Networks: []string{"2400:da00:e003:0:15f::/87"}, + } + + r, err := EtClient.CreateEtChannel(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, true, len(r.Id) != 0) +} + +func TestCreateEtGatewayRouteRule(t *testing.T) { + req := &CreateEtChannelRouteRuleArgs{ + EtId: "dcphy-tm25m1reihvw", + EtChannelId: "dedicatedconn-ybffmxnpygcx", + ClientToken: getClientToken(), + DestAddress: "11.11.12.14/32", + NextHopType: "etChannel", + NextHopId: "dedicatedconn-ybffmxnpygcx", + } + response, err := EtClient.CreateEtChannelRouteRule(req) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(response) + fmt.Println(string(r)) +} + +func TestListEtChannelRouteRule(t *testing.T) { + req := &ListEtChannelRouteRuleArgs{ + EtId: "dcphy-tm25m1reihvw", + EtChannelId: "dedicatedconn-ybffmxnpygcx", + } + response, err := EtClient.ListEtChannelRouteRule(req) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(response) + fmt.Println(string(r)) +} + +func TestUpdateEtChannelRouteRule(t *testing.T) { + req := &UpdateEtChannelRouteRuleArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-tm25m1reihvw", // 专线ID + EtChannelId: "dedicatedconn-ybffmxnpygcx", + RouteRuleId: "dcrr-07a5967b-84a", + Description: "test", + } + err := EtClient.UpdateEtChannelRouteRule(req) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteEtChannelRouteRule(t *testing.T) { + req := &DeleteEtChannelRouteRuleArgs{ + ClientToken: getClientToken(), + EtId: "dcphy-tm25m1reihvw", + EtChannelId: "dedicatedconn-ybffmxnpygcx", + RouteRuleId: "dcrr-6378ed8b-a2d", + } + err := EtClient.DeleteEtChannelRouteRule(req) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/et/et.go b/bce-sdk-go/services/et/et.go new file mode 100644 index 0000000..2738889 --- /dev/null +++ b/bce-sdk-go/services/et/et.go @@ -0,0 +1,372 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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. + */ + +// et.go - the et APIs definition supported by the et service + +package et + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// GetEtChannel - get an et channel +// +// PARAMS: +// - args: the arguments to get et channel +// +// RETURNS: +// - *GetEtChannelResult: the info of the et channel +// - error: nil if success otherwise the specific error +func (c *Client) GetEtChannel(args *GetEtChannelArgs) (*GetEtChannelsResult, error) { + if args == nil { + return nil, fmt.Errorf("The GetEtChannelArgs cannot be nil.") + } + + result := &GetEtChannelsResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannel(args.EtId)). + WithMethod(http.GET). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// RecommitEtChannel - recommit et channel +// +// PARAMS: +// - args: the arguments to recommit et channel +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecommitEtChannel(args *RecommitEtChannelArgs) error { + if args == nil { + return fmt.Errorf("The RecommitEtChannelArgs cannot be nil.") + } + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelId(args.EtId, args.EtChannelId)). + WithMethod(http.PUT). + WithBody(args.Result). + WithQueryParam("reCreate", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + return err +} + +// UpdateEtChannel - update et channel +// +// PARAMS: +// - args: the arguments to update et channel +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEtChannel(args *UpdateEtChannelArgs) error { + if args == nil { + return fmt.Errorf("The UpdateEtChannelArgs cannot be nil.") + } + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelId(args.EtId, args.EtChannelId)). + WithMethod(http.PUT). + WithBody(args.Result). + WithQueryParam("modifyAttribute", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + return err +} + +// DeleteEtChannel - delete et channel +// +// PARAMS: +// - args: the arguments to delete et channel +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEtChannel(args *DeleteEtChannelArgs) error { + if args == nil { + return fmt.Errorf("The DeleteEtChannelArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelId(args.EtId, args.EtChannelId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + return err +} + +// EnableEtChannelIPv6 - enable et channel ipv6 +// +// PARAMS: +// - args: the arguments to enable et channel ipv6 +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EnableEtChannelIPv6(args *EnableEtChannelIPv6Args) error { + if args == nil { + return fmt.Errorf("The EnableEtChannelIPv6Args cannot be nil.") + } + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelId(args.EtId, args.EtChannelId)). + WithMethod(http.PUT). + WithBody(args.Result). + WithQueryParam("enableIpv6", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + return err +} + +// DisableEtChannelIPv6 - disable EtChannelIPv6 with the specified parameters +// +// PARAMS: +// - args: the arguments to disable EtChannelIPv6 +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DisableEtChannelIPv6(args *DisableEtChannelIPv6Args) error { + if args == nil { + return fmt.Errorf("the createEtChannelRouteRuleArgs cannot be nil") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelId(args.EtId, args.EtChannelId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParam("disableIpv6", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// CreateEtDcphy - init a new Et +// +// PARAMS: +// - args: the arguments to init et dcphy +// +// RETURNS: +// - CreateEtDcphyResult: the id of et dcphy newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateEtDcphy(args *CreateEtDcphyArgs) (*CreateEtDcphyResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateEtDcphyArgs can not be nil") + } + + result := &CreateEtDcphyResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEt()+"/init"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// UpdateEtDcphy - update an existed Et +// +// PARAMS: +// - edId: the id of et dcphy +// - args: the arguments to update et dcphy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEtDcphy(dcphyId string, args *UpdateEtDcphyArgs) error { + if len(dcphyId) == 0 { + return fmt.Errorf("please set et dcphy id") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtId(dcphyId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// ListEtDcphy - List ets +// +// PARAMS: +// - args: the arguments to list et +// +// RETURNS: +// - ListEtDcphyResult: list result +// - error: nil if success otherwise the specific error +func (c *Client) ListEtDcphy(args *ListEtDcphyArgs) (*ListEtDcphyResult, error) { + if args == nil { + args = &ListEtDcphyArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListEtDcphyResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getURLForEt()). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParamFilter("status", args.Status). + WithResult(result). + Do() + + return result, err +} + +// ListEtDcphyDetail - List specific et detail +// +// PARAMS: +// - dcphyId: the id of etDcphy +// +// RETURNS: +// - EtDcphyDetail: etDcphy detail +// - error: nil if success otherwise the specific error +func (c *Client) ListEtDcphyDetail(dcphyId string) (*EtDcphyDetail, error) { + if len(dcphyId) == 0 { + return nil, fmt.Errorf("please set et dcphy id") + } + + result := &EtDcphyDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getURLForEtId(dcphyId)). + WithResult(result). + Do() + + return result, err +} + +// CreateEtChannel - create an Et channel with the specific parameters +// +// PARAMS: +// - args: the arguments to create an eip +// +// RETURNS: +// - CreateEipResult: the result of create EIP, contains new EIP's address +// - error: nil if success otherwise the specific error +func (c *Client) CreateEtChannel(args *CreateEtChannelArgs) (*CreateEtChannelResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create etChannel argments") + } + + if len(args.EtId) == 0 { + return nil, fmt.Errorf("please set et id") + } + + result := &CreateEtChannelResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getURLForEtChannel(args.EtId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateEtChannelRouteRule - create a new EtChannelRouteRule with the specified parameters +// +// PARAMS: +// - args: the arguments to create EtChannelRouteRule +// +// RETURNS: +// - *CreateEtChannelRouteRuleResult: the id of the EtChannelRouteRule newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateEtChannelRouteRule(args *CreateEtChannelRouteRuleArgs) (*CreateEtChannelRouteRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("the createEtChannelRouteRuleArgs cannot be nil") + } + result := &CreateEtChannelRouteRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelRouteRule(args.EtId, args.EtChannelId)). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + return result, err +} + +// ListEtChannelRouteRule - list all EtChannelRouteRules with the specified parameters +// +// PARAMS: +// - args: the arguments to list EtChannelRouteRules +// +// RETURNS: +// - *EtChannelRouteRuleResult: the result of all EtChannelRouteRules +// - error: nil if success otherwise the specific error +func (c *Client) ListEtChannelRouteRule(args *ListEtChannelRouteRuleArgs) (*ListEtChannelRouteRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("the listEtChannelRouteRuleArgs cannot be nil") + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("the field maxKeys is out of range [0, 1000]") + } + result := &ListEtChannelRouteRuleResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelRouteRule(args.EtId, args.EtChannelId)). + WithMethod(http.GET). + WithResult(result). + WithQueryParamFilter("marker", args.Marker) + if args.MaxKeys != 0 { + builder.WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + } + err := builder.Do() + return result, err +} + +// UpdateEtChannelRouteRule - update a specified EtChannelRouteRule +// +// PARAMS: +// - args: the arguments to update EtChannelRouteRule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEtChannelRouteRule(args *UpdateEtChannelRouteRuleArgs) error { + if args == nil { + return fmt.Errorf("the updateEtChannelRouteRuleArgs cannot be nil") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelRouteRuleId(args.EtId, args.EtChannelId, args.RouteRuleId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// DeleteEtChannelRouteRule - delete a specified EtChannelRouteRule +// +// PARAMS: +// - params: the arguments to delete EtChannelRouteRule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEtChannelRouteRule(args *DeleteEtChannelRouteRuleArgs) error { + if args == nil { + return fmt.Errorf("the deleteEtChannelRouteRuleArgs cannot be nil") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForEtChannelRouteRuleId(args.EtId, args.EtChannelId, args.RouteRuleId)). + WithMethod(http.DELETE). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + +} diff --git a/bce-sdk-go/services/et/model.go b/bce-sdk-go/services/et/model.go new file mode 100644 index 0000000..3355a37 --- /dev/null +++ b/bce-sdk-go/services/et/model.go @@ -0,0 +1,262 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package et + +type GetEtChannelArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` +} + +type RecommitEtChannelArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + Result RecommitEtChannelResult `json:"etChannelResult"` +} + +type UpdateEtChannelArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + Result UpdateEtChannelResult `json:"UpdateEtChannelResult"` +} + +type DeleteEtChannelArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` +} + +type EnableEtChannelIPv6Args struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + Result EnableEtChannelIPv6Result `json:"enableEtChannelIpv6Result"` +} + +type GetEtChannelsResult struct { + EtChannels []EtChannelResult `json:"etChannels"` +} + +type RecommitEtChannelResult struct { + AuthorizedUsers []string `json:"authorizedUsers"` + Description string `json:"description"` + BaiduAddress string `json:"baiduAddress"` + Name string `json:"name"` + Networks []string `json:"networks"` + CustomerAddress string `json:"customerAddress"` + RouteType string `json:"routeType"` + VlanId string `json:"vlanId"` + Id string `json:"id"` + Status string `json:"status"` + EnableIpv6 uint32 `json:"enableIpv6"` + BaiduIpv6Address string `json:"baiduIpv6Address"` + Ipv6Networks []string `json:"ipv6Networks"` + CustomerIpv6Address string `json:"CustomerIpv6Address"` +} + +type UpdateEtChannelResult struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type EnableEtChannelIPv6Result struct { + BaiduIpv6Address string `json:"baiduIpv6Address"` + CustomerIpv6Address string `json:"CustomerIpv6Address"` + Ipv6Networks []string `json:"ipv6Networks"` +} + +type EtChannelResult struct { + AuthorizedUsers []string `json:"authorizedUsers"` + Description string `json:"description"` + BaiduAddress string `json:"baiduAddress"` + Name string `json:"name"` + Networks []string `json:"networks"` + BGPAsn string `json:"bgpAsn"` + BGPKey string `json:"bgpKey"` + CustomerAddress string `json:"customerAddress"` + RouteType string `json:"routeType"` + VlanId string `json:"vlanId"` + Id string `json:"id"` + Status string `json:"status"` + EnableIpv6 uint32 `json:"enableIpv6"` + BaiduIpv6Address string `json:"baiduIpv6Address"` + Ipv6Networks []string `json:"ipv6Networks"` + CustomerIpv6Address string `json:"CustomerIpv6Address"` +} + +type CreateEtDcphyArgs struct { + ClientToken string `json:"clientToken,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Isp string `json:"isp"` + IntfType string `json:"intfType"` + ApType string `json:"apType"` + ApAddr string `json:"apAddr"` + UserName string `json:"userName"` + UserPhone string `json:"userPhone"` + UserEmail string `json:"userEmail"` + UserIdc string `json:"userIdc"` +} + +type CreateEtDcphyResult struct { + Id string `json:"id"` +} + +type UpdateEtDcphyArgs struct { + ClientToken string `json:"clientToken,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + UserName string `json:"userName,omitempty"` + UserPhone string `json:"userPhone,omitempty"` + UserEmail string `json:"userEmail,omitempty"` +} + +type ListEtDcphyArgs struct { + Marker string + MaxKeys int + Status string +} + +type ListEtDcphyResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Ets []Et `json:"ets"` +} + +type Et struct { + Id string `json:"Id"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` + ExpireTime string `json:"expireTime"` + Isp string `json:"isp"` + IntfType string `json:"intfType"` + ApType string `json:"apType"` + ApAddr string `json:"apAddr"` + UserName string `json:"userName"` + UserPhone string `json:"userPhone"` + UserEmail string `json:"userEmail"` + UserIdc string `json:"userIdc"` +} + +type EtDcphyDetail struct { + Id string `json:"clientToken,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` + ExpireTime string `json:"expireTime"` + Isp string `json:"isp"` + IntfType string `json:"intfType"` + ApType string `json:"apType"` + ApAddr string `json:"apAddr"` + UserName string `json:"userName"` + UserPhone string `json:"userPhone"` + UserEmail string `json:"userEmail"` + UserIdc string `json:"userIdc"` +} + +type CreateEtChannelArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + AuthorizedUsers []string `json:"authorizedUsers,omitempty"` + Description string `json:"description,omitempty"` + BaiduAddress string `json:"baiduAddress"` + Name string `json:"name"` + Networks []string `json:"networks,omitempty"` + CustomerAddress string `json:"customerAddress"` + RouteType string `json:"routeType"` + VlanId int `json:"vlanId"` + BgpAsn string `json:"bgpAsn,omitempty"` + BgpKey string `json:"bgpKey,omitempty"` + EnableIpv6 int `json:"enableIpv6,omitempty"` + BaiduIpv6Address string `json:"baiduIpv6Address,omitempty"` + CustomerIpv6Address string `json:"customerIpv6Address,omitempty"` + Ipv6Networks []string `json:"ipv6Networks,omitempty"` +} + +type CreateEtChannelResult struct { + Id string `json:"id"` +} + +type DisableEtChannelIPv6Args struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` +} + +type CreateEtChannelRouteRuleArgs struct { + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + ClientToken string `json:"clientToken,omitempty"` + IpVersion int `json:"ipVersion,omitempty"` + DestAddress string `json:"destAddress"` + NextHopType string `json:"nexthopType"` + NextHopId string `json:"nexthopId"` + Description string `json:"description,omitempty"` +} + +type CreateEtChannelRouteRuleResult struct { + RouteRuleId string `json:"routeRuleId"` +} + +type ListEtChannelRouteRuleArgs struct { + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` + DestAddress string `json:"destAddress,omitempty"` +} + +type ListEtChannelRouteRuleResult struct { + Marker string `json:"marker"` + IsTrucated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RouteRules []EtChannelRouteRule `json:"routeRules"` +} + +type EtChannelRouteRule struct { + RouteRuleId string `json:"routeRuleId"` + IpVersion int `json:"ipVersion"` + DestAddress string `json:"destAddress"` + NextHopType string `json:"nexthopType"` + NextHopId string `json:"nexthopId"` + Description string `json:"description"` + RouteProto string `json:"routeProto"` + AsPaths string `json:"asPaths"` + LocalPreference int `json:"localPreference"` + Med int `json:"med"` + Origin string `json:"origin"` +} + +type UpdateEtChannelRouteRuleArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + RouteRuleId string `json:"routeRuleId"` + Description string `json:"description"` +} + +type DeleteEtChannelRouteRuleArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtId string `json:"etId"` + EtChannelId string `json:"etChannelId"` + RouteRuleId string `json:"routeRuleId"` +} diff --git a/bce-sdk-go/services/etGateway/client.go b/bce-sdk-go/services/etGateway/client.go new file mode 100644 index 0000000..ed5247e --- /dev/null +++ b/bce-sdk-go/services/etGateway/client.go @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for EtGateway service + +// Package vpn defines the vpn services of BCE. +// The supported APIs are all defined in different files. +package etGateway + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_VPN_URL = "/etGateway" +) + +// Client of EtGateway service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForEtGateway() string { + return URI_PREFIX + REQUEST_VPN_URL +} + +func getURLForEtGatewayId(etGatewayId string) string { + return getURLForEtGateway() + "/" + etGatewayId +} diff --git a/bce-sdk-go/services/etGateway/client_test.go b/bce-sdk-go/services/etGateway/client_test.go new file mode 100644 index 0000000..48ea948 --- /dev/null +++ b/bce-sdk-go/services/etGateway/client_test.go @@ -0,0 +1,163 @@ +package etGateway + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + EtGateway_CLIENT *Client + region string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + VPCEndpoint string `json:"VPC"` + EIPEndpoint string `json:"EIP"` +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + EtGateway_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.VPCEndpoint) + + region = confObj.VPCEndpoint[4:6] + +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateEtGateway(t *testing.T) { + args := &CreateEtGatewayArgs{ + Name: "TestSDK-VPN", + Description: "vpn test", + VpcId: "vpc-2pa2x0bjt26i", + Speed: 100, + ClientToken: getClientToken(), + } + result, err := EtGateway_CLIENT.CreateEtGateway(args) + ExpectEqual(t.Errorf, nil, err) + EtGatewayId := result.EtGatewayId + log.Debug(EtGatewayId) +} + +func TestClient_ListEtGateway(t *testing.T) { + args := &ListEtGatewayArgs{ + VpcId: "vpc-xsd65rcsp5ue", + } + result := &ListEtGatewayResult{} + result, err := EtGateway_CLIENT.ListEtGateway(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_GetEtGatewayDetail(t *testing.T) { + res := &EtGatewayDetail{} + res, err := EtGateway_CLIENT.GetEtGatewayDetail("dcgw-vs1rvp9qy79f") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestClient_UpdateEtGateway(t *testing.T) { + args := &UpdateEtGatewayArgs{ + ClientToken: getClientToken(), + EtGatewayId: "dcgw-mx3annmentbu", + Name: "aaa", + Description: "test", + } + err := EtGateway_CLIENT.UpdateEtGateway(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteEtGateway(t *testing.T) { + err := EtGateway_CLIENT.DeleteEtGateway("dcgw-iiyc0ers2qx4", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_BindEt(t *testing.T) { + args := &BindEtArgs{ + ClientToken: getClientToken(), + EtGatewayId: "dcgw-iiyc0ers2qx4", + EtId: "et-aaccd", + ChannelId: "sdxs", + LocalCidrs: []string{"192.168.0.1"}, + } + err := EtGateway_CLIENT.BindEt(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnBindEt(t *testing.T) { + err := EtGateway_CLIENT.UnBindEt("dcgw-iiyc0ers2qx4", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateHealthCheck(t *testing.T) { + auto := true + args := &CreateHealthCheckArgs{ + ClientToken: getClientToken(), + EtGatewayId: "dcgw-iiyc0ers2qx4", + HealthCheckSourceIp: "1.2.3.4", + HealthCheckType: HEALTH_CHECK_ICMP, + HealthCheckPort: 80, + HealthCheckInterval: 60, + HealthThreshold: 3, + UnhealthThreshold: 4, + AutoGenerateRouteRule: &auto, + } + err := EtGateway_CLIENT.CreateHealthCheck(args) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/etGateway/etGateway.go b/bce-sdk-go/services/etGateway/etGateway.go new file mode 100644 index 0000000..e2dc7aa --- /dev/null +++ b/bce-sdk-go/services/etGateway/etGateway.go @@ -0,0 +1,155 @@ +package etGateway + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +// CreateEtGateway - create a new Et gateway +// +// PARAMS: +// - args: the arguments to create Et gateway +// RETURNS: +// - *CreateVpnGatewayResult: the id of the et gateway newly created +// - error: nil if success otherwise the specific error + +func (c *Client) CreateEtGateway(args *CreateEtGatewayArgs) (*CreateEtGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateEtGatewayArgs cannot be nil.") + } + + result := &CreateEtGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtGateway()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListEtGateway - list all Et gateways with the specific parameters +// PARAMS: +// - args: the arguments to list et gateways +// +// RETURNS: +// - *ListEtGatewayResult: the result of Et gateway list +// - error: nil if success otherwise the specific error +func (c *Client) ListEtGateway(args *ListEtGatewayArgs) (*ListEtGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListEtGatewayArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + result := &ListEtGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtGateway()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithQueryParamFilter("etGatewayId", args.EtGatewayId). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("status", args.Status). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + return result, err +} + +// GetEtGatewayDetail - Get the Et gateways with the specific parameters +// PARAMS: +// - etGatewayId: the id of the EtGateway's +// +// RETURNS: +// - *EtGatewayDetail: the result of EtGgateway detail +// - error: nil if success otherwise the specific error +func (c *Client) GetEtGatewayDetail(etGatewayId string) (*EtGatewayDetail, error) { + result := &EtGatewayDetail{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(etGatewayId)). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} + +// UpdateEtGateway - update the Et gateways with the specific parameters +// PARAMS: +// - args: the arguments to update the EtGateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateEtGateway(updateEtGatewayArgs *UpdateEtGatewayArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(updateEtGatewayArgs.EtGatewayId)). + WithQueryParamFilter("clientToken", updateEtGatewayArgs.ClientToken). + WithMethod(http.PUT). + WithBody(updateEtGatewayArgs). + Do() +} + +// DeleteEtGateway - delete the Et gateways with the specific parameters +// PARAMS: +// - etGatewayId: the id to delete the EtGateway +// - clientToken: the idempotent string +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteEtGateway(etGatewayId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(etGatewayId)). + WithQueryParam("clientToken", clientToken). + WithMethod(http.DELETE). + Do() +} + +// UnBindEt - bind the Et +// PARAMS: +// - args: the arguments to bind the Et +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindEt(args *BindEtArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(args.EtGatewayId)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("bind", ""). + WithBody(args). + WithMethod(http.PUT). + Do() +} + +// UnBindEt - unbind the Et +// PARAMS: +// - args: the arguments to unbind the Et +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindEt(EtGatewayId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(EtGatewayId)). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("unbind", ""). + WithMethod(http.PUT). + Do() +} + +// CreateHealthCheck - create the Et gateway's healthcheck with the specific parameters +// PARAMS: +// - args: the arguments to create the EtGateway's healthcheck +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateHealthCheck(args *CreateHealthCheckArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForEtGatewayId(args.EtGatewayId)+"/healthCheck"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithMethod(http.POST). + Do() +} diff --git a/bce-sdk-go/services/etGateway/model.go b/bce-sdk-go/services/etGateway/model.go new file mode 100644 index 0000000..a014ada --- /dev/null +++ b/bce-sdk-go/services/etGateway/model.go @@ -0,0 +1,101 @@ +package etGateway + +type ( + HealthCheckType string +) + +const ( + HEALTH_CHECK_ICMP HealthCheckType = "ICMP" +) + +type CreateEtGatewayArgs struct { + Name string `json:"name"` + VpcId string `json:"vpcId"` + Speed int `json:"speed"` + Description string `json:"description"` + EtId string `json:"etId"` + ChannelId string `json:"channelId"` + LocalCidrs []string `json:"localCidrs"` + ClientToken string `json:"clientToken,omitempty"` +} + +type CreateEtGatewayResult struct { + EtGatewayId string `json:"etGatewayId"` +} + +type ListEtGatewayArgs struct { + VpcId string + EtGatewayId string + Name string + Status string + Marker string + MaxKeys int +} + +type ListEtGatewayResult struct { + EtGateways []EtGateway `json:"etGateways"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} +type EtGateway struct { + EtGatewayId string `json:"etGatewayId"` + Name string `json:"name"` + Status string `json:"status"` + Speed int `json:"speed"` + CreateTime string `json:"createTime"` + Description string `json:"description"` + VpcId string `json:"vpcId"` + EtId string `json:"etId"` + ChannelId string `json:"channelId"` + LocalCidrs []string `json:"localCidrs"` +} + +type EtGatewayDetail struct { + EtGatewayId string `json:"etGatewayId"` + Name string `json:"name"` + Status string `json:"status"` + Speed int `json:"speed"` + CreateTime string `json:"createTime"` + Description string `json:"description"` + VpcId string `json:"vpcId"` + EtId string `json:"etId"` + ChannelId string `json:"channelId"` + LocalCidrs []string `json:"localCidrs"` + HealthCheckSourceIp string `json:"healthCheckSourceIp"` + HealthCheckDestIp string `json:"healthCheckDestIp"` + HealthCheckType string `json:"healthCheckType"` + HealthCheckInterval int `json:"healthCheckInterval"` + HealthThreshold int `json:"healthThreshold"` + UnhealthThreshold int `json:"unhealthThreshold"` +} + +// 参数localCidrs只有在专线网关处于running状态时允许更新。 +type UpdateEtGatewayArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtGatewayId string `json:"etGatewayId"` + Name string `json:"name,omitempty"` + Speed int `json:"speed,omitempty"` + Description string `json:"description,omitempty"` + LocalCidrs []string `json:"localCidrs,omitempty"` +} +type BindEtArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtGatewayId string `json:"etGatewayId"` + EtId string `json:"etId"` + ChannelId string `json:"channelId"` + LocalCidrs []string `json:"localCidrs,omitempty"` +} + +type CreateHealthCheckArgs struct { + ClientToken string `json:"clientToken,omitempty"` + EtGatewayId string `json:"etGatewayId"` + HealthCheckSourceIp string `json:"healthCheckSourceIp,omitempty"` + HealthCheckType HealthCheckType `json:"healthCheckType,omitempty"` + HealthCheckPort int `json:"healthCheckPort,omitempty"` + HealthCheckInterval int `json:"healthCheckInterval"` + HealthThreshold int `json:"healthThreshold"` + UnhealthThreshold int `json:"unhealthThreshold"` + AutoGenerateRouteRule *bool `json:"autoGenerateRouteRule,omitempty"` +} diff --git a/bce-sdk-go/services/gaiadb/client.go b/bce-sdk-go/services/gaiadb/client.go new file mode 100644 index 0000000..6f00e1d --- /dev/null +++ b/bce-sdk-go/services/gaiadb/client.go @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Gaiadb service + +// Package gaiadb defines the GAIADB services of BCE. The supported APIs are all defined in sub-package +package gaiadb + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + DEFAULT_ENDPOINT = "gaiadb.bj.baidubce.com" +) + +// Client of GAIADB service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} diff --git a/bce-sdk-go/services/gaiadb/client_test.go b/bce-sdk-go/services/gaiadb/client_test.go new file mode 100644 index 0000000..1d050c7 --- /dev/null +++ b/bce-sdk-go/services/gaiadb/client_test.go @@ -0,0 +1,700 @@ +package gaiadb + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + GAIADB_CLIENT *Client + GAIADB_ID = "gaiadbwjeq27" + ORDERID string + // set this value before start test + ACCOUNT_NAME = "baidu" + PASSWORD = "testpassword" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +const ( + SDK_NAME_PREFIX = "sdk_gaiadb_" +) + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 1; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + GAIADB_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} +func TestClient_CreateGaiadb(t *testing.T) { + + args := &CreateClusterArgs{ + ClientToken: getClientToken(), + Number: 1, + ProductType: "postpay", + InstanceParam: InstanceParam{ + ReleaseVersion: "8.0", + SubnetId: "sbn-na4tmg4v11hs", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, + AllocatedStorageInGB: 5120, + VpcId: "vpc-it3v6qt3jhvj", + InstanceAmount: 2, + ProxyAmount: 2, + }, + } + result, err := GAIADB_CLIENT.CreateCluster(args) + + ExpectEqual(t.Errorf, nil, err) + + GAIADB_ID = result.ClusterIds[0] + ORDERID = result.OrderId + fmt.Println("GAIADB: ", GAIADB_ID) + fmt.Println("ORDERID: ", ORDERID) +} + +func TestClient_DeleteCluster(t *testing.T) { + err := GAIADB_CLIENT.DeleteCluster(GAIADB_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RenameCluster(t *testing.T) { + args := &ClusterName{ + ClusterName: "cluster_test", + } + err := GAIADB_CLIENT.RenameCluster(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ResizeCluster(t *testing.T) { + args := &ResizeClusterArgs{ + ResizeType: "resizeSlave", + AllocatedCpuInCore: 4, + AllocatedMemoryInMB: 8192, + } + result, err := GAIADB_CLIENT.ResizeCluster(GAIADB_ID, args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetClusterList(t *testing.T) { + args := &Marker{ + Marker: "-1", + MaxKeys: 1000, + } + result, err := GAIADB_CLIENT.GetClusterList(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetClusterDetail(t *testing.T) { + result, err := GAIADB_CLIENT.GetClusterDetail(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetClusterCapacity(t *testing.T) { + result, err := GAIADB_CLIENT.GetClusterCapacity(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_QueryClusterPrice(t *testing.T) { + args := &QueryPriceArgs{ + Number: 1, + InstanceParam: InstanceInfo{ + ReleaseVersion: "8.0", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, + AllocatedStorageInGB: 5120, + InstanceAmount: 2, + ProxyAmount: 2, + }, + ProductType: "postpay", + } + result, err := GAIADB_CLIENT.QueryClusterPrice(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_QueryResizeClusterPrice(t *testing.T) { + args := &QueryResizePriceArgs{ + ClusterId: GAIADB_ID, + ResizeType: "resizeSlave", + AllocatedCpuInCore: 2, + AllocatedMemoryInMB: 8192, + } + result, err := GAIADB_CLIENT.QueryResizeClusterPrice(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RebootInstance(t *testing.T) { + args := &RebootInstanceArgs{ + ExecuteAction: "executeNow", + } + err := GAIADB_CLIENT.RebootInstance(GAIADB_ID, "gaiadbm5h6ys-secondary-129aafc0", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindTags(t *testing.T) { + args := &BindTagsArgs{ + Resources: []Resource{ + { + ResourceId: GAIADB_ID, + Tags: []Tag{ + { + TagKey: "testTagKey", + TagValue: "testTagValue", + }, + }, + }, + }, + } + err := GAIADB_CLIENT.BindTags(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ClusterSwitch(t *testing.T) { + args := &ClusterSwitchArgs{ + ExecuteAction: "executeNow", + SecondaryInstanceId: GAIADB_ID, + } + result, err := GAIADB_CLIENT.ClusterSwitch(GAIADB_ID, args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetInterfaceList(t *testing.T) { + result, err := GAIADB_CLIENT.GetInterfaceList(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateDnsName(t *testing.T) { + args := &UpdateDnsNameArgs{ + InterfaceId: "gaiadbm5h6ys_interface0000", + DnsName: "my.gaiadb.bj.baidubce.com", + } + err := GAIADB_CLIENT.UpdateDnsName(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateInterface(t *testing.T) { + args := &UpdateInterfaceArgs{ + InterfaceId: "gaiadbm5h6ys_interface0000", + Interface: InterfaceInfo{ + MasterReadable: 1, + AddressName: "addressname", + InstanceBinding: []string{ + "gaiadbymbrc8-primary-6f1cc3a2", + "gaiadbymbrc8-secondary-ec909467", + }, + }, + } + err := GAIADB_CLIENT.UpdateInterface(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_NewInstanceAutoJoin(t *testing.T) { + args := &NewInstanceAutoJoinArgs{ + AutoJoinRequestItems: []AutoJoinRequestItem{ + { + NewInstanceAutoJoin: "off", + InterfaceId: "gaiadbymbrc8-primary-6f1cc3a2", + }, + }, + } + err := GAIADB_CLIENT.NewInstanceAutoJoin(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateAccount(t *testing.T) { + args := &CreateAccountArgs{ + AccountName: "testaccount", + Password: "testpassword", + AccountType: "common", + Remark: "testRemark", + } + err := GAIADB_CLIENT.CreateAccount(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAccount(t *testing.T) { + err := GAIADB_CLIENT.DeleteAccount(GAIADB_ID, "testaccount") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAccountDetail(t *testing.T) { + result, err := GAIADB_CLIENT.GetAccountDetail(GAIADB_ID, "testaccount") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_GetAccountList(t *testing.T) { + result, err := GAIADB_CLIENT.GetAccountList(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountRemark(t *testing.T) { + args := &RemarkArgs{ + Remark: "remark", + Etag: "v0", + } + err := GAIADB_CLIENT.UpdateAccountRemark(GAIADB_ID, "testaccount", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountAuthIp(t *testing.T) { + args := &AuthIpArgs{ + Action: "ipAdd", + Value: AuthIp{ + Authip: []string{"10.10.10.10"}, + Authbns: []string{}, + }, + } + err := GAIADB_CLIENT.UpdateAccountAuthIp(GAIADB_ID, "testaccount", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountPrivileges(t *testing.T) { + args := &PrivilegesArgs{ + DatabasePrivileges: []DatabasePrivilege{ + { + DbName: "testdb", + AuthType: "definePrivilege", + Privileges: []string{"UPDATE"}, + }, + }, + Etag: "v0", + } + err := GAIADB_CLIENT.UpdateAccountPrivileges(GAIADB_ID, "testaccount", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountPassword(t *testing.T) { + args := &PasswordArgs{ + Password: "testpassword", + Etag: "v0", + } + err := GAIADB_CLIENT.UpdateAccountPassword(GAIADB_ID, "testaccount", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateDatabase(t *testing.T) { + args := &CreateDatabaseArgs{ + DbName: "test_db", + CharacterSetName: "utf8", + Remark: "sdk test", + } + err := GAIADB_CLIENT.CreateDatabase(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteDatabase(t *testing.T) { + err := GAIADB_CLIENT.DeleteDatabase(GAIADB_ID, "test_db") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListDatabase(t *testing.T) { + result, err := GAIADB_CLIENT.ListDatabase(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_CreateSnapshot(t *testing.T) { + err := GAIADB_CLIENT.CreateSnapshot(GAIADB_ID) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_ListSnapshot(t *testing.T) { + result, err := GAIADB_CLIENT.ListSnapshot(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateSnapshotPolicy(t *testing.T) { + args := &UpdateSnapshotPolicyArgs{ + DataBackupWeekDay: []string{"Monday"}, + DataBackupRetainStrategys: []DataBackupRetainStrategy{{ + StartSeconds: 0, + RetainCount: "8", + Precision: 86400, + EndSeconds: -691200, + }}, + DataBackupTime: "02:00:00Z", + } + err := GAIADB_CLIENT.UpdateSnapshotPolicy(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_GetSnapshotPolicy(t *testing.T) { + result, err := GAIADB_CLIENT.GetSnapshotPolicy(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateWhiteList(t *testing.T) { + args := &WhiteList{ + AuthIps: []string{"192.168.1.2"}, + Etag: "v0", + } + err := GAIADB_CLIENT.UpdateWhiteList(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetWhiteList(t *testing.T) { + result, err := GAIADB_CLIENT.GetWhiteList(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateMultiactiveGroup(t *testing.T) { + args := &CreateMultiactiveGroupArgs{ + LeaderClusterId: GAIADB_ID, + MultiActiveGroupName: "test_multiactive_group", + } + result, err := GAIADB_CLIENT.CreateMultiactiveGroup(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteMultiactiveGroup(t *testing.T) { + err := GAIADB_CLIENT.DeleteMultiactiveGroup("gaiagroup-fru5yw") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RenameMultiactiveGroup(t *testing.T) { + args := &RenameMultiactiveGroupArgs{ + MultiActiveGroupName: "test_multiactive_group", + } + err := GAIADB_CLIENT.RenameMultiactiveGroup("gaiagroup-5r5aur", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_MultiactiveGroupList(t *testing.T) { + result, err := GAIADB_CLIENT.MultiactiveGroupList() + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_MultiactiveGroupDetail(t *testing.T) { + result, err := GAIADB_CLIENT.MultiactiveGroupDetail("gaiagroup-0luzwo") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSyncStatus(t *testing.T) { + result, err := GAIADB_CLIENT.GetSyncStatus("gaiagroup-0luzwo", GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupExchange(t *testing.T) { + args := &ExchangeArgs{ + ExecuteAction: "executeNow", + NewLeaderClusterId: GAIADB_ID, + } + err := GAIADB_CLIENT.GroupExchange("gaiagroup-0luzwo", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamsList(t *testing.T) { + result, err := GAIADB_CLIENT.GetParamsList(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamsHistory(t *testing.T) { + result, err := GAIADB_CLIENT.GetParamsHistory(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateParams(t *testing.T) { + args := &UpdateParamsArgs{ + Params: map[string]interface{}{ + "auto_increment_increment": "5", + }, + Timing: "now", + } + err := GAIADB_CLIENT.UpdateParams(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListParamTemplate(t *testing.T) { + args := &ListParamTempArgs{ + Detail: 0, + Type: "mysql", + PageNo: 1, + PageSize: 10, + } + result, err := GAIADB_CLIENT.ListParamTemplate(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SaveAsParamTemplate(t *testing.T) { + args := &ParamTempArgs{ + Type: "mysql", + Version: "8.0", + Description: "create by sdk", + Name: "sdk_test", + Source: GAIADB_ID, + } + err := GAIADB_CLIENT.SaveAsParamTemplate(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetTemplateApplyRecords(t *testing.T) { + result, err := GAIADB_CLIENT.GetTemplateApplyRecords("ce8a8245-1d9b-c844-bff8-818933461baa") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteParamsFromTemp(t *testing.T) { + args := &Params{ + Params: []string{"long_query_time"}, + } + err := GAIADB_CLIENT.DeleteParamsFromTemp("ce8a8245-1d9b-c844-bff8-818933461baa", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateParamTemplate(t *testing.T) { + args := &UpdateParamTplArgs{ + Name: "test_template", + Description: "test_template_description", + } + err := GAIADB_CLIENT.UpdateParamTemplate("ce8a8245-1d9b-c844-bff8-818933461baa", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ModifyParams(t *testing.T) { + args := &ModifyParamsArgs{ + Params: map[string]interface{}{ + "auto_increment_increment": "5", + "long_query_time": "6.6", + }, + } + err := GAIADB_CLIENT.ModifyParams("ce8a8245-1d9b-c844-bff8-818933461baa", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteParamTemplate(t *testing.T) { + err := GAIADB_CLIENT.DeleteParamTemplate("ce8a8245-1d9b-c844-bff8-818933461baa") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateParamTemplate(t *testing.T) { + args := &CreateParamTemplateArgs{ + Name: "test_template", + Type: "mysql", + Version: "8.0", + Description: "test_template_description", + } + err := GAIADB_CLIENT.CreateParamTemplate(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamTemplateDetail(t *testing.T) { + result, err := GAIADB_CLIENT.GetParamTemplateDetail("ce8a8245-1d9b-c844-bff8-818933461baa", "0") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamTemplateHistory(t *testing.T) { + result, err := GAIADB_CLIENT.GetParamTemplateHistory("ce8a8245-1d9b-c844-bff8-818933461baa", "addParam") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ApplyParamTemplate(t *testing.T) { + args := &ApplyParamTemplateArgs{ + Timing: "now", + Clusters: map[string]interface{}{ + "gaiadbk3pyxv": []interface{}{}, + }, + } + err := GAIADB_CLIENT.ApplyParamTemplate("ce8a8245-1d9b-c844-bff8-818933461baa", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateMaintenTime(t *testing.T) { + args := &UpdateMaintenTimeArgs{ + Period: "1,2,3", + StartTime: "03:00", + Duration: 1, + } + err := GAIADB_CLIENT.UpdateMaintenTime(GAIADB_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetMaintenTime(t *testing.T) { + result, err := GAIADB_CLIENT.GetMaintenTime(GAIADB_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlDetail(t *testing.T) { + args := &GetSlowSqlArgs{ + Page: "1", + PageSize: "10", + } + result, err := GAIADB_CLIENT.GetSlowSqlDetail(GAIADB_ID, args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SlowSqlAdvice(t *testing.T) { + result, err := GAIADB_CLIENT.SlowSqlAdvice(GAIADB_ID, "a41ff8fe-b0c6-407c-91f6-354717c3fbaa") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBinlogDetail(t *testing.T) { + args := &GetBinlogArgs{ + AppID: GAIADB_ID, + LogBackupType: "logical", + } + result, err := GAIADB_CLIENT.GetBinlogDetail("1694660508228961814", args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBinlogList(t *testing.T) { + args := &GetBinlogListArgs{ + AppID: GAIADB_ID, + LogBackupType: "logical", + PageNo: 1, + PageSize: 10, + } + result, err := GAIADB_CLIENT.GetBinlogList(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ExecuteTaskNow(t *testing.T) { + taskId := "3773297" + err := GAIADB_CLIENT.ExecuteTaskNow(taskId) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CancelTask(t *testing.T) { + taskId := "3773297" + err := GAIADB_CLIENT.CancelTask(taskId) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetTaskList(t *testing.T) { + args := &TaskListArgs{ + Region: "bj", + StartTime: "2023-09-11 16:00:00", + } + result, err := GAIADB_CLIENT.GetTaskList(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetClusterByVpcId(t *testing.T) { + result, err := GAIADB_CLIENT.GetClusterByVpcId("9556bf45-5867-4495-83c5-bd945b782503") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetClusterByLbId(t *testing.T) { + result, err := GAIADB_CLIENT.GetClusterByLbId("496d6b552f316b313863786a4b32457a2f4732416e773d3d") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetOrderInfo(t *testing.T) { + result, err := GAIADB_CLIENT.GetOrderInfo("8a3c9bb4313e489f859938b7d199487f") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/gaiadb/gaiadb.go b/bce-sdk-go/services/gaiadb/gaiadb.go new file mode 100644 index 0000000..5652257 --- /dev/null +++ b/bce-sdk-go/services/gaiadb/gaiadb.go @@ -0,0 +1,1391 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// gaiadb.go - the gaiadb APIs definition supported by the GAIADB service +package gaiadb + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func (c *Client) Request(method, uri string, body interface{}) (interface{}, error) { + res := struct{}{} + req := bce.NewRequestBuilder(c). + WithMethod(method). + WithURL(uri). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + var err error + if body != nil { + err = req. + WithBody(body). + WithResult(res). + Do() + } else { + err = req. + WithResult(res). + Do() + } + + return res, err +} +func getGaiadbUri() string { + return "/v1/gaiadb" +} +func Json(v interface{}) string { + jsonStr, err := json.Marshal(v) + if err != nil { + panic("convert to json faild") + } + return string(jsonStr) +} + +// Convert struct to request params +func getQueryParams(val interface{}) (map[string]string, error) { + var params map[string]string + if val != nil { + err := json.Unmarshal([]byte(Json(val)), ¶ms) + if err != nil { + return nil, err + } + } + return params, nil +} + +// CreateCluster - create gaiadb cluster +// +// PARAMS: +// - args: the arguments to create gaiadb cluster +// +// RETURNS: +// - *CreateResult: the result of create gaiadb cluster +// - error: nil if success otherwise the specific error +func (c *Client) CreateCluster(args *CreateClusterArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/cluster"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DeleteCluster - delete gaiadb cluster +// +// PARAMS: +// - clusterId: the cluster id which you want to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteCluster(clusterId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGaiadbUri()+"/cluster/"+clusterId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// RenameCluster - rename gaiadb cluster +// +// PARAMS: +// - clusterId: the cluster id which you want to delete +// - *ClusterName: the new cluster name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameCluster(clusterId string, args *ClusterName) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/cluster/"+clusterId). + WithQueryParam("clusterName", ""). + WithBody(args). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// ResizeCluster - resize gaiadb cluster +// +// PARAMS: +// - clusterId: the cluster id which you want to delete +// - *ResizeClusterArgs: the arguments to resize gaiadb cluster +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ResizeResult: the result of resize gaiadb cluster +func (c *Client) ResizeCluster(clusterId string, args *ResizeClusterArgs) (*OrderId, error) { + result := &OrderId{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/cluster/"+clusterId). + WithQueryParam("resize", ""). + WithBody(args). + WithResult(result). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return result, err +} + +// GetClusterList - get gaiadb cluster list +// +// PARAMS: +// - *Markder: the arguments to get cluster list +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ClusterListResult: the result of get cluster list +func (c *Client) GetClusterList(args *Marker) (*ClusterListResult, error) { + result := &ClusterListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/cluster"). + WithBody(args). + WithResult(result). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return result, err +} + +// GetClusterCapacity - get cluster Capacity +// PARAMS: +// - ClusterId: cluster id +// +// RETURNS: +// - *ClusterCapacityResult: the result of cluster detail +// - error: nil if success otherwise the specific error +func (c *Client) GetClusterDetail(clusterId string) (*ClusterDetailResult, error) { + result := &ClusterDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri() + "/cluster/" + clusterId). + WithResult(result). + Do() + + return result, err +} + +// GetClusterCapacity - get cluster capacity +// PARAMS: +// - ClusterId: cluster id +// +// RETURNS: +// - *ClusterCapacityResult: the result of cluster capacity +// - error: nil if success otherwise the specific error +func (c *Client) GetClusterCapacity(clusterId string) (*ClusterCapacityResult, error) { + result := &ClusterCapacityResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri() + "/" + clusterId + "/capacity"). + WithResult(result). + Do() + + return result, err +} + +// QueryClusterPrice - query cluster price +// +// PARAMS: +// - args: the arguments to query cluster price +// +// RETURNS: +// - *PriceResult: the result of query cluster price +// - error: nil if success otherwise the specific error +func (c *Client) QueryClusterPrice(args *QueryPriceArgs) (*PriceResult, error) { + result := &PriceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/price"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// QueryResizeClusterPrice - query resize cluster price +// +// PARAMS: +// - args: the arguments to query resize cluster price +// +// RETURNS: +// - *PriceResult: the result of query resize cluster price +// - error: nil if success otherwise the specific error +func (c *Client) QueryResizeClusterPrice(args *QueryResizePriceArgs) (*PriceResult, error) { + result := &PriceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/price/diff"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// RebootInstance - reboot instance +// +// PARAMS: +// - args: the arguments to reboot instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(clusterId, instanceId string, args *RebootInstanceArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/cluster/"+clusterId+"/instance/"+instanceId+"/reboot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// BindTags - bind tags +// +// PARAMS: +// - args: the arguments to bind tags +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindTags(args *BindTagsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL("/v1/tags"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// ClusterSwitch - cluster switch +// +// PARAMS: +// - clusterId: the cluster id +// - args: the arguments to switch cluster +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SwitchResult: the result of switch cluster +func (c *Client) ClusterSwitch(clusterId string, args *ClusterSwitchArgs) (*ClusterSwitchResult, error) { + result := &ClusterSwitchResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/cluster/"+clusterId+"/switch"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetInterfaceList - get interface list +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - *InterfaceListResult: the result of interface list +// - error: nil if success otherwise the specific error +func (c *Client) GetInterfaceList(clusterId string) (*InterfaceListResult, error) { + result := &InterfaceListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri() + "/" + clusterId + "/interface"). + WithResult(result). + Do() + + return result, err +} + +// UpdateDnsName - update dns name +// +// PARAMS: +// - clusterId: the cluster id +// - args: the arguments to update dns name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDnsName(clusterId string, args *UpdateDnsNameArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/interface/dns-name"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateInterface - update interface +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to update interface +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInterface(clusterId string, args *UpdateInterfaceArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/interface"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// NewInstanceAutoJoin - new instance auto join +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to new instance auto join +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) NewInstanceAutoJoin(clusterId string, args *NewInstanceAutoJoinArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/interface/new-instance-auto-join"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// CreateAccount - create account +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to create account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateAccount(clusterId string, args *CreateAccountArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/"+clusterId+"/account"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// DeleteAccount - delete account +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAccount(clusterId, accountName string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// GetAccountDetail - get account detail +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetAccountDetail(clusterId, accountName string) (*AccountDetail, error) { + result := &AccountDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetAccountList - get account list +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetAccountList(clusterId string) (*AccountList, error) { + result := &AccountList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/account"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// UpdateAccountRemark - update account remark +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to update +// - args: the arguments to update account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountRemark(clusterId, accountName string, args *RemarkArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithQueryParam("remark", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateAccountAuthIp - update account auth ip +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to update +// - args: the arguments to update account auth ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountAuthIp(clusterId, accountName string, args *AuthIpArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithQueryParam("authip", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateAccountPrivileges - update account privileges +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to update +// - args: the arguments to update account privileges +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPrivileges(clusterId, accountName string, args *PrivilegesArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithQueryParam("privileges", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateAccountPassword - update account password +// +// PARAMS: +// - clusterId: cluster id +// - accountName: account name to update +// - args: the arguments to update account password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPassword(clusterId, accountName string, args *PasswordArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/account/"+accountName). + WithQueryParam("password", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// CreateDatabase - create database +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to create database +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateDatabase(clusterId string, args *CreateDatabaseArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/"+clusterId+"/database"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// DeleteDatabase - delete database +// +// PARAMS: +// - clusterId: cluster id +// - dbName: the database name to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDatabase(clusterId, dbName string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGaiadbUri()+"/"+clusterId+"/database/"+dbName). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// ListDatabase - list database +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *DatabaseList: the database list +func (c *Client) ListDatabase(clusterId string) (*DatabaseList, error) { + result := &DatabaseList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/database"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// CreateSnapshot - create snapshot +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateSnapshot(clusterId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/"+clusterId+"/snapshot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// ListSnapshot - list snapshot +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SnapshotList: the snapshot list +func (c *Client) ListSnapshot(clusterId string) (*SnapshotList, error) { + result := &SnapshotList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/snapshot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// UpdateSnapshotPolicy - update snapshot policy +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to update snapshot policy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSnapshotPolicy(clusterId string, args *UpdateSnapshotPolicyArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/snapshot/policy"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// GetSnapshotPolicy - get snapshot policy +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SnapshotPolicy: the snapshot policy +func (c *Client) GetSnapshotPolicy(clusterId string) (*SnapshotPolicy, error) { + result := &SnapshotPolicy{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/snapshot/policy"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// UpdateWhiteList - update white list +// +// PARAMS: +// - clusterId : cluster id +// - args: the arguments to update white list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateWhiteList(clusterId string, args *WhiteList) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/whitelist"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// GetWhiteList - get white list +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *WhiteListResult: the result of white list +func (c *Client) GetWhiteList(clusterId string) (*WhiteList, error) { + result := &WhiteList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/whitelist"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// CreateMultiactiveGroup - create multiactive group +// +// PARAMS: +// - args: the arguments to create multiactive group +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *CreateMultiactiveGroupResult: multiactive group result +func (c *Client) CreateMultiactiveGroup(args *CreateMultiactiveGroupArgs) (*CreateMultiactiveGroupResult, error) { + result := &CreateMultiactiveGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/multiactivegroup"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// DeleteMultiactiveGroup - elete multiactive group +// +// PARAMS: +// - groupId: multiactive group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteMultiactiveGroup(groupId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGaiadbUri()+"/multiactivegroup/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// RenameMultiactiveGroup - rename multiactive group +// +// PARAMS: +// - groupId: multiactive group id +// - args: the arguments to rename multiactive group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameMultiactiveGroup(groupId string, args *RenameMultiactiveGroupArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/multiactivegroup/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("multiActiveGroupName", ""). + WithBody(args). + Do() + + return err +} + +// MultiactiveGroupList - list multiactive group +// +// PARAMS: +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *MultiactiveGroupListResult: multiactive group list result +func (c *Client) MultiactiveGroupList() (*MultiactiveGroupListResult, error) { + result := &MultiactiveGroupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/multiactivegroup"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// MultiactiveGroupDetail - get multiactive group detail +// +// PARAMS: +// - groupId: multiactive group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *MultiactiveGroupDetailResult: multiactive group Detail result +func (c *Client) MultiactiveGroupDetail(groupId string) (*MultiactiveGroupDetailResult, error) { + result := &MultiactiveGroupDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/multiactivegroup/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetSyncStatus - get multiactive group sync status +// +// PARAMS: +// - groupId: multiactive group id +// - followerClusterId: the follower cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetSyncStatusResult: multiactive group Detail result +func (c *Client) GetSyncStatus(groupId, followerClusterId string) (*GetSyncStatusResult, error) { + result := &GetSyncStatusResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/multiactivegroup/"+groupId+"/syncStatus"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("followerClusterId", followerClusterId). + WithResult(result). + Do() + + return result, err +} + +// GroupExchange - group exchange +// +// PARAMS: +// - groupId: multiactive group id +// - args: the arguments to group exchange +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupExchange(groupId string, args *ExchangeArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/multiactivegroup/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("exchange", ""). + WithBody(args). + Do() + + return err +} + +// GetParamsList - get paremeters list +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetParamsListResult: get paremeters list result +func (c *Client) GetParamsList(clusterId string) (*GetParamsListResult, error) { + result := &GetParamsListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/cluster/"+clusterId+"/compute/params"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetParamsHistory - get paremeters history +// +// PARAMS: +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetParamsHistoryResult: get paremeters history result +func (c *Client) GetParamsHistory(clusterId string) (*GetParamsHistoryResult, error) { + result := &GetParamsHistoryResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/cluster/"+clusterId+"/compute/params/history"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// UpdateParams - update paremeters +// +// PARAMS: +// - clusterId: cluster id +// - args: the arguments to update paremeters +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateParams(clusterId string, args *UpdateParamsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/cluster/"+clusterId+"/compute/params"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// ListParamTemplate - list param template +// +// PARAMS: +// - args: the arguments to list param template +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ListParamTemplateResult: get paremeters list result +func (c *Client) ListParamTemplate(args *ListParamTempArgs) (*ListParamTempResult, error) { + result := &ListParamTempResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/paramTemplate/listParaTemplate"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("detail", strconv.Itoa(args.Detail)). + WithQueryParamFilter("type", args.Type). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithResult(result). + Do() + + return result, err +} + +// SaveAsParamTemplate - save as params template +// +// PARAMS: +// - args: the arguments to save as params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SaveAsParamTemplate(args *ParamTempArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/paramTemplate"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// GetTemplateApplyRecords - get template apply records +// +// PARAMS: +// - templateId: template id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetTemplateApplyRecordsResult: get template apply records result +func (c *Client) GetTemplateApplyRecords(templateId string) (*GetTemplateApplyRecordsResult, error) { + result := &GetTemplateApplyRecordsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId+"/apply"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// DeleteParamsFromTemp - delete params from template +// +// PARAMS: +// - templateId: template id +// - args: the params to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteParamsFromTemp(templateId string, args *Params) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId+"/delParams"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateParamTemplate - update param template +// +// PARAMS: +// - templateId: template id +// - args: the params to update +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateParamTemplate(templateId string, args *UpdateParamTplArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// ModifyParams - modify params +// +// PARAMS: +// - templateId: template id +// - args: the params to modify +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyParams(templateId string, args *ModifyParamsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId+"/modifyParams"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// DeleteParamTemplate - delete param template +// +// PARAMS: +// - templateId: template id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteParamTemplate(templateId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// CreateParamTemplate - create param template +// +// PARAMS: +// - args: the params to create param template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateParamTemplate(args *CreateParamTemplateArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/paramTemplate/create"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// GetParamTemplateDetail - get param template detail +// +// PARAMS: +// - templateId: template id +// - detail: detail type +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetParamTemplateDetail(templateId, detail string) (*ParamTemplateDetail, error) { + result := &ParamTemplateDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId). + WithQueryParamFilter("detail", detail). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetParamTemplateHistory - get param template history +// +// PARAMS: +// - templateId: template id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetParamTemplateHistory(templateId, action string) (*ParamTemplateHistory, error) { + result := &ParamTemplateHistory{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId+"/history"). + WithQueryParamFilter("action", action). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// ApplyParamTemplate - apply param template +// +// PARAMS: +// +// -templateId: template id +// - args: the params to apply param template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ApplyParamTemplate(templateId string, args *ApplyParamTemplateArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGaiadbUri()+"/paramTemplate/"+templateId+"/apply"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// UpdateMaintenTime - update maintenTime +// +// PARAMS: +// +// - clusterId: cluster id +// - args: the params to update maintenTime +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateMaintenTime(clusterId string, args *UpdateMaintenTimeArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/"+clusterId+"/maintentime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// GetMaintenTime - get maintenTime +// +// PARAMS: +// +// - clusterId: cluster id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *MaintenTime: maintenTime data +func (c *Client) GetMaintenTime(clusterId string) (*MaintenTimeDetail, error) { + result := &MaintenTimeDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/maintentime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlDetail - get slow sql detail +// +// PARAMS: +// +// - args: the params to get slow sql detail +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SlowSqlDetail: slow sql detail +func (c *Client) GetSlowSqlDetail(clusterId string, args *GetSlowSqlArgs) (*SlowSqlDetailDetail, error) { + result := &SlowSqlDetailDetail{} + params, err2 := getQueryParams(args) + if err2 != nil { + return nil, err2 + } + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/slowsql/gaiadb-s"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParams(params). + WithResult(result). + Do() + + return result, err +} + +// SlowSqlAdvice - slow sql advice +// +// PARAMS: +// +// - clusterId: cluster id +// - sqlId : sql id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SlowSqlAdviceDetail: slow sql advice detail +func (c *Client) SlowSqlAdvice(clusterId, sqlId string) (*SlowSqlAdviceDetail, error) { + result := &SlowSqlAdviceDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/"+clusterId+"/slowsql/gaiadb-s/"+sqlId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} + +// GetBinlogDetail - get binlog detail +// +// PARAMS: +// - binlogId : binlog id +// - args: the params to get binlog detail +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *BinlogDetail: binlog detail +func (c *Client) GetBinlogDetail(binlogId string, args *GetBinlogArgs) (*BinlogDetail, error) { + result := &BinlogDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/binlog/"+binlogId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("appID", args.AppID). + WithQueryParamFilter("logBackupType", args.LogBackupType). + WithResult(result). + Do() + + return result, err +} + +// GetBinlogList - get binlog list +// +// PARAMS: +// +// - args: the params to get binlog list +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *BinlogList: binlog List +func (c *Client) GetBinlogList(args *GetBinlogListArgs) (*BinlogList, error) { + result := &BinlogList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/binlog/list"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("appID", args.AppID). + WithQueryParamFilter("logBackupType", args.LogBackupType). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithQueryParamFilter("startDateTime", args.StartDateTime). + WithQueryParamFilter("endDateTime", args.EndDateTime). + WithResult(result). + Do() + + return result, err +} + +// ExecuteTaskNow - execute task now +// +// PARAMS: +// - taskId : task id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ExecuteTaskNow(taskId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/task/"+taskId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("executeNow", ""). + Do() + + return err +} + +// CancelTask - cancel task +// +// PARAMS: +// - taskId : task id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CancelTask(taskId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGaiadbUri()+"/task/"+taskId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParam("cancel", ""). + Do() + + return err +} + +// GetTaskList - get task list +// +// PARAMS: +// - args : the params to get task list +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *TaskList: task List +func (c *Client) GetTaskList(args *TaskListArgs) (*TaskList, error) { + result := &TaskList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/task"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("region", args.Region). + WithQueryParamFilter("startTime", args.StartTime). + WithQueryParamFilter("endTime", args.EndTime). + WithResult(result). + Do() + + return result, err +} + +// GetClusterByVpcId - get cluster by vpc id +// +// PARAMS: +// - vpcId : the params to get cluster by vpc id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ClusterList: cluster List +func (c *Client) GetClusterByVpcId(vpcId string) (*ClusterList, error) { + result := &ClusterList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/security/byVpc"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("vpcId", vpcId). + WithResult(result). + Do() + + return result, err +} + +// GetClusterByLbId - get cluster by lb id +// +// PARAMS: +// - LbId : the params to get cluster by lb id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ClusterList: cluster List +func (c *Client) GetClusterByLbId(lbIds string) (*ClusterList, error) { + result := &ClusterList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/security/byLb"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParamFilter("lbIds", lbIds). + WithResult(result). + Do() + + return result, err +} + +// GetOrderInfo - get order info +// +// PARAMS: +// - orderId : the params to get order info +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *OrderInfo: order info +func (c *Client) GetOrderInfo(orderId string) (*OrderInfo, error) { + result := &OrderInfo{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGaiadbUri()+"/order/"+orderId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/gaiadb/model.go b/bce-sdk-go/services/gaiadb/model.go new file mode 100644 index 0000000..04d9e69 --- /dev/null +++ b/bce-sdk-go/services/gaiadb/model.go @@ -0,0 +1,693 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package gaiadb + +type CreateClusterArgs struct { + ClientToken string `json:"-"` + ProductType string `json:"productType"` + Duration string `json:"duration,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + Number int `json:"number"` + InstanceParam InstanceParam `json:"instanceParam"` +} +type InstanceParam struct { + ReleaseVersion string `json:"releaseVersion"` + AllocatedCpuInCore int `json:"allocatedCpuInCore"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + InstanceAmount int `json:"instanceAmount"` + ProxyAmount int `json:"proxyAmount"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + SrcClusterId string `json:"srcClusterId,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` + Pit string `json:"pit,omitempty"` + LowerCaseTableNames string `json:"lowercaseTableNames,omitempty"` +} +type CreateResult struct { + OrderId string `json:"orderId"` + ClusterIds []string `json:"clusterIds"` +} + +type ClusterName struct { + ClusterName string `json:"clusterName"` +} + +type ResizeClusterArgs struct { + ResizeType string `json:"resizeType"` + SlaveId string `json:"slaveId,omitempty"` + AllocatedCpuInCore int `json:"allocatedCpuInCore,omitempty"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB,omitempty"` + ProxyAmount int `json:"proxyAmount,omitempty"` + InterfaceId int `json:"interfaceId,omitempty"` + Interfacee Interfacee `json:"interfacee"` +} +type Interfacee struct { + AddressName string `json:"addressName,omitempty"` + InstanceBinding []string `json:"instanceBinding,omitempty"` + ProxyAmount int `json:"proxyAmount,omitempty"` + ReadWriteMode string `json:"readWriteMode,omitempty"` + MasterReadable int `json:"masterReadable,omitempty"` +} + +type OrderId struct { + OrderId string `json:"orderId"` +} + +type Marker struct { + Marker string + MaxKeys int +} + +type ClusterListResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + Istruncated bool `json:"istruncated"` + Clusters []Cluster `json:"clusters"` +} + +type Cluster struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + Endpoint Endpoint `json:"endpoint"` + AllocatedCpuInCore int `json:"allocatedCpuInCore"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + InstanceStatus string `json:"instanceStatus"` + PubliclyAccessible bool `json:"publiclyAccessible"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + InstanceAmount int `json:"instanceAmount"` + ProxyAmount int `json:"proxyAmount"` + ProductType string `json:"productType"` + MultiActiveGroupId string `json:"multiActiveGroupId"` + MultiActiveGroupRole string `json:"multiActiveGroupRole"` + MultiActiveGroupName string `json:"multiActiveGroupName"` +} + +type Endpoint struct { + Port int `json:"port"` + Address string `json:"address"` + VnetIp string `json:"vnetIp,omitempty"` + InetIp string `json:"inetIp,omitempty"` +} + +type ClusterDetailResult struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + Endpoint Endpoint `json:"endpoint"` + AllocatedCpuInCore int `json:"allocatedCpuInCore"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + InstanceStatus string `json:"instanceStatus"` + PubliclyAccessible bool `json:"publiclyAccessible"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + InstanceAmount int `json:"instanceAmount"` + ProductType string `json:"productType"` + MultiActiveGroupId string `json:"multiActiveGroupId"` + MultiActiveGroupRole string `json:"multiActiveGroupRole"` + MultiActiveGroupName string `json:"multiActiveGroupName"` + ComputeList []ComputeNode `json:"computeList"` +} +type ComputeNode struct { + Role string `json:"role"` + InstanceId string `json:"instanceId"` + InstanceShortId string `json:"instanceShortId"` + InstanceUniqueId string `json:"instanceUniqueId"` + Status string `json:"status"` +} + +type ClusterCapacityResult struct { + TotalFree int `json:"totalFree"` + TotalCapacity int `json:"totalCapacity"` +} + +type QueryPriceArgs struct { + Number int `json:"number"` + InstanceParam InstanceInfo `json:"instanceParam"` + ProductType string `json:"productType"` + Duration int `json:"duration"` +} + +type InstanceInfo struct { + ReleaseVersion string `json:"releaseVersion"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + AllocatedCpuInCore int `json:"allocatedCpuInCore"` + InstanceAmount int `json:"instanceAmount"` + ProxyAmount int `json:"proxyAmount"` +} + +type PriceResult struct { + Price float32 `json:"price"` + CatalogPrice float32 `json:"catalogPrice,omitempty"` +} + +type QueryResizePriceArgs struct { + ClusterId string `json:"clusterId"` + ResizeType string `json:"resizeType"` + SlaveId string `json:"slaveId"` + AllocatedCpuInCore int `json:"allocatedCpuInCore"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + ProxyAmount int `json:"proxyAmount"` + InterfaceId string `json:"interfaceId"` + Interfacee Interfacee `json:"interfacee"` +} + +type RebootInstanceArgs struct { + ExecuteAction string `json:"executeAction"` +} + +type BindTagsArgs struct { + Resources []Resource `json:"resources"` +} + +type Resource struct { + ResourceId string `json:"resourceId"` + Tags []Tag `json:"tags"` +} + +type Tag struct { + TagKey string `json:"tagKey"` + TagValue string `json:"tagValue"` +} + +type InterfaceListResult struct { + Interfaces []Interface `json:"interfaces"` +} +type Interface struct { + AppId string `json:"appId"` + InterfaceId string `json:"interfaceId"` + Status string `json:"status"` + CreateTime string `json:"createTime"` + InstanceBinding []string `json:"instanceBinding"` + NewInstanceAutoJoin int `json:"newInstanceAutoJoin"` + ReadWriteMode string `json:"readWriteMode"` + InterfaceType string `json:"interfaceType"` + MasterReadable int `json:"masterReadable"` + Access Access `json:"access"` + Flavor Flavor `json:"flavor"` + Proxies []Proxy `json:"proxies"` +} +type Proxy struct { + Id int `json:"id"` + ProxyId string `json:"proxyId"` +} +type Flavor struct { + RamInMB int `json:"ramInMB"` + DiskInGB string `json:"diskInGB"` + CpuInCore int `json:"cpuInCore"` +} +type Access struct { + Name string `json:"name"` + DnsName string `json:"dnsName"` + AddressName string `json:"addressName"` + Eip string `json:"eip"` +} + +type UpdateDnsNameArgs struct { + InterfaceId string `json:"instanceId"` + DnsName string `json:"dnsName"` +} + +type UpdateInterfaceArgs struct { + InterfaceId string `json:"instanceId"` + Interface InterfaceInfo `json:"interface"` +} +type InterfaceInfo struct { + MasterReadable int `json:"masterReadable"` + AddressName string `json:"addressName"` + InstanceBinding []string `json:"instanceBinding"` +} + +type NewInstanceAutoJoinArgs struct { + AutoJoinRequestItems []AutoJoinRequestItem `json:"autoJoinRequestItems"` +} +type AutoJoinRequestItem struct { + NewInstanceAutoJoin string `json:"newInstanceAutoJoin"` + InterfaceId string `json:"interfaceId"` +} +type WhiteList struct { + AuthIps []string `json:"authIps"` + Etag string `json:"etag"` +} + +type ClusterSwitchArgs struct { + ExecuteAction string `json:"executeAction"` + SecondaryInstanceId string `json:"secondaryInstanceId"` +} + +type ClusterSwitchResult struct { + Switch TaskId `json:"switch"` +} +type TaskId struct { + TaskId int `json:"taskId"` +} + +type CreateAccountArgs struct { + AccountName string `json:"accountName"` + Password string `json:"password"` + AccountType string `json:"accountType"` + Remark string `json:"remark"` +} + +type AccountDetail struct { + Account AccountInfo `json:"account"` +} +type AccountInfo struct { + AccountName string `json:"accountName"` + Remark string `json:"remark"` + AccountStatus string `json:"accountStatus"` + AccountType string `json:"accountType"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` + Etag string `json:"etag"` +} +type DatabasePrivilege struct { + DbName string `json:"dbName"` + AuthType string `json:"authType"` + Privileges []string `json:"privileges"` +} + +type AccountList struct { + Accounts []AccountInfo `json:"accounts"` +} + +type RemarkArgs struct { + Remark string `json:"remark"` + Etag string `json:"etag"` +} + +type AuthIpArgs struct { + Action string `json:"action"` + Value AuthIp `json:"value"` + Etag string `json:"etag"` +} +type AuthIp struct { + Authip []string `json:"authip"` + Authbns []string `json:"authbns"` +} + +type PrivilegesArgs struct { + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` + Etag string `json:"etag"` +} +type PasswordArgs struct { + Password string `json:"password"` + Etag string `json:"etag"` +} + +type CreateDatabaseArgs struct { + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + Remark string `json:"remark"` +} + +type DatabaseList struct { + Databases []DatabaseInfo `json:"databases"` +} +type DatabaseInfo struct { + DbName string `json:"dbName"` + Remark string `json:"remark"` + DbStatus string `json:"dbStatus"` + CharacterSetName string `json:"characterSetName"` + AccountPrivileges []AccountPrivilege `json:"accountPrivileges"` +} +type AccountPrivilege struct { + AccountName string `json:"accountName"` + AuthType string `json:"authType"` +} + +type SnapshotList struct { + Snapshots []Snapshot `json:"snapshots"` +} +type Snapshot struct { + SnapshotId string `json:"snapshotId"` + AppId string `json:"appId"` + SnapshotSizeInBytes int `json:"snapshotSizeInBytes"` + SnapshotType string `json:"snapshotType"` + SnapshotStatus string `json:"snapshotStatus"` + SnapshotStartTime string `json:"snapshotStartTime"` + SnapshotEndTime string `json:"snapshotEndTime"` + SnapshotDataTime string `json:"snapshotDataTime"` +} + +type UpdateSnapshotPolicyArgs struct { + DataBackupWeekDay []string `json:"dataBackupWeekDay"` + DataBackupRetainStrategys []DataBackupRetainStrategy `json:"dataBackupRetainStrategys"` + DataBackupTime string `json:"dataBackupTime"` +} +type DataBackupRetainStrategy struct { + StartSeconds int `json:"startSeconds"` + RetainCount string `json:"retainCount"` + Precision int `json:"precision"` + EndSeconds int `json:"endSeconds"` +} +type SnapshotPolicy struct { + Policys []SnapshotPolicyPolicy `json:"policys"` +} +type SnapshotPolicyPolicy struct { + PolicyID string `json:"policyID"` + PolicyName string `json:"policyName"` + DataBackupWeekDay []string `json:"dataBackupWeekDay"` + DataBackupTime string `json:"dataBackupTime"` + DataBackupRetainStrategys []DataBackupRetainStrategy `json:"dataBackupRetainStrategys"` + LogBackupRetainDays int `json:"logBackupRetainDays"` + SpeedLimitBytesPerSec int `json:"speedLimitBytesPerSec"` + EncryptStrategy EncryptStrategy `json:"encryptStrategy"` +} +type EncryptStrategy struct { + EncryptEnable bool `json:"encryptEnable"` + KeyManagementType string `json:"keyManagementType"` + KeyManagementServiceName string `json:"keyManagementServiceName"` + SecretKeyID string `json:"secretKeyID"` +} +type CreateMultiactiveGroupArgs struct { + LeaderClusterId string `json:"leaderClusterId"` + MultiActiveGroupName string `json:"multiActiveGroupName"` +} +type CreateMultiactiveGroupResult struct { + MultiActiveGroupId string `json:"multiActiveGroupId"` +} + +type RenameMultiactiveGroupArgs struct { + MultiActiveGroupName string `json:"multiActiveGroupName"` +} + +type MultiactiveGroupListResult struct { + MultiActiveGroupInfoList []MultiactiveGroupInfo `json:"multiActiveGroupInfoList"` +} +type MultiactiveGroupInfo struct { + MultiActiveGroupId string `json:"multiActiveGroupId"` + MultiActiveGroupName string `json:"multiActiveGroupName"` + Status string `json:"status"` + CreateTime string `json:"createTime"` + LeaderCluster LeaderCluster `json:"leaderCluster"` + FollowerClusterList []FollowerCluster `json:"followerClusterList"` +} +type LeaderCluster struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + Region string `json:"region"` + Azone string `json:"azone"` + Status string `json:"status"` + ScaleOutFlag bool `json:"scaleOutFlag"` +} +type FollowerCluster struct { + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + Region string `json:"region"` + Azone string `json:"azone"` + Status string `json:"status"` +} + +type MultiactiveGroupDetailResult struct { + MultiActiveGroupInfo MultiactiveGroupInfo `json:"multiActiveGroupInfo"` +} + +type GetSyncStatusResult struct { + DelayTime int `json:"delayTime"` + Status string `json:"status"` +} + +type ExchangeArgs struct { + ExecuteAction string `json:"executeAction"` + NewLeaderClusterId string `json:"newLeaderClusterId"` +} + +type GetParamsListResult struct { + Params []Param `json:"params"` +} +type Param struct { + Status string `json:"status"` + UpdateTime string `json:"updateTime"` + Type string `json:"type"` + Name string `json:"name"` + CaseSensitive bool `json:"caseSensitive"` + Modifiable bool `json:"modifiable"` + CreateTime string `json:"createTime"` + Attention string `json:"attention"` + Precision int `json:"precision"` + Value string `json:"value"` + AllowedValues string `json:"allowedValues"` + BestValue string `json:"bestValue"` + Id int64 `json:"id"` + Description string `json:"description"` + AllowEmpty bool `json:"allowEmpty"` +} + +type GetParamsHistoryResult struct { + Records []Record `json:"records"` +} +type Record struct { + Status string `json:"status"` + FinishTime string `json:"finishTime"` + Name string `json:"name"` + StartTime string `json:"startTime"` + AfterValue string `json:"afterValue"` + BeforeValue string `json:"beforeValue"` + TaskId int64 `json:"taskId"` + Id int64 `json:"id"` +} + +type UpdateParamsArgs struct { + Params ParamsItem `json:"params"` + Timing string `json:"timing"` +} + +type ParamsItem map[string]interface{} + +type ListParamTempArgs struct { + Detail int `json:"detail,omitempty"` + Type string `json:"type,omitempty"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` +} +type ListParamTempResult struct { + ParamTemplates []ParamTemplate `json:"paramTemplates"` +} +type ParamTemplate struct { + Uuid string `json:"uuid"` + Name string `json:"name"` + Type string `json:"type"` + Version string `json:"version"` + Description string `json:"description"` + IsReboot bool `json:"isReboot"` + ParamAmount int `json:"paramAmount"` + Status string `json:"status"` + IsSystem bool `json:"isSystem"` + UserId string `json:"userId,omitempty"` + UserName string `json:"userName,omitempty"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type ParamTempArgs struct { + Type string `json:"type"` + Version string `json:"version"` + Description string `json:"description"` + Name string `json:"name"` + Source string `json:"source"` +} + +type GetTemplateApplyRecordsResult struct { + Records []ApplyRecord `json:"records"` +} +type ApplyRecord struct { + Status string `json:"status"` + FinishTime string `json:"finishTime"` + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + ClusterStatus string `json:"clusterStatus"` + ClusterRegion string `json:"clusterRegion"` + StartTime string `json:"startTime"` + ErrMsg string `json:"errMsg"` +} +type Params struct { + Params []string `json:"params"` +} + +type UpdateParamTplArgs struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type ModifyParamsArgs struct { + Params ParamsItem `json:"params"` +} + +type CreateParamTemplateArgs struct { + Name string `json:"name"` + Type string `json:"type"` + Version string `json:"version"` + Description string `json:"description"` +} +type ParamTemplateDetail struct { + ParamTemplate ParamTemplate `json:"paramTemplate"` +} +type ParamTemplateHistory struct { + Records []ParamTemplateRecord `json:"records"` +} +type ParamTemplateRecord struct { + Action string `json:"action"` + Name string `json:"name"` + ParamTemplateId string `json:"paramTemplateId"` + RequestId string `json:"requestId"` + Id int64 `json:"id"` + ErrMsg string `json:"errMsg"` + AfterValue string `json:"afterValue"` + StartTime string `json:"startTime"` + FinishTime string `json:"finishTime"` +} + +type ApplyParamTemplateArgs struct { + Timing string `json:"timing"` + Clusters ParamsItem `json:"clusters"` +} + +type UpdateMaintenTimeArgs struct { + Period string `json:"period"` + StartTime string `json:"startTime"` + Duration int `json:"duration"` +} + +type MaintenTimeDetail struct { + MaintenTime MaintenTime `json:"maintenTime"` +} +type MaintenTime struct { + ClusterId string `json:"clusterId"` + Period string `json:"period"` + StartTime string `json:"startTime"` + Duration int `json:"duration"` +} + +type GetSlowSqlArgs struct { + Page string `json:"page,omitempty"` + PageSize string `json:"pageSize,omitempty"` + Offset string `json:"offset,omitempty"` + Sort string `json:"sort,omitempty"` + NodeId string `json:"nodeId,omitempty"` + Engine string `json:"engine,omitempty"` + Schema string `json:"schema,omitempty"` + Digest string `json:"digest,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type SlowSqlDetailDetail struct { + Items []SlowSqlDetailItem `json:"items"` + TotalCount int64 `json:"totalCount"` +} +type SlowSqlDetailItem struct { + AffectedRows int64 `json:"affectedRows"` + App string `json:"app"` + ClientHost string `json:"clientHost"` + ClientIP string `json:"clientIP"` + Cluster string `json:"cluster"` + ConnectionId int `json:"connectionId"` + CurrentDB string `json:"currentDB"` + Digest string `json:"digest"` + Duration string `json:"duration"` + ExaminedRows int64 `json:"examinedRows"` + LockTime string `json:"lockTime"` + Node string `json:"node"` + NumRows int64 `json:"numRows"` + SqlId string `json:"sqlId"` + Start string `json:"start"` + User string `json:"user"` +} + +type SlowSqlAdviceDetail struct { + IndexAdvice []SlowSqlAdvice `json:"indexAdvice"` + StatementAdvice []SlowSqlAdvice `json:"statementAdvice"` +} +type SlowSqlAdvice struct { + Advice string `json:"advice"` + Title string `json:"title"` +} + +type GetBinlogArgs struct { + AppID string `json:"appID"` + LogBackupType string `json:"logBackupType"` +} +type BinlogDetail struct { + Name string `json:"name"` + LogStartDateTime string `json:"logStartDateTime"` + LogEndDateTime string `json:"logEndDateTime"` + LogSizeBytes int64 `json:"logSizeBytes"` + OuterLinks []string `json:"outerLinks"` +} +type BinlogList struct { + LogBackups []BinlogItem `json:"logBackups"` + Pagination Pagination `json:"pagination"` +} +type BinlogItem struct { + Id string `json:"id"` + Name string `json:"name"` + LogStartDateTime string `json:"logStartDateTime"` + LogEndDateTime string `json:"logEndDateTime"` + LogSizeBytes int64 `json:"logSizeBytes"` +} +type Pagination struct { + TotalKeys int64 `json:"totalKeys"` +} + +type GetBinlogListArgs struct { + AppID string `json:"appID"` + LogBackupType string `json:"logBackupType"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + StartDateTime string `json:"startDateTime"` + EndDateTime string `json:"endDateTime"` +} + +type TaskListArgs struct { + Region string `json:"region,omitempty"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime,omitempty"` +} +type TaskList struct { + Task []TaskItem `json:"task"` +} +type TaskItem struct { + TaskId int64 `json:"taskId"` + Region string `json:"region"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + TaskName string `json:"taskName"` + ClusterId string `json:"clusterId"` + ClusterName string `json:"clusterName"` + TaskStatus string `json:"taskStatus"` + SupportedOperate []string `json:"supportedOperate"` +} + +type ClusterList struct { + Clusters []ClusterItem `json:"clusters"` +} +type ClusterItem struct { + ClusterId string `json:"clusterId"` + Name string `json:"name"` + LbInfos []LbInfos `json:"lbInfos"` +} +type LbInfos struct { + LbId string `json:"lbId"` + LbIp string `json:"lbIp"` + Eip string `json:"eip"` +} +type OrderInfo struct { + Status string `json:"status"` +} diff --git a/bce-sdk-go/services/gaiadb/util.go b/bce-sdk-go/services/gaiadb/util.go new file mode 100644 index 0000000..091db2a --- /dev/null +++ b/bce-sdk-go/services/gaiadb/util.go @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of GAIADB service +package gaiadb + +import ( + "encoding/hex" + "fmt" + + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} diff --git a/bce-sdk-go/services/havip/client.go b/bce-sdk-go/services/havip/client.go new file mode 100644 index 0000000..b6a2486 --- /dev/null +++ b/bce-sdk-go/services/havip/client.go @@ -0,0 +1,49 @@ +/* + * Copyright Baidu, Inc. + * + * 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 havip + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_HAVIP = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_HAVIP_URL = "/havip" +) + +// Client of ESG service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_HAVIP + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForHaVip() string { + return URI_PREFIX + REQUEST_HAVIP_URL +} + +func getURLForHaVipId(haVipId string) string { + return getURLForHaVip() + "/" + haVipId +} diff --git a/bce-sdk-go/services/havip/client_test.go b/bce-sdk-go/services/havip/client_test.go new file mode 100644 index 0000000..bd4e79e --- /dev/null +++ b/bce-sdk-go/services/havip/client_test.go @@ -0,0 +1,186 @@ +/* + * Copyright Baidu, Inc. + * + * 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 havip + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + HAVIP_CLIENT *Client + region string +) + +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + //for i := 0; i < 7; i++ { + // f = filepath.Dir(f) + //} + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + _ = decoder.Decode(confObj) + HAVIP_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + region = confObj.Endpoint[4:6] +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateHaVip(t *testing.T) { + args := &CreateHaVipArgs{ + Name: "havipGoSdkTest", + Description: "go sdk test", + SubnetId: "sbn-mnnhbyd2tbqr", + PrivateIpAddress: "192.168.1.3", + ClientToken: getClientToken(), + } + result, err := HAVIP_CLIENT.CreateHaVip(args) + ExpectEqual(t.Errorf, nil, err) + HaVipId := result.HaVipId + log.Debug(HaVipId) +} + +func TestClient_ListHaVips(t *testing.T) { + args := &ListHaVipArgs{} + res, err := HAVIP_CLIENT.ListHaVip(args) + fmt.Println(res) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestClient_GetHaVipDetail(t *testing.T) { + result, err := HAVIP_CLIENT.GetHaVipDetail("havip-ied0wq4fs8va") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_UpdateHaVip(t *testing.T) { + args := &UpdateHaVipArgs{ + HaVipId: "havip-yxz7bx3tskqs", + ClientToken: getClientToken(), + Name: "havipGoSdkTest2", + } + err := HAVIP_CLIENT.UpdateHaVip(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteHaVip(t *testing.T) { + args := &DeleteHaVipArgs{ + HaVipId: "havip-qzctfgy0uadj", + ClientToken: getClientToken(), + } + err := HAVIP_CLIENT.DeleteHaVip(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_HaVipAttachInstance(t *testing.T) { + args := &HaVipInstanceArgs{ + HaVipId: "havip-yxz7bx3tskqs", + ClientToken: getClientToken(), + InstanceIds: []string{ + "eni-xgg3pfhw384n", + }, + InstanceType: "ENI", + } + err := HAVIP_CLIENT.HaVipAttachInstance(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_HaVipDetachInstance(t *testing.T) { + args := &HaVipInstanceArgs{ + HaVipId: "havip-yxz7bx3tskqs", + ClientToken: getClientToken(), + InstanceIds: []string{ + "eni-xgg3pfhw384n", + }, + InstanceType: "ENI", + } + err := HAVIP_CLIENT.HaVipDetachInstance(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_HaVipBindPublicIp(t *testing.T) { + args := &HaVipBindPublicIpArgs{ + HaVipId: "havip-ied0wq4fs8va", + ClientToken: getClientToken(), + PublicIpAddress: "110.242.73.217", + } + err := HAVIP_CLIENT.HaVipBindPublicIp(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_HaVipUnbindPublicIp(t *testing.T) { + args := &HaVipUnbindPublicIpArgs{ + HaVipId: "havip-ied0wq4fs8va", + ClientToken: getClientToken(), + } + err := HAVIP_CLIENT.HaVipUnbindPublicIp(args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/havip/havip.go b/bce-sdk-go/services/havip/havip.go new file mode 100644 index 0000000..b98d319 --- /dev/null +++ b/bce-sdk-go/services/havip/havip.go @@ -0,0 +1,226 @@ +/* + * Copyright Baidu, Inc. + * + * 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 havip + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +// CreateHaVip - create an havip with the specific parameters +// +// PARAMS: +// - args: the arguments to create an havip +// +// RETURNS: +// - *CreateHavipResult: the result of create havip +// - error: nil if success otherwise the specific error +func (c *Client) CreateHaVip(args *CreateHaVipArgs) (*CreateHavipResult, error) { + if args == nil { + return nil, fmt.Errorf("The createHaVipArgs cannot be nil.") + } + + result := &CreateHavipResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVip()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListHaVip - list all havip with the specific parameters +// +// PARAMS: +// - args: the arguments to list all havip +// +// RETURNS: +// - *ListHaVipResult: the result of list all havip +// - error: nil if success otherwise the specific error +func (c *Client) ListHaVip(args *ListHaVipArgs) (*ListHaVipResult, error) { + if args == nil { + return nil, fmt.Errorf("The listHaVipArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListHaVipResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForHaVip()). + WithMethod(http.GET). + WithQueryParamFilter("vpcId", args.VpcId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + + err := builder.WithResult(result).Do() + + return result, err +} + +// GetHaVipDetail - get the havip detail +// +// PARAMS: +// - haVipId: the specific haVipId +// +// RETURNS: +// - *HaVip: the havip +// - error: nil if success otherwise the specific error +func (c *Client) GetHaVipDetail(haVipId string) (*HaVip, error) { + if haVipId == "" { + return nil, fmt.Errorf("The haVipId cannot be empty.") + } + + result := &HaVip{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(haVipId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateHaVip - update an havip +// +// PARAMS: +// - UpdateHaVipArgs: the arguments to update an havip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateHaVip(args *UpdateHaVipArgs) error { + if args == nil { + return fmt.Errorf("The updateHaVipArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("modifyAttribute", ""). + Do() +} + +// DeleteHaVip - delete an havip +// +// PARAMS: +// - DeleteHaVipArgs: the arguments to delete an havip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteHaVip(args *DeleteHaVipArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// HaVipAttachInstance - havip attach instance +// +// PARAMS: +// - args: the arguments to attach instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) HaVipAttachInstance(args *HaVipInstanceArgs) error { + if args == nil { + return fmt.Errorf("The haVipInstanceArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.PUT). + WithQueryParam("attach", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// HaVipDetachInstance - havip detach instance +// +// PARAMS: +// - args: the arguments to detach instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) HaVipDetachInstance(args *HaVipInstanceArgs) error { + if args == nil { + return fmt.Errorf("The haVipInstanceArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.PUT). + WithQueryParam("detach", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// HaVipBindPublicIp - havip bind public ip +// +// PARAMS: +// - args: the arguments to bind public ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) HaVipBindPublicIp(args *HaVipBindPublicIpArgs) error { + if args == nil { + return fmt.Errorf("The haVipBindPublicIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.PUT). + WithQueryParam("bindPublicIp", ""). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} + +// HaVipUnbindPublicIp - havip unbind public ip +// +// PARAMS: +// - args: the arguments to unbind public ip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) HaVipUnbindPublicIp(args *HaVipUnbindPublicIpArgs) error { + if args == nil { + return fmt.Errorf("The HaVipUnbindPublicIpArgs cannot be nil.") + } + + err := bce.NewRequestBuilder(c). + WithURL(getURLForHaVipId(args.HaVipId)). + WithMethod(http.PUT). + WithQueryParam("unbindPublicIp", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() + + return err +} diff --git a/bce-sdk-go/services/havip/model.go b/bce-sdk-go/services/havip/model.go new file mode 100644 index 0000000..6258ec1 --- /dev/null +++ b/bce-sdk-go/services/havip/model.go @@ -0,0 +1,90 @@ +/* + * Copyright Baidu, Inc. + * + * 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 havip + +type CreateHaVipArgs struct { + ClientToken string `json:"-"` + SubnetId string `json:"subnetId"` + Name string `json:"name"` + PrivateIpAddress string `json:"privateIpAddress"` + Description string `json:"description"` +} + +type CreateHavipResult struct { + HaVipId string `json:"haVipId"` +} + +type HaVipBindedInstance struct { + InstanceId string `json:"instanceId"` + InstanceType string `json:"instanceType"` + Master bool `json:"master"` +} + +type HaVip struct { + HaVipId string `json:"haVipId"` + Name string `json:"name"` + Description string `json:"description"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Status string `json:"status"` + PrivateIpAddress string `json:"privateIpAddress"` + PublicIpAddress string `json:"publicIpAddress,omitempty"` + BindedInstances []HaVipBindedInstance `json:"bindedInstances,omitempty"` + CreatedTime string `json:"createdTime"` +} + +type ListHaVipArgs struct { + VpcId string + Marker string + MaxKeys int +} + +type ListHaVipResult struct { + HaVips []HaVip `json:"haVips"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type UpdateHaVipArgs struct { + HaVipId string `json:"-"` + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +type DeleteHaVipArgs struct { + HaVipId string + ClientToken string +} + +type HaVipInstanceArgs struct { + HaVipId string `json:"-"` + InstanceIds []string `json:"instanceIds"` + InstanceType string `json:"instanceType"` + ClientToken string `json:"-"` +} + +type HaVipBindPublicIpArgs struct { + HaVipId string `json:"-"` + ClientToken string `json:"-"` + PublicIpAddress string `json:"publicIpAddress"` +} + +type HaVipUnbindPublicIpArgs struct { + HaVipId string + ClientToken string +} diff --git a/bce-sdk-go/services/iam/api/accesskey.go b/bce-sdk-go/services/iam/api/accesskey.go new file mode 100644 index 0000000..0dd6134 --- /dev/null +++ b/bce-sdk-go/services/iam/api/accesskey.go @@ -0,0 +1,130 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateAccessKey(cli bce.Client, userName string) (*CreateAccessKeyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getAccessKeyUri(userName)) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateAccessKeyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DisableAccessKey(cli bce.Client, userName, accessKeyId string) (*UpdateAccessKeyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getAccessKeyWithIdUri(userName, accessKeyId)) + req.SetParam(ACCESSKEY_STATUS_DISABLE, "") + req.SetMethod(http.PUT) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &UpdateAccessKeyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func EnableAccessKey(cli bce.Client, userName, accessKeyId string) (*UpdateAccessKeyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getAccessKeyWithIdUri(userName, accessKeyId)) + req.SetParam(ACCESSKEY_STATUS_ENABLE, "") + req.SetMethod(http.PUT) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &UpdateAccessKeyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeleteAccessKey(cli bce.Client, userName, accessKeyId string) error { + req := &bce.BceRequest{} + req.SetUri(getAccessKeyWithIdUri(userName, accessKeyId)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListAccessKey(cli bce.Client, userName string) (*ListAccessKeyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getAccessKeyUri(userName)) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListAccessKeyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func getAccessKeyUri(userName string) string { + return URI_PREFIX + URI_USER + "/" + userName + URI_ACCESSKEY +} + +func getAccessKeyWithIdUri(userName, accessKeyId string) string { + return getAccessKeyUri(userName) + "/" + accessKeyId +} diff --git a/bce-sdk-go/services/iam/api/constants.go b/bce-sdk-go/services/iam/api/constants.go new file mode 100644 index 0000000..adbe01c --- /dev/null +++ b/bce-sdk-go/services/iam/api/constants.go @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +const ( + URI_PREFIX = "/v1" + URI_USER = "/user" + URI_GROUP = "/group" + URI_POLICY = "/policy" + URI_ACCESSKEY = "/accesskey" + URI_ROLE = "/role" + + SUB_USER = "/subUser" + + POLICY_TYPE_SYSTEM = "System" + POLICY_TYPE_CUSTOM = "Custom" + + ACCESSKEY_STATUS_ENABLE = "enable" + ACCESSKEY_STATUS_DISABLE = "disable" +) diff --git a/bce-sdk-go/services/iam/api/group.go b/bce-sdk-go/services/iam/api/group.go new file mode 100644 index 0000000..d55061e --- /dev/null +++ b/bce-sdk-go/services/iam/api/group.go @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateGroup(cli bce.Client, body *bce.Body) (*CreateGroupResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_GROUP) + req.SetMethod(http.POST) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CreateGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func GetGroup(cli bce.Client, name string) (*GetGroupResult, error) { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(name)) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func UpdateGroup(cli bce.Client, name string, body *bce.Body) (*UpdateGroupResult, error) { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(name)) + req.SetMethod(http.PUT) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdateGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeleteGroup(cli bce.Client, name string) error { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(name)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListGroup(cli bce.Client) (*ListGroupResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_GROUP) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func AddUserToGroup(cli bce.Client, userName string, groupName string) error { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_GROUP + "/" + groupName + URI_USER + "/" + userName) + req.SetMethod(http.PUT) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func DeleteUserFromGroup(cli bce.Client, userName string, groupName string) error { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_GROUP + "/" + groupName + URI_USER + "/" + userName) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListUsersInGroup(cli bce.Client, name string) (*ListUsersInGroupResult, error) { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(name) + URI_USER) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListUsersInGroupResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func ListGroupsForUser(cli bce.Client, name string) (*ListGroupsForUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name) + URI_GROUP) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListGroupsForUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func getGroupUri(name string) string { + return URI_PREFIX + URI_GROUP + "/" + name +} diff --git a/bce-sdk-go/services/iam/api/model.go b/bce-sdk-go/services/iam/api/model.go new file mode 100644 index 0000000..2343e15 --- /dev/null +++ b/bce-sdk-go/services/iam/api/model.go @@ -0,0 +1,227 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import "time" + +type UserModel struct { + Id string `json:"id"` + Name string `json:"name"` + CreateTime time.Time `json:"createTime"` + Description string `json:"description"` + Enabled bool `json:"enabled"` +} + +type CreateUserArgs struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type CreateUserResult UserModel + +type GetUserResult UserModel + +type UpdateUserArgs struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +type UpdateUserResult UserModel + +type ListUserResult struct { + Users []UserModel `json:"users"` +} + +type AccessKeyModel struct { + Id string `json:"id"` + Secret string `json:"secret"` + CreateTime time.Time `json:"createTime"` + LastUsedTime time.Time `json:"lastUsedTime"` + Enabled bool `json:"enabled"` + Description string `json:"description"` +} + +type CreateAccessKeyResult AccessKeyModel + +type UpdateAccessKeyResult AccessKeyModel + +type ListAccessKeyResult struct { + AccessKeys []AccessKeyModel `json:"accessKeys"` +} + +type LoginProfileModel struct { + Password string `json:"password,omitempty"` + NeedResetPassword bool `json:"needResetPassword"` + EnabledLoginMfa bool `json:"enabledLoginMfa"` + LoginMfaType string `json:"loginMfaType,omitempty"` + ThirdPartyType bool `json:"thirdPartyType,omitempty"` + ThirdPartyAccount bool `json:"thirdPartyAccount,omitempty"` +} + +type UpdateUserLoginProfileArgs LoginProfileModel + +type UpdateUserLoginProfileResult LoginProfileModel + +type GetUserLoginProfileResult LoginProfileModel + +type GroupModel struct { + Id string `json:"id"` + Name string `json:"name"` + CreateTime time.Time `json:"createTime"` + Description string `json:"description"` +} + +type CreateGroupArgs struct { + Name string `json:"name"` + Description string `json:"description"` +} + +type CreateGroupResult GroupModel + +type GetGroupResult GroupModel + +type UpdateGroupArgs struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +type UpdateGroupResult GroupModel + +type ListGroupResult struct { + Groups []GroupModel `json:"groups"` +} + +type ListUsersInGroupResult ListUserResult + +type ListGroupsForUserResult ListGroupResult + +type AclEntry struct { + Eid string `json:"eid,omitempty"` + Service string `json:"service"` + Region string `json:"region"` + Permission []string `json:"permission"` + Resource []string `json:"resource,omitempty"` + Grantee []Grantee `json:"grantee,omitempty"` + Effect string `json:"effect"` +} + +type Grantee struct { + ID string `json:"id"` +} + +type Acl struct { + Id string `json:"id,omitempty"` + AccessControlList []AclEntry `json:"accessControlList"` +} + +type PolicyModel struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + CreateTime time.Time `json:"createTime"` + Description string `json:"description"` + Document string `json:"document"` +} + +type CreatePolicyArgs struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Document string `json:"document"` +} + +type CreatePolicyResult PolicyModel + +type GetPolicyResult PolicyModel + +type ListPolicyResult struct { + Policies []PolicyModel `json:"policies"` +} + +type AttachPolicyToUserArgs struct { + UserName string `json:"userName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type DetachPolicyFromUserArgs struct { + UserName string `json:"userName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type AttachPolicyToGroupArgs struct { + GroupName string `json:"groupName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type DetachPolicyFromGroupArgs struct { + GroupName string `json:"groupName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type AttachPolicyToRoleArgs struct { + RoleName string `json:"roleName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type DetachPolicyToRoleArgs struct { + RoleName string `json:"roleName"` + PolicyName string `json:"policyName"` + PolicyType string `json:"policyType,omitempty"` +} + +type RoleModel struct { + Id string `json:"id"` + Name string `json:"name"` + CreateTime time.Time `json:"createTime"` + Description string `json:"description"` + AssumeRolePolicyDocument string `json:"assumeRolePolicyDocument"` +} + +type CreateRoleArgs struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + AssumeRolePolicyDocument string `json:"assumeRolePolicyDocument"` +} + +type UpdateRoleArgs struct { + Description string `json:"description"` + AssumeRolePolicyDocument string `json:"assumeRolePolicyDocument,omitempty"` +} + +type CreateRoleResult RoleModel + +type GetRoleResult RoleModel + +type UpdateRoleResult RoleModel + +type ListRoleResult struct { + Roles []RoleModel `json:"roles"` +} + +type UserSwitchMfaArgs struct { + UserName string `json:"userName,omitempty"` + EnabledMfa bool `json:"enabledMfa"` + MfaType string `json:"mfaType,omitempty"` +} + +type UpdateSubUserArgs struct { + Password string `json:"password,omitempty"` + Provider string `json:"provider,omitempty"` + Enable bool `json:"enable"` +} diff --git a/bce-sdk-go/services/iam/api/policy.go b/bce-sdk-go/services/iam/api/policy.go new file mode 100644 index 0000000..8a7f4ec --- /dev/null +++ b/bce-sdk-go/services/iam/api/policy.go @@ -0,0 +1,285 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreatePolicy(cli bce.Client, body *bce.Body) (*CreatePolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_POLICY) + req.SetMethod(http.POST) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CreatePolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func GetPolicy(cli bce.Client, name, policyType string) (*GetPolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getPolicyUri(name)) + if policyType != "" { + req.SetParam("policyType", policyType) + } + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetPolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeletePolicy(cli bce.Client, name string) error { + req := &bce.BceRequest{} + req.SetUri(getPolicyUri(name)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListPolicy(cli bce.Client, nameFilter, policyType string) (*ListPolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_POLICY) + if nameFilter != "" { + req.SetParam("nameFilter", nameFilter) + } + if policyType != "" { + req.SetParam("policyType", policyType) + } + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func AttachPolicyToUser(cli bce.Client, args *AttachPolicyToUserArgs) error { + req := &bce.BceRequest{} + req.SetUri(getUserUri(args.UserName) + URI_POLICY + "/" + args.PolicyName) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + req.SetMethod(http.PUT) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func DetachPolicyFromUser(cli bce.Client, args *DetachPolicyFromUserArgs) error { + req := &bce.BceRequest{} + req.SetUri(getUserUri(args.UserName) + URI_POLICY + "/" + args.PolicyName) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListUserAttachedPolicies(cli bce.Client, name string) (*ListPolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name) + URI_POLICY) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func AttachPolicyToGroup(cli bce.Client, args *AttachPolicyToGroupArgs) error { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(args.GroupName) + URI_POLICY + "/" + args.PolicyName) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + req.SetMethod(http.PUT) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func DetachPolicyFromGroup(cli bce.Client, args *DetachPolicyFromGroupArgs) error { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(args.GroupName) + URI_POLICY + "/" + args.PolicyName) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListGroupAttachedPolicies(cli bce.Client, name string) (*ListPolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getGroupUri(name) + URI_POLICY) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func AttachPolicyToRole(cli bce.Client, args *AttachPolicyToRoleArgs) error { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(args.RoleName) + URI_POLICY + "/" + args.PolicyName) + req.SetMethod(http.PUT) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func DetachPolicyFromRole(cli bce.Client, args *DetachPolicyToRoleArgs) error { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(args.RoleName) + URI_POLICY + "/" + args.PolicyName) + req.SetMethod(http.DELETE) + if args.PolicyType != "" { + req.SetParam("policyType", args.PolicyType) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListRoleAttachedPolicies(cli bce.Client, roleName string) (*ListPolicyResult, error) { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(roleName) + URI_POLICY) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPolicyResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func getPolicyUri(name string) string { + return URI_PREFIX + URI_POLICY + "/" + name +} diff --git a/bce-sdk-go/services/iam/api/role.go b/bce-sdk-go/services/iam/api/role.go new file mode 100644 index 0000000..db4372c --- /dev/null +++ b/bce-sdk-go/services/iam/api/role.go @@ -0,0 +1,125 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateRole(cli bce.Client, body *bce.Body) (*CreateRoleResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_ROLE) + req.SetMethod(http.POST) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + jsonBody := &CreateRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func GetRole(cli bce.Client, roleName string) (*GetRoleResult, error) { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(roleName)) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func UpdateRole(cli bce.Client, roleName string, body *bce.Body) (*UpdateRoleResult, error) { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(roleName)) + req.SetMethod(http.PUT) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdateRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeleteRole(cli bce.Client, roleName string) error { + req := &bce.BceRequest{} + req.SetUri(getRoleUri(roleName)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func ListRole(cli bce.Client) (*ListRoleResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_ROLE) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListRoleResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func getRoleUri(roleName string) string { + return URI_PREFIX + URI_ROLE + "/" + roleName +} diff --git a/bce-sdk-go/services/iam/api/user.go b/bce-sdk-go/services/iam/api/user.go new file mode 100644 index 0000000..b87c785 --- /dev/null +++ b/bce-sdk-go/services/iam/api/user.go @@ -0,0 +1,225 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateUser(cli bce.Client, body *bce.Body) (*CreateUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_USER) + req.SetMethod(http.POST) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CreateUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func GetUser(cli bce.Client, name string) (*GetUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name)) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeleteUser(cli bce.Client, name string) error { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name)) + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func UpdateUser(cli bce.Client, name string, body *bce.Body) (*UpdateUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name)) + req.SetMethod(http.PUT) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdateUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func ListUser(cli bce.Client) (*ListUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_USER) + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func UpdateUserLoginProfile(cli bce.Client, name string, body *bce.Body) (*UpdateUserLoginProfileResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name) + "/loginProfile") + req.SetMethod(http.PUT) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdateUserLoginProfileResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func GetUserLoginProfile(cli bce.Client, name string) (*GetUserLoginProfileResult, error) { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name) + "/loginProfile") + req.SetMethod(http.GET) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetUserLoginProfileResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func DeleteUserLoginProfile(cli bce.Client, name string) error { + req := &bce.BceRequest{} + req.SetUri(getUserUri(name) + "/loginProfile") + req.SetMethod(http.DELETE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func UserOperationMfaSwitch(cli bce.Client, body *bce.Body) error { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + URI_USER + "/operation/mfa/switch") + req.SetMethod(http.POST) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func SubUserUpdate(cli bce.Client, body *bce.Body, name string) (*UpdateUserResult, error) { + req := &bce.BceRequest{} + req.SetUri(getSubUserUri(name) + "/update") + req.SetMethod(http.PUT) + req.SetBody(body) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdateUserResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return jsonBody, nil +} + +func getUserUri(name string) string { + return URI_PREFIX + URI_USER + "/" + name +} + +func getSubUserUri(name string) string { + return URI_PREFIX + SUB_USER + "/" + name +} diff --git a/bce-sdk-go/services/iam/client.go b/bce-sdk-go/services/iam/client.go new file mode 100644 index 0000000..40cb280 --- /dev/null +++ b/bce-sdk-go/services/iam/client.go @@ -0,0 +1,283 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for IAM service which is derived from BceClient + +// Package iam defines the IAM service of BCE. +// It contains the model sub package to implement the concrete request and response of the +// IAM user/accessKey/policy API +package iam + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/iam/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "iam." + bce.DEFAULT_REGION + ".baidubce.com" +) + +// Client of IAM service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk string) (*Client, error) { + return NewClientWithEndpoint(ak, sk, DEFAULT_SERVICE_DOMAIN) +} + +func NewClientWithEndpoint(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func (c *Client) CreateUser(args *api.CreateUserArgs) (*api.CreateUserResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.CreateUser(c, body) +} + +func (c *Client) GetUser(name string) (*api.GetUserResult, error) { + return api.GetUser(c, name) +} + +func (c *Client) UpdateUser(name string, args *api.UpdateUserArgs) (*api.UpdateUserResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.UpdateUser(c, name, body) +} + +func (c *Client) DeleteUser(name string) error { + return api.DeleteUser(c, name) +} + +func (c *Client) ListUser() (*api.ListUserResult, error) { + return api.ListUser(c) +} + +func (c *Client) UpdateUserLoginProfile(name string, args *api.UpdateUserLoginProfileArgs) ( + *api.UpdateUserLoginProfileResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.UpdateUserLoginProfile(c, name, body) +} + +func (c *Client) GetUserLoginProfile(name string) (*api.GetUserLoginProfileResult, error) { + return api.GetUserLoginProfile(c, name) +} + +func (c *Client) DeleteUserLoginProfile(name string) error { + return api.DeleteUserLoginProfile(c, name) +} + +func (c *Client) CreateGroup(args *api.CreateGroupArgs) (*api.CreateGroupResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.CreateGroup(c, body) +} + +func (c *Client) GetGroup(name string) (*api.GetGroupResult, error) { + return api.GetGroup(c, name) +} + +func (c *Client) UpdateGroup(name string, args *api.UpdateGroupArgs) (*api.UpdateGroupResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.UpdateGroup(c, name, body) +} + +func (c *Client) DeleteGroup(name string) error { + return api.DeleteGroup(c, name) +} + +func (c *Client) ListGroup() (*api.ListGroupResult, error) { + return api.ListGroup(c) +} + +func (c *Client) AddUserToGroup(userName string, groupName string) error { + return api.AddUserToGroup(c, userName, groupName) +} + +func (c *Client) DeleteUserFromGroup(userName string, groupName string) error { + return api.DeleteUserFromGroup(c, userName, groupName) +} + +func (c *Client) ListUsersInGroup(name string) (*api.ListUsersInGroupResult, error) { + return api.ListUsersInGroup(c, name) +} + +func (c *Client) ListGroupsForUser(name string) (*api.ListGroupsForUserResult, error) { + return api.ListGroupsForUser(c, name) +} + +func (c *Client) CreatePolicy(args *api.CreatePolicyArgs) (*api.CreatePolicyResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.CreatePolicy(c, body) +} + +func (c *Client) GetPolicy(name, policyType string) (*api.GetPolicyResult, error) { + return api.GetPolicy(c, name, policyType) +} + +func (c *Client) DeletePolicy(name string) error { + return api.DeletePolicy(c, name) +} + +func (c *Client) ListPolicy(nameFilter, policyType string) (*api.ListPolicyResult, error) { + return api.ListPolicy(c, nameFilter, policyType) +} + +func (c *Client) AttachPolicyToUser(args *api.AttachPolicyToUserArgs) error { + return api.AttachPolicyToUser(c, args) +} + +func (c *Client) DetachPolicyFromUser(args *api.DetachPolicyFromUserArgs) error { + return api.DetachPolicyFromUser(c, args) +} + +func (c *Client) ListUserAttachedPolicies(name string) (*api.ListPolicyResult, error) { + return api.ListUserAttachedPolicies(c, name) +} + +func (c *Client) AttachPolicyToGroup(args *api.AttachPolicyToGroupArgs) error { + return api.AttachPolicyToGroup(c, args) +} + +func (c *Client) DetachPolicyFromGroup(args *api.DetachPolicyFromGroupArgs) error { + return api.DetachPolicyFromGroup(c, args) +} + +func (c *Client) ListGroupAttachedPolicies(name string) (*api.ListPolicyResult, error) { + return api.ListGroupAttachedPolicies(c, name) +} + +func (c *Client) CreateAccessKey(userName string) (*api.CreateAccessKeyResult, error) { + return api.CreateAccessKey(c, userName) +} + +func (c *Client) DisableAccessKey(userName, accessKeyId string) (*api.UpdateAccessKeyResult, error) { + return api.DisableAccessKey(c, userName, accessKeyId) +} + +func (c *Client) EnableAccessKey(userName, accessKeyId string) (*api.UpdateAccessKeyResult, error) { + return api.EnableAccessKey(c, userName, accessKeyId) +} + +func (c *Client) DeleteAccessKey(userName, accessKeyId string) error { + return api.DeleteAccessKey(c, userName, accessKeyId) +} + +func (c *Client) ListAccessKey(userName string) (*api.ListAccessKeyResult, error) { + return api.ListAccessKey(c, userName) +} + +func (c *Client) CreateRole(args *api.CreateRoleArgs) (*api.CreateRoleResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.CreateRole(c, body) +} + +func (c *Client) GetRole(roleName string) (*api.GetRoleResult, error) { + return api.GetRole(c, roleName) +} + +func (c *Client) UpdateRole(roleName string, args *api.UpdateRoleArgs) (*api.UpdateRoleResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.UpdateRole(c, roleName, body) +} + +func (c *Client) DeleteRole(roleName string) error { + return api.DeleteRole(c, roleName) +} + +func (c *Client) ListRole() (*api.ListRoleResult, error) { + return api.ListRole(c) +} + +func (c *Client) AttachPolicyToRole(args *api.AttachPolicyToRoleArgs) error { + return api.AttachPolicyToRole(c, args) +} + +func (c *Client) DetachPolicyFromRole(args *api.DetachPolicyToRoleArgs) error { + return api.DetachPolicyFromRole(c, args) +} + +func (c *Client) ListRoleAttachedPolicies(name string) (*api.ListPolicyResult, error) { + return api.ListRoleAttachedPolicies(c, name) +} + +func (c *Client) UserOperationMfaSwitch(args *api.UserSwitchMfaArgs) error { + body, err := NewBodyFromStruct(args) + if err != nil { + return err + } + return api.UserOperationMfaSwitch(c, body) +} + +func (c *Client) SubUserUpdate(userName string, args *api.UpdateSubUserArgs) (*api.UpdateUserResult, error) { + body, err := NewBodyFromStruct(args) + if err != nil { + return nil, err + } + return api.SubUserUpdate(c, body, userName) +} + +func NewBodyFromStruct(args interface{}) (*bce.Body, error) { + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + return body, nil +} diff --git a/bce-sdk-go/services/iam/client_test.go b/bce-sdk-go/services/iam/client_test.go new file mode 100644 index 0000000..602aa36 --- /dev/null +++ b/bce-sdk-go/services/iam/client_test.go @@ -0,0 +1,592 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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 iam + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/services/iam/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +var IAM_CLIENT *Client + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + IAM_CLIENT, _ = NewClientWithEndpoint(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestCreateListUpdateDeleteUser(t *testing.T) { + name := "test-user-sdk-go" + args := &api.CreateUserArgs{ + Name: name, + Description: "description", + } + res, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + users, err := IAM_CLIENT.ListUser() + ExpectEqual(t.Errorf, err, nil) + if users == nil || len(users.Users) == 0 { + t.Errorf("list user return no result") + } + jsonRes, _ = json.Marshal(users) + t.Logf(string(jsonRes)) + + updateArgs := &api.UpdateUserArgs{ + Description: "updated description", + } + updated, err := IAM_CLIENT.UpdateUser(name, updateArgs) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ = json.Marshal(updated) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, updated.Name, name) + ExpectEqual(t.Errorf, updated.Description, updateArgs.Description) + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUpdateGetDeleteUserLoginProfile(t *testing.T) { + name := "test-user-sdk-go-login-profile" + args := &api.CreateUserArgs{ + Name: name, + } + _, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + + updateArgs := &api.UpdateUserLoginProfileArgs{ + Password: "testpassword", + EnabledLoginMfa: true, + LoginMfaType: "PHONE", + } + updateRes, err := IAM_CLIENT.UpdateUserLoginProfile(name, updateArgs) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(updateRes) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, updateRes.EnabledLoginMfa, true) + ExpectEqual(t.Errorf, updateRes.LoginMfaType, "PHONE") + + getRes, err := IAM_CLIENT.GetUserLoginProfile(name) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ = json.Marshal(getRes) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, updateRes.EnabledLoginMfa, true) + ExpectEqual(t.Errorf, updateRes.LoginMfaType, "PHONE") + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateGroupUpdateGetListDeleteGroup(t *testing.T) { + name := "test_sdk_go_group" + args := &api.CreateUserArgs{ + Name: name, + } + _, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + + groupArgs := &api.CreateGroupArgs{ + Name: name, + Description: "description", + } + group, err := IAM_CLIENT.CreateGroup(groupArgs) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, name, group.Name) + ExpectEqual(t.Errorf, groupArgs.Description, group.Description) + + updateGroupArgs := &api.UpdateGroupArgs{ + Description: "updated group", + } + updated, err := IAM_CLIENT.UpdateGroup(name, updateGroupArgs) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, name, updated.Name) + ExpectEqual(t.Errorf, updateGroupArgs.Description, updated.Description) + + getRes, err := IAM_CLIENT.GetGroup(name) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, name, getRes.Name) + ExpectEqual(t.Errorf, updateGroupArgs.Description, getRes.Description) + + listRes, err := IAM_CLIENT.ListGroup() + ExpectEqual(t.Errorf, err, nil) + if listRes == nil || len(listRes.Groups) == 0 { + t.Errorf("list group return no result") + } + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) + err = IAM_CLIENT.DeleteGroup(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAddDeleteUserFromGroup(t *testing.T) { + name := "test_sdk_go_group" + args := &api.CreateUserArgs{ + Name: name, + } + user, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + + groupArgs := &api.CreateGroupArgs{ + Name: name, + Description: "description", + } + group, err := IAM_CLIENT.CreateGroup(groupArgs) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.AddUserToGroup(name, name) + ExpectEqual(t.Errorf, err, nil) + + usersRes, err := IAM_CLIENT.ListUsersInGroup(name) + ExpectEqual(t.Errorf, err, nil) + if usersRes == nil || len(usersRes.Users) != 1 { + t.Errorf("list group result not 1") + } + ExpectEqual(t.Errorf, 1, len(usersRes.Users)) + ExpectEqual(t.Errorf, user.Id, usersRes.Users[0].Id) + ExpectEqual(t.Errorf, user.Name, usersRes.Users[0].Name) + + groupsRes, err := IAM_CLIENT.ListGroupsForUser(name) + ExpectEqual(t.Errorf, err, nil) + if groupsRes == nil || len(groupsRes.Groups) != 1 { + t.Errorf("list user result not 1") + } + ExpectEqual(t.Errorf, 1, len(groupsRes.Groups)) + ExpectEqual(t.Errorf, group.Id, groupsRes.Groups[0].Id) + ExpectEqual(t.Errorf, group.Name, groupsRes.Groups[0].Name) + + err = IAM_CLIENT.DeleteUserFromGroup(name, name) + ExpectEqual(t.Errorf, err, nil) + + usersRes, err = IAM_CLIENT.ListUsersInGroup(name) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, 0, len(usersRes.Users)) + + groupsRes, err = IAM_CLIENT.ListGroupsForUser(name) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, 0, len(groupsRes.Groups)) + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeleteGroup(name) + ExpectEqual(t.Errorf, err, nil) +} + +func getPolicyDocument() string { + aclEntry := api.AclEntry{ + Service: "bos", + Region: "bj", + Permission: []string{"ListBucket"}, + Resource: []string{"*"}, + Effect: "Allow", + } + acl := &api.Acl{ + AccessControlList: []api.AclEntry{aclEntry}, + } + document, _ := json.Marshal(acl) + return string(document) +} + +func getAssumeRolePolicyDocument() string { + + grantee := api.Grantee{ + ID: "test-account-id", + } + + aclEntry := api.AclEntry{ + Service: "bce:iam", + Region: "*", + Permission: []string{"AssumeRole"}, + Grantee: []api.Grantee{grantee}, + Effect: "Allow", + } + acl := &api.Acl{ + AccessControlList: []api.AclEntry{aclEntry}, + } + document, _ := json.Marshal(acl) + return string(document) +} + +func TestCreateGetListDeletePolicy(t *testing.T) { + name := "test_sdk_go_policy" + args := &api.CreatePolicyArgs{ + Name: name, + Description: "description", + Document: getPolicyDocument(), + } + + res, err := IAM_CLIENT.CreatePolicy(args) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, name, res.Name) + ExpectEqual(t.Errorf, args.Description, res.Description) + + getRes, err := IAM_CLIENT.GetPolicy(name, "") + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, name, getRes.Name) + ExpectEqual(t.Errorf, args.Description, getRes.Description) + + listRes, err := IAM_CLIENT.ListPolicy(name, "") + ExpectEqual(t.Errorf, err, nil) + if listRes == nil || len(listRes.Policies) == 0 { + t.Errorf("list policy result is empty") + } + + err = IAM_CLIENT.DeletePolicy(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestUserAttachDetachPolicy(t *testing.T) { + userName := "test_sdk_go_policy" + args := &api.CreateUserArgs{ + Name: userName, + } + _, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + + policyName := "test_sdk_go_policy" + policyArgs := &api.CreatePolicyArgs{ + Name: policyName, + Description: "description", + Document: getPolicyDocument(), + } + + _, err = IAM_CLIENT.CreatePolicy(policyArgs) + ExpectEqual(t.Errorf, err, nil) + + attachArgs := &api.AttachPolicyToUserArgs{ + UserName: userName, + PolicyName: policyName, + } + err = IAM_CLIENT.AttachPolicyToUser(attachArgs) + ExpectEqual(t.Errorf, err, nil) + + policies, err := IAM_CLIENT.ListUserAttachedPolicies(userName) + ExpectEqual(t.Errorf, err, nil) + if policies == nil || len(policies.Policies) != 1 { + t.Errorf("list policy result is not 1") + } + policy := policies.Policies[0] + ExpectEqual(t.Errorf, policyName, policy.Name) + + detachArgs := &api.DetachPolicyFromUserArgs{ + UserName: userName, + PolicyName: policyName, + } + err = IAM_CLIENT.DetachPolicyFromUser(detachArgs) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeletePolicy(policyName) + ExpectEqual(t.Errorf, err, nil) + err = IAM_CLIENT.DeleteUser(userName) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGroupAttachDetachPolicy(t *testing.T) { + groupName := "test_sdk_go_policy" + args := &api.CreateGroupArgs{ + Name: groupName, + } + _, err := IAM_CLIENT.CreateGroup(args) + ExpectEqual(t.Errorf, err, nil) + + policyName := "test_sdk_go_policy" + policyArgs := &api.CreatePolicyArgs{ + Name: policyName, + Description: "description", + Document: getPolicyDocument(), + } + + _, err = IAM_CLIENT.CreatePolicy(policyArgs) + ExpectEqual(t.Errorf, err, nil) + + attachArgs := &api.AttachPolicyToGroupArgs{ + GroupName: groupName, + PolicyName: policyName, + } + err = IAM_CLIENT.AttachPolicyToGroup(attachArgs) + ExpectEqual(t.Errorf, err, nil) + + policies, err := IAM_CLIENT.ListGroupAttachedPolicies(groupName) + ExpectEqual(t.Errorf, err, nil) + if policies == nil || len(policies.Policies) != 1 { + t.Errorf("list policy result is not 1") + } + policy := policies.Policies[0] + ExpectEqual(t.Errorf, policyName, policy.Name) + + detachArgs := &api.DetachPolicyFromGroupArgs{ + GroupName: groupName, + PolicyName: policyName, + } + err = IAM_CLIENT.DetachPolicyFromGroup(detachArgs) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeletePolicy(policyName) + ExpectEqual(t.Errorf, err, nil) + err = IAM_CLIENT.DeleteGroup(groupName) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAccessKeyCreateAndDelete(t *testing.T) { + name := "test-user-sdk-go-ak" + args := &api.CreateUserArgs{ + Name: name, + Description: "description", + } + res, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + akRes, err := IAM_CLIENT.CreateAccessKey(name) + jsonAkRes, _ := json.Marshal(akRes) + t.Logf(string(jsonAkRes)) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, akRes.Enabled, true) + + accessKeys, err := IAM_CLIENT.ListAccessKey(name) + ExpectEqual(t.Errorf, err, nil) + if accessKeys == nil || len(accessKeys.AccessKeys) == 0 { + t.Errorf("list accessKeys return no result") + } + aksJsonRes, _ := json.Marshal(accessKeys) + t.Logf(string(aksJsonRes)) + + err = IAM_CLIENT.DeleteAccessKey(name, akRes.Id) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestAccessKeyDisableAndEnable(t *testing.T) { + name := "test-user-sdk-go-ak" + args := &api.CreateUserArgs{ + Name: name, + Description: "description", + } + res, err := IAM_CLIENT.CreateUser(args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + akRes, err := IAM_CLIENT.CreateAccessKey(name) + jsonAkRes, _ := json.Marshal(akRes) + t.Logf(string(jsonAkRes)) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, akRes.Enabled, true) + + disAbleAkRes, err := IAM_CLIENT.DisableAccessKey(name, akRes.Id) + ExpectEqual(t.Errorf, err, nil) + jsonDisAbleAkRes, _ := json.Marshal(disAbleAkRes) + t.Logf(string(jsonDisAbleAkRes)) + ExpectEqual(t.Errorf, disAbleAkRes.Enabled, false) + + enAbleAkRes, err := IAM_CLIENT.EnableAccessKey(name, akRes.Id) + ExpectEqual(t.Errorf, err, nil) + jsonEnAbleAkRes, _ := json.Marshal(enAbleAkRes) + t.Logf(string(jsonEnAbleAkRes)) + ExpectEqual(t.Errorf, enAbleAkRes.Enabled, true) + + err = IAM_CLIENT.DeleteAccessKey(name, akRes.Id) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeleteUser(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRoleCreateAndDelete(t *testing.T) { + name := "test-role-sdk-go" + args := &api.CreateRoleArgs{ + Name: name, + Description: "description", + AssumeRolePolicyDocument: getAssumeRolePolicyDocument(), + } + res, err := IAM_CLIENT.CreateRole(args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + getRes, err := IAM_CLIENT.GetRole(name) + ExpectEqual(t.Errorf, err, nil) + getJsonRes, _ := json.Marshal(getRes) + t.Logf(string(getJsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + rolesRes, err := IAM_CLIENT.ListRole() + ExpectEqual(t.Errorf, err, nil) + if rolesRes == nil || len(rolesRes.Roles) == 0 { + t.Errorf("list roles return no result") + } + rolesResJson, _ := json.Marshal(rolesRes) + t.Logf(string(rolesResJson)) + + newDescription := "newDescription" + updateArgs := &api.UpdateRoleArgs{ + Description: newDescription, + } + updateRes, err := IAM_CLIENT.UpdateRole(name, updateArgs) + ExpectEqual(t.Errorf, err, nil) + ExpectEqual(t.Errorf, updateRes.Description, newDescription) + + err = IAM_CLIENT.DeleteRole(name) + ExpectEqual(t.Errorf, err, nil) +} + +func TestRoleAttachPolicyAndDetachPolicy(t *testing.T) { + roleName := "test-role-sdk-go" + args := &api.CreateRoleArgs{ + Name: roleName, + Description: "description", + AssumeRolePolicyDocument: getAssumeRolePolicyDocument(), + } + res, err := IAM_CLIENT.CreateRole(args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, args.Name) + ExpectEqual(t.Errorf, res.Description, args.Description) + + policyName := "test_sdk_go_policy" + policyArgs := &api.CreatePolicyArgs{ + Name: policyName, + Description: "description", + Document: getPolicyDocument(), + } + + _, err = IAM_CLIENT.CreatePolicy(policyArgs) + ExpectEqual(t.Errorf, err, nil) + + attachArgs := &api.AttachPolicyToRoleArgs{ + RoleName: roleName, + PolicyName: policyName, + } + err = IAM_CLIENT.AttachPolicyToRole(attachArgs) + ExpectEqual(t.Errorf, err, nil) + + policies, err := IAM_CLIENT.ListRoleAttachedPolicies(roleName) + ExpectEqual(t.Errorf, err, nil) + if policies == nil || len(policies.Policies) != 1 { + t.Errorf("list policy result is not 1") + } + policy := policies.Policies[0] + ExpectEqual(t.Errorf, policyName, policy.Name) + + detachArgs := &api.DetachPolicyToRoleArgs{ + RoleName: roleName, + PolicyName: policyName, + } + err = IAM_CLIENT.DetachPolicyFromRole(detachArgs) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeletePolicy(policyName) + ExpectEqual(t.Errorf, err, nil) + + err = IAM_CLIENT.DeleteRole(roleName) + ExpectEqual(t.Errorf, err, nil) + +} + +func TestUserOperationMfaSwitch(t *testing.T) { + userName := "test-user-sdk-go-switch-operation-mfa" + enableMfa := true + mfaType := "PHONE,TOTP" + args := &api.UserSwitchMfaArgs{ + UserName: userName, + EnabledMfa: enableMfa, + MfaType: mfaType, + } + err := IAM_CLIENT.UserOperationMfaSwitch(args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestSubUserUpdate(t *testing.T) { + userName := "test-user-name-sdk-go-sub-update" + Password := "testpassword" + args := &api.UpdateSubUserArgs{ + Password: Password, + } + res, err := IAM_CLIENT.SubUserUpdate(userName, args) + ExpectEqual(t.Errorf, err, nil) + jsonRes, _ := json.Marshal(res) + t.Logf(string(jsonRes)) + ExpectEqual(t.Errorf, res.Name, userName) +} diff --git a/bce-sdk-go/services/lbdc/client.go b/bce-sdk-go/services/lbdc/client.go new file mode 100644 index 0000000..e9e0ba7 --- /dev/null +++ b/bce-sdk-go/services/lbdc/client.go @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for Application LBDC service + +// Package lbdc defines the LBDC services of BCE. The supported APIs are all defined in sub-package +package lbdc + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + DEFAULT_SERVICE_DOMAIN = "blb." + bce.DEFAULT_REGION + ".baidubce.com" + URI_PREFIX = bce.URI_PREFIX + "v1" + REQUEST_LBDC_URL = "/lbdc" +) + +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getUrlForLbdc() string { + return URI_PREFIX + REQUEST_LBDC_URL +} + +func getUrlForLbdcId(lbdcId string) string { + return getUrlForLbdc() + "/" + lbdcId +} diff --git a/bce-sdk-go/services/lbdc/client_test.go b/bce-sdk-go/services/lbdc/client_test.go new file mode 100644 index 0000000..399f00a --- /dev/null +++ b/bce-sdk-go/services/lbdc/client_test.go @@ -0,0 +1,185 @@ +package lbdc + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + LDBC_CLIENT *Client + + region string + + LDBCID string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + Endpoint string `json:"Endpoint"` +} + +func init() { + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + LDBC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + + region = confObj.Endpoint[4:6] + + log.SetLogHandler(log.FILE) + log.SetLogDir("/Users/xxx/go/log/baidu/bce/go-sdk") + log.SetRotateType(log.ROTATE_SIZE) + log.Info("this is my own logger from the sdk") +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_CreateLbdc(t *testing.T) { + description := "" + args := &CreateLbdcArgs{ + ClientToken: getClientToken(), + Name: "abc", + Type: "7Layer", + CcuCount: 2, + Description: &description, + Billing: &Billing{ + PaymentTiming: "Prepaid", + Reservation: &Reservation{ + ReservationLength: 1, + }, + }, + RenewReservation: &Reservation{ + ReservationLength: 1, + }, + } + res, err := LDBC_CLIENT.CreateLbdc(args) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_UpgradeLbdc(t *testing.T) { + args := &UpgradeLbdcArgs{ + ClientToken: getClientToken(), + Id: "bgw_group-81196491", + CcuCount: 4, + } + err := LDBC_CLIENT.UpgradeLbdc(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RenewLbdc(t *testing.T) { + args := &RenewLbdcArgs{ + ClientToken: getClientToken(), + Id: "bgw_group-81196491", + Billing: &BillingForRenew{ + Reservation: &Reservation{ + ReservationLength: 1, + }, + }, + } + err := LDBC_CLIENT.RenewLbdc(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListLbdc(t *testing.T) { + args := &ListLbdcArgs{ + //Id: "bgw_group-a6dd5dc2", + //Name: "abc", + } + res, err := LDBC_CLIENT.ListLbdc(args) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_GetLbdcDetail(t *testing.T) { + lbdcId := "bgw_group-81196491" + //lbdcId := "nginx_group-39d9d255" + res, err := LDBC_CLIENT.GetLbdcDetail(lbdcId) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_UpdateLbdc(t *testing.T) { + name := "" + desc := "test" + args := &UpdateLbdcArgs{ + ClientToken: getClientToken(), + Id: "bgw_group-81196491", + UpdateLbdcBody: &UpdateLbdcBody{ + Name: &name, + Description: &desc, + }, + } + err := LDBC_CLIENT.UpdateLbdc(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBoundBlBListOfLbdc(t *testing.T) { + lbdcId := "bgw_group-81196491" + res, err := LDBC_CLIENT.GetBoundBlBListOfLbdc(lbdcId) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} diff --git a/bce-sdk-go/services/lbdc/lbdc.go b/bce-sdk-go/services/lbdc/lbdc.go new file mode 100644 index 0000000..b4b1a6b --- /dev/null +++ b/bce-sdk-go/services/lbdc/lbdc.go @@ -0,0 +1,190 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 lbdc lbdc.go - the LBDC APIs definition supported by the LBDC service +package lbdc + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateLbdc - create the LBDC instance with the specific parameters +// +// PARAMS: +// - args: the arguments to create LBDC +// +// RETURNS: +// - *CreateLoadBalancerResult: the result of create LoadBalancer, contains new LoadBalancer's ID +// - error: nil if success otherwise the specific error +func (c *Client) CreateLbdc(args *CreateLbdcArgs) (*CreateLbdcResult, error) { + if args == nil { + return nil, fmt.Errorf("the CreateLbdcArgs cannot be nil") + } + result := &CreateLbdcResult{} + err := bce.NewRequestBuilder(c). + WithURL(getUrlForLbdc()). + WithMethod(http.POST). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpgradeLbdc - upgrade LBDC with the specific parameters +// +// PARAMS: +// - args: the arguments to update LBDC +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpgradeLbdc(args *UpgradeLbdcArgs) error { + if args == nil { + return fmt.Errorf("the UpgradeLbdcArgs cannot be nil") + } + + if len(args.Id) == 0 { + return fmt.Errorf("the LbdcId cannot be empty") + } + return bce.NewRequestBuilder(c). + WithURL(getUrlForLbdcId(args.Id)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("resize", ""). + Do() +} + +// RenewLbdc - renew LBDC with the specific parameters +// +// PARAMS: +// - args: the arguments to renew LBDC +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenewLbdc(args *RenewLbdcArgs) error { + if args == nil { + return fmt.Errorf("the RenewLbdcArgs cannot be nil") + } + + if len(args.Id) == 0 { + return fmt.Errorf("the LbdcId cannot be empty") + } + return bce.NewRequestBuilder(c). + WithURL(getUrlForLbdcId(args.Id)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("purchaseReserved", ""). + Do() +} + +// ListLbdc - list LBDC with the specific id and/or name +// +// PARAMS: +// - args: the arguments to list LBDC instances +// +// RETURNS: +// - *ListSslVpnUserResult: the result of Cluster list contains page infos +// - error: nil if success otherwise the specific error +func (c *Client) ListLbdc(args *ListLbdcArgs) (*ListLbdcResult, error) { + if args == nil { + args = &ListLbdcArgs{} + } + result := &ListLbdcResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getUrlForLbdc()). + WithMethod(http.GET). + WithResult(result) + if len(args.Id) > 0 { + builder.WithQueryParamFilter("id", args.Id) + } + if len(args.Name) > 0 { + builder.WithQueryParamFilter("name", args.Name) + } + err := builder.Do() + return result, err +} + +// GetLbdcDetail - get details of the specific lbdc +// +// PARAMS: +// - lbdcId: the id of the specified lbdc +// +// RETURNS: +// - *LbdcDetailResult: the result of the specific lbdc details +// - error: nil if success otherwise the specific error +func (c *Client) GetLbdcDetail(lbdcId string) (*GetLbdcDetailResult, error) { + if len(lbdcId) == 0 { + return nil, fmt.Errorf("the LbdcId cannot be empty") + } + + result := &GetLbdcDetailResult{} + err := bce.NewRequestBuilder(c). + WithURL(getUrlForLbdcId(lbdcId)). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} + +// UpdateLbdc - update lbdc with the specific parameters +// +// PARAMS: +// - args: the arguments to update lbdc +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateLbdc(args *UpdateLbdcArgs) error { + if args == nil { + return fmt.Errorf("the UpdateLbdcArgs cannot be nil") + } + if len(args.Id) == 0 { + return fmt.Errorf("the LbdcId cannot be empty") + } + + if args.UpdateLbdcBody == nil { + return fmt.Errorf("the UpdateLbdcBody cannot be nil") + } + + return bce.NewRequestBuilder(c). + WithURL(getUrlForLbdcId(args.Id)). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args.UpdateLbdcBody). + Do() +} + +// GetBoundBlBListOfLbdc - get Bound blb list of lbdc +// +// PARAMS: +// - lbdcId: the id of the specified lbdc +// +// RETURNS: +// - *GetBoundBlBListOfLbdcResult: the result of the bound blb list of lbdc +// - error: nil if success otherwise the specific error +func (c *Client) GetBoundBlBListOfLbdc(lbdcId string) (*GetBoundBlBListOfLbdcResult, error) { + if len(lbdcId) == 0 { + return nil, fmt.Errorf("the LbdcId cannot be empty") + } + result := &GetBoundBlBListOfLbdcResult{} + err := bce.NewRequestBuilder(c). + WithURL(getUrlForLbdcId(lbdcId) + "/blb"). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/lbdc/model.go b/bce-sdk-go/services/lbdc/model.go new file mode 100644 index 0000000..6368024 --- /dev/null +++ b/bce-sdk-go/services/lbdc/model.go @@ -0,0 +1,147 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package lbdc + +type Reservation struct { + ReservationLength int `json:"reservationLength"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation *Reservation `json:"reservation"` +} + +type ReservationForCreate struct { + ReservationLength int `json:"reservationLength,omitempty"` +} + +type BillingForRenew struct { + Reservation *Reservation `json:"reservation"` +} + +type Cluster struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Status string `json:"status"` + CcuCount int `json:"ccuCount"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + Description string `json:"desc"` +} + +// CreateLbdcArgs defines the structure of input parameters for the CreateLbdc api +type CreateLbdcArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Type string `json:"type"` + CcuCount int `json:"ccuCount"` + Description *string `json:"desc,omitempty"` + Billing *Billing `json:"billing"` + RenewReservation *Reservation `json:"renewReservation"` +} + +// CreateLbdcResult defines the structure of output parameters for the CreateLbdc api +type CreateLbdcResult struct { + Id string `json:"id"` + Type string `json:"type"` + Description string `json:"desc"` +} + +// UpgradeLbdcArgs defines the structure of input parameters for the UpgradeLbdc api +type UpgradeLbdcArgs struct { + ClientToken string `json:"-"` + Id string `json:"id"` + CcuCount int `json:"ccuCount"` +} + +// RenewLbdcArgs defines the structure of input parameters for the RenewLbdc api +type RenewLbdcArgs struct { + ClientToken string `json:"-"` + Id string `json:"id"` + Billing *BillingForRenew `json:"billing"` +} + +// ListLbdcArgs defines the structure of input parameters for the ListLbdc api +type ListLbdcArgs struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// ListLbdcResult defines the structure of output parameters for the ListLbdc api +type ListLbdcResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + ClusterList []Cluster `json:"clusterList"` +} + +// GetLbdcDetailResult defines the structure of output parameters for the GetLbdcDetail api +type GetLbdcDetailResult struct { + // 4 layer + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Status string `json:"status"` + CcuCount int `json:"ccuCount"` + CreateTime string `json:"createTime"` + ExpireTime string `json:"expireTime"` + TotalConnectCount int64 `json:"totalConnectCount"` + NewConnectCps *int64 `json:"newConnectCps,omitempty"` + NetworkInBps int64 `json:"networkInBps"` + NetworkOutBps int64 `json:"networkOutBps"` + + // 7layer + HttpsQps *int64 `json:"httpsQps,omitempty"` + HttpQps *int64 `json:"httpQps,omitempty"` + HttpNewConnectCps *int64 `json:"httpNewConnectCps,omitempty"` + HttpsNewConnectCps *int64 `json:"httpsNewConnectCps,omitempty"` + SslNewConnectCps *int64 `json:"sslNewConnectCps,omitempty"` +} + +// UpdateLbdcArgs defines the structure of input parameters for the UpdateLbdc api +type UpdateLbdcArgs struct { + ClientToken string `json:"-"` + Id string `json:"id"` + UpdateLbdcBody *UpdateLbdcBody `json:"updateLbdcBody"` +} + +// UpdateLbdcBody defines the structure of input parameters for the UpdateLbdc api request body +type UpdateLbdcBody struct { + Name *string `json:"name,omitempty"` + Description *string `json:"desc,omitempty"` +} + +type AssociateBlb struct { + BlbId string `json:"blbId"` + Name string `json:"name"` + Status string `json:"status"` + BlbType string `json:"blbType"` + PublicIp string `json:"publicIp"` + EipRouteType string `json:"eipRouteType"` + Bandwidth int `json:"bandwidth"` + Address string `json:"address"` + Ipv6 string `json:"ipv6"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` +} + +// GetBoundBlBListOfLbdcResult defines the structure of output parameters for the GetBoundBlBListOfLbdc api +type GetBoundBlBListOfLbdcResult struct { + BlbList []AssociateBlb `json:"blbList"` +} diff --git a/bce-sdk-go/services/localDns/client.go b/bce-sdk-go/services/localDns/client.go new file mode 100644 index 0000000..befe9e5 --- /dev/null +++ b/bce-sdk-go/services/localDns/client.go @@ -0,0 +1,221 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 localDns + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "http://privatezone.baidubce.com" + DEFAULT_MAX_PARALLEL = 10 + MULTIPART_ALIGN = 1 << 20 // 1MB + MIN_MULTIPART_SIZE = 1 << 20 // 1MB + DEFAULT_MULTIPART_SIZE = 12 * (1 << 20) // 12MB + MAX_PART_NUMBER = 10000 +) + +// Client of bcd service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient + + // Fileds that used in parallel operation for BOS service + MaxParallel int64 + MultipartSize int64 +} + +// NewClient make the bcd service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer), + DEFAULT_MAX_PARALLEL, DEFAULT_MULTIPART_SIZE} + return client, nil +} + +// AddRecord - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - *api.AddRecordResponse: +// - error: the return error if any occurs +func (c *Client) AddRecord(zoneId string, body *AddRecordRequest) ( + *AddRecordResponse, error) { + return AddRecord(c, zoneId, body, body.ClientToken) +} + +// DeletePrivateZone - +// +// PARAMS: +// - zoneId: zone的id +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeletePrivateZone(zoneId string, clientToken string) error { + return DeletePrivateZone(c, zoneId, clientToken) +} + +// CreatePrivateZone - +// +// PARAMS: +// - body: body参数 +// +// RETURNS: +// - *api.CreatePrivateZoneResponse: +// - error: the return error if any occurs +func (c *Client) CreatePrivateZone(body *CreatePrivateZoneRequest) ( + *CreatePrivateZoneResponse, error) { + return CreatePrivateZone(c, body, body.ClientToken) +} + +// BindVpc - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) BindVpc(zoneId string, body *BindVpcRequest) error { + return BindVpc(c, zoneId, body, body.ClientToken) +} + +// DeleteRecord - +// +// PARAMS: +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteRecord(recordId string, clientToken string) error { + return DeleteRecord(c, recordId, clientToken) +} + +// DisableRecord - +// +// PARAMS: +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DisableRecord(recordId string, clientToken string) error { + return DisableRecord(c, recordId, clientToken) +} + +// EnableRecord - +// +// PARAMS: +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) EnableRecord(recordId string, clientToken string) error { + return EnableRecord(c, recordId, clientToken) +} + +// GetPrivateZone - +// +// PARAMS: +// - zoneId: zone的ID +// +// RETURNS: +// - *api.GetPrivateZoneResponse: +// - error: the return error if any occurs +func (c *Client) GetPrivateZone(zoneId string) (*GetPrivateZoneResponse, error) { + return GetPrivateZone(c, zoneId) +} + +// ListPrivateZone - +// +// PARAMS: +// - request: 获取privateZone列表的入参 +// +// RETURNS: +// - *api.ListPrivateZoneResponse: +// - error: the return error if any occurs +func (c *Client) ListPrivateZone(request *ListPrivateZoneRequest) ( + *ListPrivateZoneResponse, error) { + return ListPrivateZone(c, request.Marker, request.MaxKeys) +} + +// ListRecord - +// +// PARAMS: +// - zoneId: Zone的ID +// +// RETURNS: +// - *api.ListRecordResponse: +// - error: the return error if any occurs +func (c *Client) ListRecord(zoneId string) (*ListRecordResponse, error) { + return ListRecord(c, zoneId) +} + +// UnbindVpc - +// +// PARAMS: +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UnbindVpc(zoneId string, body *UnbindVpcRequest) error { + return UnbindVpc(c, zoneId, body, body.ClientToken) +} + +// UpdateRecord - +// +// PARAMS: +// - recordId: 解析记录的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: body参数 +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateRecord(recordId string, body *UpdateRecordRequest) error { + return UpdateRecord(c, recordId, body, body.ClientToken) +} diff --git a/bce-sdk-go/services/localDns/client_test.go b/bce-sdk-go/services/localDns/client_test.go new file mode 100644 index 0000000..7e9029a --- /dev/null +++ b/bce-sdk-go/services/localDns/client_test.go @@ -0,0 +1,169 @@ +package localDns + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +var ( + LD_CLIENT *Client + + // set these values before start test + VPC_ID = "vpc-0n1hhh8759b0" + Region = "bj" + RR = "rr" + TYPE = "A" + VALUE = "1.2.3.5" + ZONE_ID = "zone-mk2guy4qxd7c" + RECORD_ID = "rc-9mfacvjk6weq" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + LD_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreatePrivateZone(t *testing.T) { + createArgs := &CreatePrivateZoneRequest{ + ClientToken: getClientToken(), + ZoneName: "sdkLd.com", + } + createResult, err := LD_CLIENT.CreatePrivateZone(createArgs) + ExpectEqual(t.Errorf, nil, err) + + ZONE_ID = createResult.ZoneId +} + +func TestClient_DeletePrivateZone(t *testing.T) { + err := LD_CLIENT.DeletePrivateZone(ZONE_ID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListPrivateZone(t *testing.T) { + listPrivateZoneRequest := &ListPrivateZoneRequest{} + res, err := LD_CLIENT.ListPrivateZone(listPrivateZoneRequest) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetPrivateZone(t *testing.T) { + res, err := LD_CLIENT.GetPrivateZone(ZONE_ID) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BindVpc(t *testing.T) { + bindVpcRequest := &BindVpcRequest{ + Region: Region, + VpcIds: []string{VPC_ID}, + } + err := LD_CLIENT.BindVpc(ZONE_ID, bindVpcRequest) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnbindVpc(t *testing.T) { + unbindVpcRequest := &UnbindVpcRequest{ + Region: Region, + VpcIds: []string{VPC_ID}, + } + err := LD_CLIENT.UnbindVpc(ZONE_ID, unbindVpcRequest) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AddRecord(t *testing.T) { + addRecordRequest := &AddRecordRequest{ + Rr: RR, + Type: TYPE, + Value: VALUE, + } + res, err := LD_CLIENT.AddRecord(ZONE_ID, addRecordRequest) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateRecord(t *testing.T) { + updateRecordRequest := &UpdateRecordRequest{ + Rr: RR, + Type: TYPE, + Value: VALUE, + } + err := LD_CLIENT.UpdateRecord(RECORD_ID, updateRecordRequest) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteRecord(t *testing.T) { + err := LD_CLIENT.DeleteRecord(RECORD_ID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListRecord(t *testing.T) { + res, err := LD_CLIENT.ListRecord(ZONE_ID) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EnableRecord(t *testing.T) { + err := LD_CLIENT.EnableRecord(RECORD_ID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DisableRecord(t *testing.T) { + err := LD_CLIENT.DisableRecord(RECORD_ID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/localDns/ld.go b/bce-sdk-go/services/localDns/ld.go new file mode 100644 index 0000000..aa7b881 --- /dev/null +++ b/bce-sdk-go/services/localDns/ld.go @@ -0,0 +1,431 @@ +/* + * Copyright 2022 Baidu, Inc. + * + * 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 localDns + +import ( + "encoding/json" + "strconv" + "strings" +) +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// AddRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - *api.AddRecordResponse: +// - error: the return error if any occurs +func AddRecord(cli bce.Client, zoneId string, body *AddRecordRequest, clientToken string) ( + *AddRecordResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/privatezone/[zoneId]/record" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &AddRecordResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// BindVpc - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func BindVpc(cli bce.Client, zoneId string, body *BindVpcRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/privatezone/[zoneId]" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + req.SetParam("bind", "") + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// CreatePrivateZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - *api.CreatePrivateZoneResponse: +// - error: the return error if any occurs +func CreatePrivateZone(cli bce.Client, body *CreatePrivateZoneRequest, clientToken string) ( + *CreatePrivateZoneResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.POST) + path := "/v1/privatezone" + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return nil, err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &CreatePrivateZoneResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// DeletePrivateZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneId: zone的id +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeletePrivateZone(cli bce.Client, zoneId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/privatezone/[zoneId]" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func DeleteRecord(cli bce.Client, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.DELETE) + path := "/v1/privatezone/record/[recordId]" + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DisableRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func DisableRecord(cli bce.Client, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/privatezone/record/[recordId]" + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("disable", "") + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// EnableRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - recordId: 解析记录ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// +// RETURNS: +// - error: the return error if any occurs +func EnableRecord(cli bce.Client, recordId string, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/privatezone/record/[recordId]" + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("enable", "") + req.SetParam("clientToken", clientToken) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// GetPrivateZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneId: zone的ID +// +// RETURNS: +// - *api.GetPrivateZoneResponse: +// - error: the return error if any occurs +func GetPrivateZone(cli bce.Client, zoneId string) (*GetPrivateZoneResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/privatezone/[zoneId]" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &GetPrivateZoneResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListPrivateZone - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - marker: 批量获取列表的查询的起始位置,是一个由系统生成的字符串 +// - maxKeys: 每页包含的最大数量,最大数量通常不超过1000。缺省值为1000 +// +// RETURNS: +// - *api.ListPrivateZoneResponse: +// - error: the return error if any occurs +func ListPrivateZone(cli bce.Client, marker string, maxKeys int) ( + *ListPrivateZoneResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/privatezone" + req.SetUri(path) + if "" != marker { + req.SetParam("marker", marker) + } + if 0 != maxKeys { + req.SetParam("maxKeys", strconv.Itoa(maxKeys)) + } + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListPrivateZoneResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// ListRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - zoneId: Zone的ID +// +// RETURNS: +// - *api.ListRecordResponse: +// - error: the return error if any occurs +func ListRecord(cli bce.Client, zoneId string) (*ListRecordResponse, error) { + req := &bce.BceRequest{} + req.SetMethod(http.GET) + path := "/v1/privatezone/[zoneId]/record" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + res := &ListRecordResponse{} + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +// UnbindVpc - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UnbindVpc(cli bce.Client, zoneId string, body *UnbindVpcRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/privatezone/[zoneId]" + path = strings.Replace(path, "[zoneId]", zoneId, -1) + req.SetUri(path) + req.SetParam("unbind", "") + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// UpdateRecord - +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - recordId: 解析记录的ID +// - clientToken: 幂等性Token,是一个长度不超过64位的ASCII字符串 +// - body: +// +// RETURNS: +// - error: the return error if any occurs +func UpdateRecord(cli bce.Client, recordId string, body *UpdateRecordRequest, clientToken string) error { + req := &bce.BceRequest{} + req.SetMethod(http.PUT) + path := "/v1/privatezone/record/[recordId]" + path = strings.Replace(path, "[recordId]", recordId, -1) + req.SetUri(path) + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(body) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/localDns/model.go b/bce-sdk-go/services/localDns/model.go new file mode 100644 index 0000000..380edfa --- /dev/null +++ b/bce-sdk-go/services/localDns/model.go @@ -0,0 +1,144 @@ +/* + * Copyright Baidu, Inc. + * + * 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 localDns + +type AddRecordRequest struct { + ClientToken string `json:"-"` + Rr string `json:"rr"` + Value string `json:"value"` + Type string `json:"type"` + Ttl int32 `json:"ttl,omitempty"` + Priority int32 `json:"priority,omitempty"` + Description string `json:"description,omitempty"` +} + +type AddRecordResponse struct { + RecordId string `json:"recordId"` +} + +type BindVpcRequest struct { + ClientToken string `json:"-"` + Region string `json:"region"` + VpcIds []string `json:"vpcIds"` +} + +type BindVpcRequestVpcIds struct { +} + +type ListPrivateZoneRequest struct { + Marker string + MaxKeys int +} + +type CreatePrivateZoneRequest struct { + ClientToken string `json:"-"` + ZoneName string `json:"zoneName"` +} + +type CreatePrivateZoneResponse struct { + ZoneId string `json:"zoneId"` +} + +type DeletePrivateZoneRequest struct { + ZoneName string `json:"zoneName"` +} + +type DeleteRecordRequest struct { + RecordId string `json:"recordId"` + Rr string `json:"rr"` + Value string `json:"value"` + Type string `json:"type"` + Ttl *int32 `json:"ttl"` + Priority *int32 `json:"priority"` + Description *string `json:"description"` +} + +type GetPrivateZoneResponse struct { + ZoneId string `json:"zoneId"` + ZoneName string `json:"zoneName"` + RecordCount int32 `json:"recordCount"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` + BindVpcs []Vpc `json:"bindVpcs"` +} + +type GetPrivateZoneResponseBindVpcs struct { +} + +type ListPrivateZoneResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int32 `json:"maxKeys"` + Zones []Zone `json:"zones"` +} + +type ListPrivateZoneResponseZones struct { +} + +type ListRecordResponse struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int32 `json:"maxKeys"` + Records []Record `json:"records"` +} + +type ListRecordResponseRecords struct { +} + +type Record struct { + RecordId string `json:"recordId"` + Rr string `json:"rr"` + Value string `json:"value"` + Status string `json:"status"` + Type string `json:"type"` + Ttl int32 `json:"ttl"` + Priority int32 `json:"priority"` + Description string `json:"description"` +} + +type UnbindVpcRequest struct { + ClientToken string `json:"-"` + Region string `json:"region"` + VpcIds []string `json:"vpcIds"` +} + +type UnbindVpcRequestVpcIds struct { +} + +type UpdateRecordRequest struct { + ClientToken string `json:"-"` + Rr string `json:"rr"` + Value string `json:"value"` + Type string `json:"type"` + Ttl int32 `json:"ttl,omitempty"` + Priority int32 `json:"priority,omitempty"` + Description string `json:"description,omitempty"` +} + +type Vpc struct { + VpcId string `json:"vpcId"` + VpcName string `json:"vpcName"` + VpcRegion string `json:"vpcRegion"` +} + +type Zone struct { + ZoneId string `json:"zoneId"` + ZoneName string `json:"zoneName"` + RecordCount int32 `json:"recordCount"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} diff --git a/bce-sdk-go/services/media/api/job.go b/bce-sdk-go/services/media/api/job.go new file mode 100644 index 0000000..a4cb251 --- /dev/null +++ b/bce-sdk-go/services/media/api/job.go @@ -0,0 +1,121 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateJob(cli bce.Client, pipelineName, sourceKey, targetKey, presetName string) (*CreateJobResponse, error) { + args := &CreateJobArgs{} + args.PipelineName = pipelineName + + source := &Source{} + source.SourceKey = sourceKey + + target := &Target{} + target.TargetKey = targetKey + target.PresetName = presetName + + args.Source = source + args.Target = target + + req := &bce.BceRequest{} + req.SetUri(getTrandCodingJobUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, jsonErr := bce.NewBodyFromBytes(jsonBytes) + if jsonErr != nil { + return nil, jsonErr + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + result := &CreateJobResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return result, nil + +} + +func CreateJobCustomize(cli bce.Client, args *CreateJobArgs) (*CreateJobResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getTrandCodingJobUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, jsonErr := bce.NewBodyFromBytes(jsonBytes) + if jsonErr != nil { + return nil, jsonErr + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + result := &CreateJobResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + defer func() { resp.Body().Close() }() + return result, nil +} + +func ListTranscodingJobs(cli bce.Client, pipelineName string) (*ListTranscodingJobsResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getTrandCodingJobUrl()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListTranscodingJobsResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetTranscodingJob(cli bce.Client, jobId string) (*GetTranscodingJobResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getTrandCodingJobUrl() + "/" + jobId) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetTranscodingJobResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/media/api/media_info.go b/bce-sdk-go/services/media/api/media_info.go new file mode 100644 index 0000000..268d975 --- /dev/null +++ b/bce-sdk-go/services/media/api/media_info.go @@ -0,0 +1,29 @@ +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func GetMediaInfoOfFile(cli bce.Client, bucket, key string) (*GetMediaInfoOfFileResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getMediaInfoUrl()) + req.SetMethod(http.GET) + paramMap := make(map[string]string) + paramMap["bucket"] = bucket + paramMap["key"] = key + req.SetParams(paramMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetMediaInfoOfFileResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/media/api/model.go b/bce-sdk-go/services/media/api/model.go new file mode 100644 index 0000000..84525b7 --- /dev/null +++ b/bce-sdk-go/services/media/api/model.go @@ -0,0 +1,453 @@ +package api + +// create pipline args +type CreatePiplineArgs struct { + PipelineName string `json:"pipelineName"` + Description string `json:"description,omitempty"` + SourceBucket string `json:"sourceBucket"` + TargetBucket string `json:"targetBucket"` + Config *CreatePiplineConfig `json:"config"` +} + +type CreatePiplineConfig struct { + Capacity int `json:"capacity"` + Notification string `json:"notification,omitempty"` + PipelineType string `json:"pipelineType,omitempty"` +} + +type ListPipelinesResponse struct { + Pipelines []PipelineStatus `json:"pipelines"` +} + +type JobStatus struct { + Total int `json:"total,omitempty"` + Running int `json:"running,omitempty"` + Pending int `json:"pending,omitempty"` + Failed int `json:"failed,omitempty"` +} + +type PipelineStatus struct { + PipelineName string `json:"pipelineName"` + Description string `json:"description,omitempty"` + SourceBucket string `json:"sourceBucket"` + TargetBucket string `json:"targetBucket"` + Config CreatePiplineConfig `json:"config"` + State string `json:"state,omitempty"` + Createtime string `json:"createtime,omitempty"` + JobStatus JobStatus `json:"jobStatus,omitempty"` +} + +type UpdatePipelineArgs struct { + PipelineName string `json:"pipelineName,omitempty"` + Description string `json:"description,omitempty"` + SourceBucket string `json:"sourceBucket,omitempty"` + TargetBucket string `json:"targetBucket,omitempty"` + UpdatePipelineConfig *UpdatePipelineConfig `json:"config,omitempty"` +} + +type UpdatePipelineConfig struct { + Capacity int `json:"capacity,omitempty"` + Notification string `json:"notification,omitempty"` +} + +type CreateJobArgs struct { + PipelineName string `json:"pipelineName,omitempty"` + Source *Source `json:"source"` + Target *Target `json:"target"` +} + +type Source struct { + SourceKey string `json:"sourceKey,omitempty"` + Clips *[]SourceClip `json:"clips,omitempty"` +} + +type SourceClip struct { + Bucket string `json:"bucket,omitempty"` + SourceKey string `json:"sourceKey,omitempty"` + StartTimeInSecond int `json:"startTimeInSecond,omitempty"` + DurationInSecond int `json:"durationInSecond,omitempty"` + StartTimeInMillisecond int `json:"startTimeInMillisecond,omitempty"` + DurationInMillisecond int `json:"durationInMillisecond,omitempty"` + EnableLogo bool `json:"enableLogo,omitempty"` + AsMasterClip bool `json:"asMasterClip,omitempty"` + EnableDelogo bool `json:"enableDelogo,omitempty"` + EnableCrop bool `json:"enableCrop,omitempty"` +} + +type Target struct { + TargetKey string `json:"targetKey,omitempty"` + PresetName string `json:"presetName,omitempty"` + AutoDelogo bool `json:"autoDelogo,omitempty"` + DelogoMode string `json:"delogoMode,omitempty"` + DelogoArea *Area `json:"delogoArea,omitempty"` + DelogoAreas *[]Area `json:"delogoAreas,omitempty"` + AutoCrop bool `json:"autoCrop,omitempty"` + Crop *Area `json:"crop,omitempty"` + WatermarkIds []string `json:"watermarkIds,omitempty"` + Inserts *[]Insert `json:"inserts,omitempty"` +} + +type Area struct { + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` +} + +type Insert struct { + Bucket string `json:"bucket,omitempty"` + Key string `json:"key,omitempty"` + Type string `json:"type,omitempty"` + Text string `json:"text"` + Font *Font `json:"font,omitempty"` + Timeline *Timeline `json:"timeline"` + Layout *Layout `json:"layout,omitempty"` +} + +type Font struct { + Family string `json:"family,omitempty"` + SizeInPoint int `json:"sizeInPoint,omitempty"` +} + +type Timeline struct { + StartTimeInMillisecond int `json:"startTimeInMillisecond,omitempty"` + DurationInMillisecond int `json:"durationInMillisecond,omitempty"` +} + +type Layout struct { + VerticalAlignment string `json:"verticalAlignment,omitempty"` + HorizontalAlignment string `json:"horizontalAlignment,omitempty"` + VerticalOffsetInPixel int `json:"verticalOffsetInPixel,omitempty"` + HorizontalOffsetInPixel int `json:"horizontalOffsetInPixel,omitempty"` +} + +type CreateJobResponse struct { + JobId string `json:"jobId"` +} + +type ListTranscodingJobsResponse struct { + Jobs []Job `json:"jobs"` +} + +type Job struct { + JobID string `json:"jobId"` + PipelineName string `json:"pipelineName"` + Source Source `json:"source"` + Target Target `json:"target"` + JobStatus string `json:"jobStatus"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Error Error `json:"error"` +} + +type GetTranscodingJobResponse struct { + JobID string `json:"jobId"` + PipelineName string `json:"pipelineName"` + Source Source `json:"source"` + Target Target `json:"target"` + JobStatus string `json:"jobStatus"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Error Error `json:"error"` +} + +type Error struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type ListPresetsResponse struct { + Presets []Preset `json:"presets"` +} + +type Preset struct { + PresetName string `json:"presetName,omitempty"` + Description string `json:"description,omitempty"` + Container string `json:"container,omitempty"` + Transmux bool `json:"transmux,omitempty"` + Clip *Clip `json:"clip,omitempty"` + Audio *Audio `json:"audio,omitempty"` + Video *Video `json:"video,omitempty"` + Encryption *Encryption `json:"encryption,omitempty"` + WatermarkID string `json:"watermarkId,omitempty"` + Watermarks *Watermarks `json:"watermarks,omitempty"` + TransCfg *TransCfg `json:"transCfg,omitempty"` + ExtraCfg *ExtraCfg `json:"extraCfg,omitempty"` + State string `json:"state,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` +} + +type Clip struct { + StartTimeInSecond int `json:"startTimeInSecond,omitempty"` + DurationInSecond int `json:"durationInSecond,omitempty"` +} + +type Audio struct { + BitRateInBps int `json:"bitRateInBps,omitempty"` + SampleRateInHz int `json:"sampleRateInHz,omitempty"` + Channels int `json:"channels,omitempty"` + PcmFormat string `json:"pcmFormat,omitempty"` + VolumeAdjust *VolumeAdjust `json:"volumeAdjust,omitempty"` + Codec string `json:"codec,omitemptyc"` +} + +type VolumeAdjust struct { + Mute bool `json:"mute,omitempty"` + Norm bool `json:"norm,omitempty"` + Gain int `json:"gain,omitempty"` +} + +type Video struct { + Codec string `json:"codec,omitempty"` + CodecOptions *CodecOptions `json:"codecOptions,omitempty"` + RateControl string `json:"rateControl,omitempty"` + CodecEnhance bool `json:"codecEnhance,omitempty"` + BitRateInBps int `json:"bitRateInBps,omitempty"` + MaxFrameRate float64 `json:"maxFrameRate,omitempty"` + MaxWidthInPixel int `json:"maxWidthInPixel,omitempty"` + MaxHeigtInPixel int `json:"maxHeightInPixel,omitempty"` + SizingPolicy string `json:"sizingPolicy,omitempty"` + PlaybackSpeed float64 `json:"playbackSpeed,omitempty"` + Crf int `json:"crf,omitempty"` + AutoAdjustResolution bool `json:"autoAdjustResolution,omitempty"` +} + +type CodecOptions struct { + Profile string `json:"profile,omitempty"` +} + +type Encryption struct { + Strategy string `json:"strategy,omitempty"` + AesKey string `json:"aesKey,omitempty"` + KeyServerURL string `json:"keyServerUrl,omitempty"` +} + +type Watermarks struct { + Image []string `json:"image,omitempty"` +} + +type TransCfg struct { + TransMode string `json:"transMode,omitempty"` +} + +type ExtraCfg struct { + WatermarkDisableWhitelist []string `json:"watermarkDisableWhitelist,omitempty"` + SegmentDurationInSecond float64 `json:"segmentDurationInSecond,omitempty"` + GopLength int `json:"gopLength,omitempty"` + SkipBlackFrame bool `json:"skipBlackFrame,omitempty"` +} + +type GetPresetResponse struct { + PresetName string `json:"presetName"` + Description string `json:"description"` + Container string `json:"container"` + Transmux bool `json:"transmux"` + Clip Clip `json:"clip"` + Audio Audio `json:"audio"` + Video *Video `json:"video"` + Encryption *Encryption `json:"encryption"` + WatermarkID string `json:"watermarkId"` + Watermarks *Watermarks `json:"watermarks"` + TransCfg *TransCfg `json:"transCfg"` + ExtraCfg *ExtraCfg `json:"extraCfg"` + State string `json:"state"` + CreatedTime string `json:"createdTime"` +} + +type GetMediaInfoOfFileResponse struct { + Bucket string `json:"bucket"` + Key string `json:"key"` + FileSizeInByte int `json:"fileSizeInByte"` + Container string `json:"container"` + DurationInSecond int `json:"durationInSecond"` + DurationInMillisecond int `json:"durationInMillisecond"` + Etag string `json:"etag"` + Type string `json:"type"` + VideoInfo *VideoInfo `json:"video"` + AudioInfo *AudioInfo `json:"audio"` +} + +type VideoInfo struct { + Codec string `json:"codec"` + HeightInPixel int `json:"heightInPixel"` + WidthInPixel int `json:"widthInPixel"` + BitRateInBps int `json:"bitRateInBps"` + FrameRate float64 `json:"frameRate"` + Rotate int `json:"rotate"` + Dar string `json:"dar"` +} + +type AudioInfo struct { + Codec string `json:"codec"` + Channels int `json:"channels"` + SampleRateInHz int `json:"sampleRateInHz"` + BitRateInBps int `json:"bitRateInBps"` +} + +type CreateThumbnailJobArgs struct { + PipelineName string `json:"pipelineName,omitempty"` + ThumbnailSource *ThumbnailSource `json:"source"` + PresetName string `json:"presetName,omitempty"` + ThumbnailTarget *ThumbnailTarget `json:"target,omitempty"` + ThumbnailCapture *ThumbnailCapture `json:"capture,omitempty"` + Area *Area `json:"delogoArea,omitempty"` + Crop *Area `json:"crop,omitempty"` +} + +type ThumbnailSource struct { + Key string `json:"key,omitempty"` +} + +type ThumbnailCapture struct { + Mode string `json:"mode,omitempty"` + StartTimeInSecond float64 `json:"startTimeInSecond,omitempty"` + EndTimeInSecond float64 `json:"endTimeInSecond,omitempty"` + IntervalInSecond float64 `json:"intervalInSecond,omitempty"` + MinIntervalInSecond float64 `json:"minIntervalInSecond,omitempty"` + FrameNumber int `json:"frameNumber,omitempty"` + SkipBlackFrame bool `json:"skipBlackFrame,omitempty"` + HighlightOutputCfg *HighlightOutputCfg `json:"highlightOutputCfg,omitempty"` + SpriteOutputCfg *SpriteOutputCfg `json:"spriteOutputCfg,omitempty"` +} + +type ThumbnailTarget struct { + KeyPrefix string `json:"keyPrefix,omitempty"` + Format string `json:"format,omitempty"` + FrameRate float64 `json:"frameRate,omitempty"` + GifQuality string `json:"gifQuality,omitempty"` + SizingPolicy string `json:"sizingPolicy,omitempty"` + WidthInPixel int `json:"widthInPixel,omitempty"` + HeightInPixel int `json:"heightInPixel,omitempty"` + SpriteOutputCfg *SpriteOutputCfg `json:"spriteOutputCfg,omitempty"` +} + +type HighlightOutputCfg struct { + DurationInSecond float64 `json:"durationInSecond,omitempty"` + FrameRate float64 `json:"frameRate,omitempty"` + PlaybackSpeed float64 `json:"playbackSpeed,omitempty"` + ReverseConcat bool `json:"reverseConcat,omitempty"` +} + +type SpriteOutputCfg struct { + Rows int `json:"rows,omitempty"` + Columns int `json:"columns,omitempty"` + Margin int `json:"margin,omitempty"` + Padding int `json:"padding,omitempty"` + KeepCellPic bool `json:"keepCellPic,omitempty"` + SpriteKeyTag string `json:"spriteKeyTag,omitempty"` +} + +type ThumbnailOptional struct { + PresetName string + Target *ThumbnailTarget + Capture *ThumbnailCapture + DelogoArea *Area + Crop *Area + ThumbnailSource *ThumbnailSource +} + +type GetThumbnailJobResponse struct { + JobID string `json:"jobId,omitempty"` + JobStatus string `json:"jobStatus,omitempty"` + PipelineName string `json:"pipelineName,omitempty"` + Source *ThumbnailSource `json:"source,omitempty"` + PresetName string `json:"presetName,omitempty"` + Target *ThumbnailTargetStatus `json:"target,omitempty"` + Capture *ThumbnailCapture `json:"capture,omitempty"` + DelogoArea *Area `json:"delogoArea,omitempty"` + Error *Error `json:"error,omitempty"` +} + +type ThumbnailTargetStatus struct { + KeyPrefix string `json:"keyPrefix,omitempty"` + Format string `json:"format,omitempty"` + FrameRate float64 `json:"frameRate,omitempty"` + GifQuality string `json:"gifQuality,omitempty"` + SizingPolicy string `json:"sizingPolicy,omitempty"` + WidthInPixel int `json:"widthInPixel,omitempty"` + HeightInPixel int `json:"heightInPixel,omitempty"` + SpriteOutputCfg *SpriteOutputCfg `json:"spriteOutputCfg,omitempty"` + Keys []string `json:"keys,omitempty"` +} + +type ListThumbnailJobsResponse struct { + Thumbnails []ThumbnailJobStatus `json:"thumbnails"` +} + +type ThumbnailJobStatus struct { + JobID string `json:"jobId"` + JobStatus string `json:"jobStatus"` + PipelineName string `json:"pipelineName"` + Source *ThumbnailSource `json:"source,omitempty"` + Target *ThumbnailTargetStatus `json:"target,omitempty"` + Capture *Area `json:"capture,omitempty"` + DelogoArea *Area `json:"delogoArea,omitempty"` + Error *Error `json:"error,omitempty"` +} + +type CreateWaterMarkArgs struct { + Bucket string `json:"bucket,omitempty"` + Key string `json:"key,omitempty"` + VerticalAlignment string `json:"verticalAlignment,omitempty"` + HorizontalAlignment string `json:"horizontalAlignment,omitempty"` + VerticalOffsetInPixel int `json:"verticalOffsetInPixel,omitempty"` + HorizontalOffsetInPixel int `json:"horizontalOffsetInPixel,omitempty"` + Timeline *Timeline `json:"timeline,omitempty"` + Repeated int `json:"repeated,omitempty"` + AllowScaling bool `json:"allowScaling,omitempty"` + Dx string `json:"dx,omitempty"` + Dy string `json:"dy,omitempty"` + Width string `json:"width,omitempty"` + Height string `json:"height,omitempty"` +} + +type CreateWaterMarkResponse struct { + WatermarkId string `json:"watermarkId"` +} + +type GetWaterMarkResponse struct { + Bucket string `json:"bucket"` + Key string `json:"key"` + VerticalAlignment string `json:"verticalAlignment"` + HorizontalAlignment string `json:"horizontalAlignment"` + VerticalOffsetInPixel int `json:"verticalOffsetInPixel"` + HorizontalOffsetInPixel int `json:"horizontalOffsetInPixel"` + Timeline *Timeline `json:"timeline"` + Repeated int `json:"repeated"` + AllowScaling bool `json:"allowScaling"` + Dx string `json:"dx"` + Dy string `json:"dy"` + Width string `json:"width"` + Height string `json:"height"` +} + +type ListWaterMarkResponse struct { + Watermarks []Watermark `json:"watermarks"` +} + +type Watermark struct { + Bucket string `json:"bucket"` + Key string `json:"key"` + VerticalOffsetInPixel int `json:"verticalOffsetInPixel"` + HorizontalOffsetInPixel int `json:"horizontalOffsetInPixel"` + WatermarkID string `json:"watermarkId"` + CreateTime string `json:"createTime"` + VerticalAlignment string `json:"verticalAlignment"` + HorizontalAlignment string `json:"horizontalAlignment"` + Dx string `json:"dx"` + Dy string `json:"dy"` + Width string `json:"width"` + Height string `json:"height"` + Timeline *Timeline `json:"timeline,omitempty"` + Repeated int `json:"repeated"` + AllowScaling bool `json:"allowScaling"` +} + +type CreateNotificationArgs struct { + Name string `json:"name,omitempty"` + Endpoint string `json:"endpoint,omitempty"` +} + +type ListNotificationsResponse struct { + Notifications []CreateNotificationArgs `json:"notifications"` +} diff --git a/bce-sdk-go/services/media/api/notification.go b/bce-sdk-go/services/media/api/notification.go new file mode 100644 index 0000000..de188e9 --- /dev/null +++ b/bce-sdk-go/services/media/api/notification.go @@ -0,0 +1,88 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateNotification(cli bce.Client, name, endpoint string) error { + notify := &CreateNotificationArgs{} + notify.Name = name + notify.Endpoint = endpoint + + req := &bce.BceRequest{} + req.SetUri(getNotification()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + jsonBytes, err := json.Marshal(notify) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func GetNotification(cli bce.Client, name string) (*CreateNotificationArgs, error) { + req := &bce.BceRequest{} + req.SetUri(getNotification() + "/" + name) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &CreateNotificationArgs{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ListNotification(cli bce.Client) (*ListNotificationsResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getNotification()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListNotificationsResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func DeleteNotification(cli bce.Client, name string) error { + req := &bce.BceRequest{} + req.SetUri(getNotification() + "/" + name) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/media/api/pipline.go b/bce-sdk-go/services/media/api/pipline.go new file mode 100644 index 0000000..8a2571e --- /dev/null +++ b/bce-sdk-go/services/media/api/pipline.go @@ -0,0 +1,140 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreatePipeline(cli bce.Client, pipelineName, sourceBucket, targetBucket string, capacity int) error { + req := &bce.BceRequest{} + args := &CreatePiplineArgs{} + + args.PipelineName = pipelineName + args.SourceBucket = sourceBucket + args.TargetBucket = targetBucket + + config := &CreatePiplineConfig{} + config.Capacity = capacity + + args.Config = config + req.SetUri(getPipLineUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func CreatePipelineCustomize() { + +} + +func ListPipelines(cli bce.Client) (*ListPipelinesResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getPipLineUrl()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPipelinesResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetPipeline(cli bce.Client, pipelineName string) (*PipelineStatus, error) { + req := &bce.BceRequest{} + req.SetUri(getPipLineUrl() + "/" + pipelineName) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &PipelineStatus{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetPipelineUpdate(cli bce.Client, pipelineName string) (*UpdatePipelineArgs, error) { + req := &bce.BceRequest{} + req.SetUri(getPipLineUrl() + "/" + pipelineName) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &UpdatePipelineArgs{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func DeletePipeline(cli bce.Client, pipelineName string) error { + req := &bce.BceRequest{} + req.SetUri(getPipLineUrl() + "/" + pipelineName) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func UpdatePipeline(cli bce.Client, pipelineName string, updatePipelineArgs *UpdatePipelineArgs) error { + req := &bce.BceRequest{} + jsonBytes, err := json.Marshal(updatePipelineArgs) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetUri(getPipLineUrl() + "/" + pipelineName) + req.SetMethod(http.PUT) + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/media/api/preset.go b/bce-sdk-go/services/media/api/preset.go new file mode 100644 index 0000000..9715177 --- /dev/null +++ b/bce-sdk-go/services/media/api/preset.go @@ -0,0 +1,128 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func ListPresets(cli bce.Client) (*ListPresetsResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getPresetUrl()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListPresetsResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetPreset(cli bce.Client, presetName string) (*Preset, error) { + req := &bce.BceRequest{} + req.SetUri(getPresetUrl() + "/" + presetName) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &Preset{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil + +} + +func CreatePreset(cli bce.Client, presetName, description, container string) error { + req := &bce.BceRequest{} + req.SetUri(getPresetUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + args := &Preset{} + args.PresetName = presetName + args.Description = description + args.Container = container + args.Transmux = true + extraCfg := &ExtraCfg{} + args.ExtraCfg = extraCfg + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + + return nil +} + +func CreatePrestCustomize(cli bce.Client, preset *Preset) error { + req := &bce.BceRequest{} + req.SetUri(getPresetUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, err := json.Marshal(preset) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +func UpdatePreset(cli bce.Client, preset *Preset) error { + presetName := preset.PresetName + req := &bce.BceRequest{} + req.SetUri(getPresetUrl() + "/" + presetName) + req.SetMethod(http.PUT) + jsonBytes, err := json.Marshal(preset) + if err != nil { + return err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/media/api/thumbnail_job.go b/bce-sdk-go/services/media/api/thumbnail_job.go new file mode 100644 index 0000000..2992703 --- /dev/null +++ b/bce-sdk-go/services/media/api/thumbnail_job.go @@ -0,0 +1,83 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateThumbnailJob(cli bce.Client, pipelineName, sourceKey string, createThumbnialArgs *CreateThumbnailJobArgs) ( + *CreateJobResponse, error) { + + createThumbnialArgs.PipelineName = pipelineName + source := &ThumbnailSource{} + source.Key = sourceKey + createThumbnialArgs.ThumbnailSource = source + req := &bce.BceRequest{} + req.SetUri(getThumbnailUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + jsonBytes, err := json.Marshal(createThumbnialArgs) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CreateJobResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func GetThumbanilJob(cli bce.Client, jobId string) (*GetThumbnailJobResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getThumbnailUrl() + "/" + jobId) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetThumbnailJobResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ListThumbnailJobs(cli bce.Client, pipelineName string) (*ListThumbnailJobsResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getThumbnailUrl()) + req.SetMethod(http.GET) + paramMap := make(map[string]string) + paramMap["pipelineName"] = pipelineName + req.SetParams(paramMap) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListThumbnailJobsResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/media/api/util.go b/bce-sdk-go/services/media/api/util.go new file mode 100644 index 0000000..2ddd3d9 --- /dev/null +++ b/bce-sdk-go/services/media/api/util.go @@ -0,0 +1,44 @@ +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + MEDIA_PREFIX = bce.URI_PREFIX + "v3/" + PIPLINE = "pipeline" + TRANSCODING_JOB = "job/transcoding" + PRESET = "preset" + MEDIA_INFO = "mediainfo" + THUMBNAIL = "job/thumbnail" + WATERMARK = "watermark" + NOTIFICATION = "notification" +) + +func getPipLineUrl() string { + return MEDIA_PREFIX + PIPLINE +} + +func getTrandCodingJobUrl() string { + return MEDIA_PREFIX + TRANSCODING_JOB +} + +func getPresetUrl() string { + return MEDIA_PREFIX + PRESET +} + +func getMediaInfoUrl() string { + return MEDIA_PREFIX + MEDIA_INFO +} + +func getThumbnailUrl() string { + return MEDIA_PREFIX + THUMBNAIL +} + +func getWatermarkUrl() string { + return MEDIA_PREFIX + WATERMARK +} + +func getNotification() string { + return MEDIA_PREFIX + NOTIFICATION +} diff --git a/bce-sdk-go/services/media/api/watermark.go b/bce-sdk-go/services/media/api/watermark.go new file mode 100644 index 0000000..1ee0a7c --- /dev/null +++ b/bce-sdk-go/services/media/api/watermark.go @@ -0,0 +1,87 @@ +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func CreateWaterMark(cli bce.Client, watermark *CreateWaterMarkArgs) (*CreateWaterMarkResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getWatermarkUrl()) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, err := json.Marshal(watermark) + if err != nil { + return nil, err + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CreateWaterMarkResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +func GetWaterMark(cli bce.Client, watermarkId string) (*GetWaterMarkResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getWatermarkUrl() + "/" + watermarkId) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetWaterMarkResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func ListWaterMark(cli bce.Client) (*ListWaterMarkResponse, error) { + req := &bce.BceRequest{} + req.SetUri(getWatermarkUrl()) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &ListWaterMarkResponse{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func DeleteWaterMark(cli bce.Client, watermarkId string) error { + req := &bce.BceRequest{} + req.SetUri(getWatermarkUrl() + "/" + watermarkId) + req.SetMethod(http.DELETE) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/media/client.go b/bce-sdk-go/services/media/client.go new file mode 100644 index 0000000..706c1d3 --- /dev/null +++ b/bce-sdk-go/services/media/client.go @@ -0,0 +1,232 @@ +package media + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/media/api" +) + +const DEFAULT_SERVICE_DOMAIN = "media.bj.baidubce.com" + +// mcp(media) client extends BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make MCP(media) service client with defualt configuration +// endPoint value can chose bj and sz or gz defualt bj +func NewClient(ak, sk, endPoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endPoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// create a simple pipeline with pipelieName,soureBucket,targetBucket,capacity +func (cli *Client) CreatePipeline(pipelineName, sourceBucket, targetBucket string, capacity int) error { + + return api.CreatePipeline(cli, pipelineName, sourceBucket, targetBucket, capacity) +} + +// list all pipelines for user +func (cli *Client) ListPipelines() (*api.ListPipelinesResponse, error) { + return api.ListPipelines(cli) +} + +// query pipeline by piplineName +func (cli *Client) GetPipeline(pipelineName string) (*api.PipelineStatus, error) { + return api.GetPipeline(cli, pipelineName) +} + +func (cli *Client) GetPipelineUpdate(pipelineName string) (*api.UpdatePipelineArgs, error) { + return api.GetPipelineUpdate(cli, pipelineName) +} + +// delete pipeline by pipelineName +func (cli *Client) DeletePipeline(pipelineName string) error { + return api.DeletePipeline(cli, pipelineName) +} + +// update pipeline with UpdatePipelineArgs +func (cli *Client) UpdatePipeline(pipelineName string, updatePipelineArgs *api.UpdatePipelineArgs) error { + return api.UpdatePipeline(cli, pipelineName, updatePipelineArgs) +} + +// create transcoding job with pipelineName, sourceKey, targetKey, presetName +func (cli *Client) CreateJob(pipelineName, sourceKey, targetKey, presetName string) (*api.CreateJobResponse, error) { + return api.CreateJob(cli, pipelineName, sourceKey, targetKey, presetName) +} + +// create trandcoding job with customize params +func (cli *Client) CreateJobCustomize(args *api.CreateJobArgs) (*api.CreateJobResponse, error) { + return api.CreateJobCustomize(cli, args) +} + +// list all jobs with piplineName +func (cli *Client) ListTranscodingJobs(pipelineName string) (*api.ListTranscodingJobsResponse, error) { + return api.ListTranscodingJobs(cli, pipelineName) +} + +// get transcoding job by jobId +func (cli *Client) GetTranscodingJob(jobId string) (*api.GetTranscodingJobResponse, error) { + return api.GetTranscodingJob(cli, jobId) +} + +// list all presets +func (cli *Client) ListPresets() (*api.ListPresetsResponse, error) { + return api.ListPresets(cli) +} + +// get preset by presetName +func (cli *Client) GetPreset(presetName string) (*api.Preset, error) { + return api.GetPreset(cli, presetName) +} + +// create preset at the same time perform container format conversion +func (cli *Client) CreatePreset(presetName, description, container string) error { + return api.CreatePreset(cli, presetName, description, container) +} + +// create preset with user-defined configuration +func (cli *Client) CreatePrestCustomize(preset *api.Preset) error { + return api.CreatePrestCustomize(cli, preset) +} + +// update preset +func (cli *Client) UpdatePreset(preset *api.Preset) error { + return api.UpdatePreset(cli, preset) +} + +// get media information with bucket and key +func (cli *Client) GetMediaInfoOfFile(bucket, key string) (*api.GetMediaInfoOfFileResponse, error) { + return api.GetMediaInfoOfFile(cli, bucket, key) +} + +// this option implements create thumbnail job function overloading +type Option func(thumbnailOptional *api.ThumbnailOptional) + +func PresetNameOp(presetName string) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.PresetName = presetName + } +} + +func TargetOp(target *api.ThumbnailTarget) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.Target = target + } +} + +func CaptureOp(capture *api.ThumbnailCapture) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.Capture = capture + } +} + +func DelogoAreaOp(delogoArea *api.Area) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.DelogoArea = delogoArea + } +} + +func CropOp(crop *api.Area) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.Crop = crop + } +} + +func SourceOp(source *api.ThumbnailSource) Option { + return func(thumbnailOptional *api.ThumbnailOptional) { + thumbnailOptional.ThumbnailSource = source + } +} + +// create thumbnail job. +// you can create a thumbnail job with pipelineName and sourceKey and ThumbnailCapture and ThumbnailTarget or other args +func (cli *Client) CreateThumbnailJob(pipelineName, sourceKey string, ops ...Option) (*api.CreateJobResponse, error) { + var thumbnailOptional api.ThumbnailOptional + for _, op := range ops { + op(&thumbnailOptional) + } + + createThumbnialArgs := &api.CreateThumbnailJobArgs{} + createThumbnialArgs.PipelineName = pipelineName + source := &api.ThumbnailSource{} + source.Key = sourceKey + createThumbnialArgs.ThumbnailSource = source + createThumbnialArgs.PresetName = thumbnailOptional.PresetName + target := thumbnailOptional.Target + createThumbnialArgs.ThumbnailTarget = target + createThumbnialArgs.ThumbnailCapture = thumbnailOptional.Capture + createThumbnialArgs.Area = thumbnailOptional.DelogoArea + createThumbnialArgs.Crop = thumbnailOptional.Crop + return api.CreateThumbnailJob(cli, pipelineName, sourceKey, createThumbnialArgs) +} + +// query thumbanil job by jobId +func (cli *Client) GetThumbanilJob(jobId string) (*api.GetThumbnailJobResponse, error) { + return api.GetThumbanilJob(cli, jobId) +} + +// get thumbanil job by pipelineName +func (cli *Client) ListThumbnailJobs(pipelineName string) (*api.ListThumbnailJobsResponse, error) { + return api.ListThumbnailJobs(cli, pipelineName) +} + +// create watermark job +func (cli *Client) CreateWaterMark(watermarks *api.CreateWaterMarkArgs) (*api.CreateWaterMarkResponse, error) { + return api.CreateWaterMark(cli, watermarks) +} + +// get watermark by watermarkId +func (cli *Client) GetWaterMark(watermarkId string) (*api.GetWaterMarkResponse, error) { + return api.GetWaterMark(cli, watermarkId) +} + +// list user`s watermark by watermarkId +func (cli *Client) ListWaterMark() (*api.ListWaterMarkResponse, error) { + return api.ListWaterMark(cli) +} + +// delete watermark by watermarkId +func (cli *Client) DeleteWaterMark(watermarkId string) error { + return api.DeleteWaterMark(cli, watermarkId) +} + +// create notification with name and endpoint +func (cli *Client) CreateNotification(name, endpoint string) error { + return api.CreateNotification(cli, name, endpoint) +} + +// get notification by notification`name +func (cli *Client) GetNotification(name string) (*api.CreateNotificationArgs, error) { + return api.GetNotification(cli, name) +} + +// list all of user`s notification +func (cli *Client) ListNotification() (*api.ListNotificationsResponse, error) { + return api.ListNotification(cli) +} + +// delete notification by name +func (cli *Client) DeleteNotification(name string) error { + return api.DeleteNotification(cli, name) +} diff --git a/bce-sdk-go/services/media/client_test.go b/bce-sdk-go/services/media/client_test.go new file mode 100644 index 0000000..a64adcb --- /dev/null +++ b/bce-sdk-go/services/media/client_test.go @@ -0,0 +1,664 @@ +package media + +import ( + "encoding/json" + "fmt" + "github.com/baidubce/bce-sdk-go/services/media/api" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + MEDIA_CLIENT *Client +) + +type Conf struct { + AK string + SK string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + fmt.Printf("config json file of ak/sk not given: %+v\n", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + MEDIA_CLIENT, _ = NewClient(confObj.AK, confObj.SK, "") + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestCreatePipline(t *testing.T) { + err := MEDIA_CLIENT.CreatePipeline("test1", "go-sdk-test", "go-sdk-test", 10) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%s", "done") +} + +func TestListPipelines(t *testing.T) { + pipelines, err := MEDIA_CLIENT.ListPipelines() + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", pipelines) +} + +func TestGetPipeline(t *testing.T) { + pipeline, err := MEDIA_CLIENT.GetPipeline("go_sdk_test") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", pipeline) +} + +func TestDeletePipeline(t *testing.T) { + err := MEDIA_CLIENT.DeletePipeline("test11") + ExpectEqual(t.Errorf, err, nil) +} + +func TestUpdatePipeline(t *testing.T) { + args, _ := MEDIA_CLIENT.GetPipelineUpdate("test1") + args.Description = "update" + args.TargetBucket = "vwdemo" + args.SourceBucket = "vwdemo" + + config := &api.UpdatePipelineConfig{} + config.Capacity = 2 + config.Notification = "zz" + args.UpdatePipelineConfig = config + err := MEDIA_CLIENT.UpdatePipeline("test1", args) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateJob(t *testing.T) { + jobResponse, err := MEDIA_CLIENT.CreateJob("go_sdk_test", "01.mp4", "01_go_02.mp4", + "videoworks_system_preprocess_360p") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", jobResponse) +} + +func TestCreateJobCustomize(t *testing.T) { + args := &api.CreateJobArgs{} + args.PipelineName = "go_sdk_test" + source := &api.Source{Clips: &[]api.SourceClip{{ + SourceKey: "01.mp4", + EnableDelogo: false, + DurationInMillisecond: 6656, + StartTimeInSecond: 2}}} + args.Source = source + target := &api.Target{} + targetKey := "clips_playback_watermark_delogo_crop2.mp4" + watermarkId := "wmk-pcgqidaj13iv1eyf" + target.TargetKey = targetKey + watermarkIdSlice := append(target.WatermarkIds, watermarkId) + target.WatermarkIds = watermarkIdSlice + presetName := "go_test_customize_audio_video" + target.PresetName = presetName + + delogoArea := &api.Area{} + delogoArea.X = 10 + delogoArea.Y = 10 + delogoArea.Width = 30 + delogoArea.Height = 40 + target.DelogoArea = delogoArea + + args.Target = target + + jobResponse, err := MEDIA_CLIENT.CreateJobCustomize(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", jobResponse) +} + +func TestCreateJobCustomizeDelogoCrop(t *testing.T) { + args := &api.CreateJobArgs{} + args.PipelineName = "go_sdk_test" + source := &api.Source{Clips: &[]api.SourceClip{{ + SourceKey: "01.mp4", + EnableDelogo: false, + DurationInMillisecond: 6656, + StartTimeInSecond: 2}}} + args.Source = source + target := &api.Target{} + targetKey := "clips_playback_watermark_delogo_crop2.mp4" + watermarkId := "wmk-pcgqidaj13iv1eyf" + target.TargetKey = targetKey + watermarkIdSlice := append(target.WatermarkIds, watermarkId) + target.WatermarkIds = watermarkIdSlice + presetName := "go_test_customize_audio_video" + target.PresetName = presetName + + delogoArea := &api.Area{} + delogoArea.X = 10 + delogoArea.Y = 10 + delogoArea.Width = 30 + delogoArea.Height = 40 + target.DelogoArea = delogoArea + + args.Target = target + + jobResponse, err := MEDIA_CLIENT.CreateJobCustomize(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", jobResponse) +} + +func TestListTranscodingJobs(t *testing.T) { + listTranscodingJobsResponse, err := MEDIA_CLIENT.ListTranscodingJobs("go_sdk_test") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", listTranscodingJobsResponse) +} + +func TestGetTranscodingJob(t *testing.T) { + getTranscodingJobResponse, err := MEDIA_CLIENT.GetTranscodingJob("job-pbsq30p9161enwpz") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", getTranscodingJobResponse) +} + +func TestListPresets(t *testing.T) { + listPresetsResponse, err := MEDIA_CLIENT.ListPresets() + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", listPresetsResponse) +} + +func TestGetPreset(t *testing.T) { + getPresetResponse, err := MEDIA_CLIENT.GetPreset("q_go_test_customize_audio_video") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", getPresetResponse) + video := getPresetResponse.Video + t.Logf("%+v", video) + audio := getPresetResponse.Audio + t.Logf("%+v", audio) +} + +func TestCreatePreset(t *testing.T) { + err := MEDIA_CLIENT.CreatePreset("go_sdk_test_preset3", "测试go创建模板3", "mp4") + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeAudio(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "go_test_customize" + preset.Description = "自定义创建模板" + preset.Container = "mp4" + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + preset.Audio = audio + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeAudioEncryptionC(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "go_test_customize_encryption_clip" + preset.Description = "自定义创建模板" + preset.Container = "mp3" + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + preset.Audio = audio + + clip := &api.Clip{} + clip.StartTimeInSecond = 2 + clip.DurationInSecond = 10 + preset.Clip = clip + + encryption := &api.Encryption{} + encryption.Strategy = "PlayerBinding" + preset.Encryption = encryption + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeAudioEncryption(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "go_test_customize_encryption" + preset.Description = "自定义创建模板" + preset.Container = "mp3" + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + preset.Audio = audio + + encryption := &api.Encryption{} + encryption.Strategy = "PlayerBinding" + preset.Encryption = encryption + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeAudioVideo(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "q_go_test_customize_audio_video" + preset.Description = "自定义创建模板" + preset.Container = "mp4" + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + audio.Codec = "opus" + preset.Audio = audio + + video := &api.Video{} + //video.BitRateInBps = 1024000 + video.MaxHeigtInPixel = 1920 + video.MaxWidthInPixel = 1280 + video.CodecEnhance = true + video.Crf = 23 + video.RateControl = "crf" + preset.Video = video + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeClpAudVidEncWat(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "go_test_customize_clp_aud_vid_en_wat" + preset.Description = "自定义创建模板" + preset.Container = "mp4" + + clip := &api.Clip{} + clip.StartTimeInSecond = 0 + clip.DurationInSecond = 60 + preset.Clip = clip + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + preset.Audio = audio + + video := &api.Video{} + video.BitRateInBps = 1024000 + preset.Video = video + + encryption := &api.Encryption{} + encryption.Strategy = "PlayerBinding" + preset.Encryption = encryption + + preset.WatermarkID = "wmk-pc0rdhzbm8ff99qw" + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreatePrestCustomizeFullArgs(t *testing.T) { + preset := &api.Preset{} + preset.PresetName = "go_test_customize_full_args" + preset.Description = "全参数" + preset.Container = "hls" + preset.Transmux = false + + clip := &api.Clip{} + clip.StartTimeInSecond = 0 + clip.DurationInSecond = 60 + preset.Clip = clip + + audio := &api.Audio{} + audio.BitRateInBps = 256000 + preset.Audio = audio + + video := &api.Video{} + video.BitRateInBps = 1024000 + preset.Video = video + + encryption := &api.Encryption{} + encryption.Strategy = "PlayerBinding" + preset.Encryption = encryption + + water := &api.Watermarks{} + water.Image = []string{"wmk-pc0rdhzbm8ff99qw"} + preset.Watermarks = water + + transCfg := &api.TransCfg{} + transCfg.TransMode = "normal" + preset.TransCfg = transCfg + + extraCfg := &api.ExtraCfg{} + extraCfg.SegmentDurationInSecond = 6.66 + preset.ExtraCfg = extraCfg + + err := MEDIA_CLIENT.CreatePrestCustomize(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestClient_UpdatePreset(t *testing.T) { + preset, _ := MEDIA_CLIENT.GetPreset("go_test_customize") + preset.Description = "测试update-v2" + err := MEDIA_CLIENT.UpdatePreset(preset) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetMediaInfoOfFile(t *testing.T) { + info, err := MEDIA_CLIENT.GetMediaInfoOfFile("go-test", "01.mp4") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", info) + videoInfo := info.VideoInfo + t.Logf("%+v", videoInfo) + audioInfo := info.AudioInfo + t.Logf("%+v", audioInfo) +} + +func TestCreateThumbnailJob(t *testing.T) { + target := &api.ThumbnailTarget{} + target.Format = "jpg" + target.SizingPolicy = "keep" + capture := &api.ThumbnailCapture{} + capture.Mode = "manual" + capture.StartTimeInSecond = 0.0 + capture.EndTimeInSecond = 5.0 + capture.IntervalInSecond = 1.0 + // params piplineName sourceKey target capture + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01.mp4", + TargetOp(target), CaptureOp(capture)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobTargetKeyPrefix(t *testing.T) { + //source := &api.ThumbnailSource{} + //source.Key = "01.mp4" + target := &api.ThumbnailTarget{} + target.KeyPrefix = "taget_key_prefix_test" + + // pipelineName presetName sourceKey targetKeyPrefix + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01_go_02.mp4", + PresetNameOp("test"), TargetOp(target)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobDelogo(t *testing.T) { + target := &api.ThumbnailTarget{} + target.KeyPrefix = "taget_key_prefix_test_delogo3" + delogo := &api.Area{} + delogo.X = 20 + delogo.Y = 20 + delogo.Height = 50 + delogo.Width = 80 + // piplineName sourceKey target capture delogo + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01_go_02.mp4", + TargetOp(target), DelogoAreaOp(delogo)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobDelogoCrop(t *testing.T) { + target := &api.ThumbnailTarget{} + target.KeyPrefix = "taget_key_prefix_test_delogo_crop" + delogo := &api.Area{} + delogo.X = 20 + delogo.Y = 20 + delogo.Height = 50 + delogo.Width = 80 + + crop := &api.Area{} + crop.X = 120 + crop.Y = 120 + crop.Height = 100 + crop.Width = 80 + // piplineName sourceKey target capture delogo crop + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01_go_02.mp4", + TargetOp(target), DelogoAreaOp(delogo), CropOp(crop)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobCaptureDelogo(t *testing.T) { + target := &api.ThumbnailTarget{} + target.Format = "jpg" + target.SizingPolicy = "keep" + + capture := &api.ThumbnailCapture{} + capture.Mode = "split" + capture.FrameNumber = 30 + + delogo := &api.Area{} + delogo.X = 20 + delogo.Y = 20 + delogo.Height = 50 + delogo.Width = 80 + // params piplineName sourceKey target capture delogo + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01.mp4", + TargetOp(target), CaptureOp(capture), DelogoAreaOp(delogo)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobCaptureDelogoCrop(t *testing.T) { + target := &api.ThumbnailTarget{} + target.Format = "jpg" + target.SizingPolicy = "keep" + + capture := &api.ThumbnailCapture{} + capture.Mode = "splitss0" + capture.FrameNumber = 10 + + delogo := &api.Area{} + delogo.X = 20 + delogo.Y = 20 + delogo.Height = 50 + delogo.Width = 80 + + crop := &api.Area{} + crop.X = 120 + crop.Y = 120 + crop.Height = 100 + crop.Width = 80 + // params piplineName sourceKey target capture delogo + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01.mp4", + TargetOp(target), CaptureOp(capture), DelogoAreaOp(delogo), CropOp(crop)) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestCreateThumbnailJobSimple(t *testing.T) { + // params piplineName sourceKey target capture delogo + createJobResponse, err := MEDIA_CLIENT.CreateThumbnailJob("go_sdk_test", "01.mp4") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createJobResponse) +} + +func TestGetThumbanilJob(t *testing.T) { + jobResponse, err := MEDIA_CLIENT.GetThumbanilJob("job-pcduuweehm1qd0et") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", jobResponse) + t.Logf("%+v", jobResponse.Source) + t.Logf("%+v", jobResponse.Target) + t.Logf("%+v", jobResponse.Capture) + t.Logf("%+v", jobResponse.DelogoArea) + t.Logf("%+v", jobResponse.Error) +} + +func TestListThumbnailJobs(t *testing.T) { + listThumbnailJobsResponse, err := MEDIA_CLIENT.ListThumbnailJobs("go_sdk_test") + ExpectEqual(t.Errorf, err, nil) + for _, job := range listThumbnailJobsResponse.Thumbnails { + t.Logf("%+v", job) + } +} + +func TestCreateWaterMark(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalOffsetInPixel, verticalOffsetInPixel + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalOffsetInPixel = 20 + args.VerticalOffsetInPixel = 10 + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHV(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalAlignment = "right" + args.VerticalAlignment = "top" + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHHVV(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalOffsetInPixel = 200 + args.HorizontalAlignment = "left" + args.VerticalOffsetInPixel = 200 + args.VerticalAlignment = "bottom" + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHVXY(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalAlignment = "center" + args.VerticalAlignment = "center" + args.Dy = "0.1" + args.Dy = "0.2" + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHVXYWH(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalAlignment = "center" + args.VerticalAlignment = "center" + args.Dy = "0.1" + args.Dy = "0.2" + args.Width = "0.15" + args.Height = "0.11" + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHVTRA(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "01.jpg" + args.HorizontalAlignment = "left" + args.VerticalAlignment = "top" + args.HorizontalOffsetInPixel = 20 + args.VerticalOffsetInPixel = 10 + timeline := &api.Timeline{} + timeline.StartTimeInMillisecond = 1000 + timeline.DurationInMillisecond = 3000 + args.Timeline = timeline + args.Repeated = 1 + args.AllowScaling = true + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestCreateWaterMarkHVTXYWHTR(t *testing.T) { + args := &api.CreateWaterMarkArgs{} + // bucket, key, horizontalAlignment, verticalAlignment + args.Bucket = "go-test" + args.Key = "tupian.jpg" + args.HorizontalAlignment = "center" + args.VerticalAlignment = "center" + args.Dy = "0.1" + args.Dy = "0.2" + args.Width = "150" + args.Height = "110" + timeline := &api.Timeline{} + timeline.StartTimeInMillisecond = 1 + timeline.DurationInMillisecond = 5 + args.Timeline = timeline + args.Repeated = 10 + createWaterMarkResponse, err := MEDIA_CLIENT.CreateWaterMark(args) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", createWaterMarkResponse) +} + +func TestGetWaterMark(t *testing.T) { + response, err := MEDIA_CLIENT.GetWaterMark("wmk-pcep0x4vvmvvx84r") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) + t.Logf("%+v", response.Timeline) +} + +func TestListWaterMark(t *testing.T) { + response, err := MEDIA_CLIENT.ListWaterMark() + ExpectEqual(t.Errorf, err, nil) + for _, watermark := range response.Watermarks { + t.Logf("%+v", watermark) + } +} + +func TestDeleteWaterMark(t *testing.T) { + err := MEDIA_CLIENT.DeleteWaterMark("wmk-pcep0x4vvmvvx84r") + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateNotification(t *testing.T) { + err := MEDIA_CLIENT.CreateNotification("test", "http://www.baidu.com") + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetNotification(t *testing.T) { + response, err := MEDIA_CLIENT.GetNotification("zz") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", response) +} + +func TestListNotification(t *testing.T) { + response, err := MEDIA_CLIENT.ListNotification() + ExpectEqual(t.Errorf, err, nil) + for _, notification := range response.Notifications { + t.Logf("%+v", notification) + } +} + +func TestDeleteNotification(t *testing.T) { + err := MEDIA_CLIENT.DeleteNotification("test") + ExpectEqual(t.Errorf, err, nil) +} diff --git a/bce-sdk-go/services/mms/api/api.go b/bce-sdk-go/services/mms/api/api.go new file mode 100644 index 0000000..ca85246 --- /dev/null +++ b/bce-sdk-go/services/mms/api/api.go @@ -0,0 +1,303 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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 api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func InsertVideo(cli bce.Client, lib string, args *BaseRequest) (*BaseResponse, error) { + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + + resp, err := sendRequest(cli, http.PUT, URI_PREFIX+VIDEO_URI+"/"+lib, jsonBytes, map[string]string{}) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil + +} + +func GetInsertVideoResult(cli bce.Client, lib, source string) (*BaseResponse, error) { + + params := map[string]string{ + "source": source, + } + + resp, err := sendRequest(cli, http.GET, URI_PREFIX+VIDEO_URI+"/"+lib, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func GetInsertVideoResultById(cli bce.Client, libId, mediaId string) (*BaseResponse, error) { + + params := map[string]string{ + "mediaId": mediaId, + "getInsertResponseById": "", + } + + resp, err := sendRequest(cli, http.GET, URI_PREFIX+VIDEO_URI+"/"+libId, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func DeleteVideo(cli bce.Client, lib, source string) (*BaseResponse, error) { + + params := map[string]string{ + "source": source, + "deleteVideo": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+VIDEO_URI+"/"+lib, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func DeleteVideoById(cli bce.Client, libId, mediaId string) (*BaseResponse, error) { + + params := map[string]string{ + "mediaId": mediaId, + "deleteVideoById": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+VIDEO_URI+"/"+libId, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func InsertImage(cli bce.Client, lib string, args *BaseRequest) (*BaseResponse, error) { + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + + resp, err := sendRequest(cli, http.PUT, URI_PREFIX+IMAGE_URI+"/"+lib, jsonBytes, map[string]string{}) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func DeleteImage(cli bce.Client, lib, source string) (*BaseResponse, error) { + + params := map[string]string{ + "source": source, + "deleteImage": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+IMAGE_URI+"/"+lib, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func DeleteImageById(cli bce.Client, libId, mediaId string) (*BaseResponse, error) { + + params := map[string]string{ + "mediaId": mediaId, + "deleteImageById": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+IMAGE_URI+"/"+libId, []byte{}, params) + res := &BaseResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func SearchImageByImage(cli bce.Client, lib string, args *BaseRequest) (*SearchTaskResultResponse, error) { + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + + params := map[string]string{ + "searchByImage": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+IMAGE_URI+"/"+lib, jsonBytes, params) + res := &SearchTaskResultResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func SearchVideoByImage(cli bce.Client, lib string, args *BaseRequest) (*SearchTaskResultResponse, error) { + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + + params := map[string]string{ + "searchByImage": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+VIDEO_URI+"/"+lib, jsonBytes, params) + res := &SearchTaskResultResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func SearchVideoByVideo(cli bce.Client, lib string, args *BaseRequest) (*SearchTaskResultResponse, error) { + + jsonBytes, err := json.Marshal(args) + if err != nil { + return nil, err + } + + params := map[string]string{ + "searchByVideo": "", + } + + resp, err := sendRequest(cli, http.POST, URI_PREFIX+VIDEO_URI+"/"+lib, jsonBytes, params) + res := &SearchTaskResultResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func GetSearchVideoByVideoResult(cli bce.Client, lib, source string) (*SearchTaskResultResponse, error) { + + params := map[string]string{ + "searchByVideo": "", + "source": source, + } + + resp, err := sendRequest(cli, http.GET, URI_PREFIX+VIDEO_URI+"/"+lib, []byte{}, params) + res := &SearchTaskResultResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func GetSearchVideoByVideoResultById(cli bce.Client, lib, taskId string) (*SearchTaskResultResponse, error) { + + params := map[string]string{ + "getSearchResponseByTaskId": "", + "taskId": taskId, + } + + resp, err := sendRequest(cli, http.GET, URI_PREFIX+VIDEO_URI+"/"+lib, []byte{}, params) + res := &SearchTaskResultResponse{} + if err != nil { + return nil, err + } + + if err := resp.ParseJsonBody(res); err != nil { + return nil, err + } + return res, nil +} + +func sendRequest(cli bce.Client, httpMethod, url string, bodyJson []byte, params map[string]string) (*bce.BceResponse, error) { + req := &bce.BceRequest{} + req.SetHeader(http.CONTENT_TYPE, "application/json;charset=utf-8") + req.SetUri(url) + req.SetMethod(httpMethod) + req.SetParams(params) + + body, err := bce.NewBodyFromBytes(bodyJson) + if err != nil { + return nil, err + } + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + return resp, nil +} diff --git a/bce-sdk-go/services/mms/api/model.go b/bce-sdk-go/services/mms/api/model.go new file mode 100644 index 0000000..0e3993f --- /dev/null +++ b/bce-sdk-go/services/mms/api/model.go @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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 api + +import ( + "time" + + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v2" + VIDEO_URI = "/videolib" + IMAGE_URI = "/imagelib" +) + +type BaseRequest struct { + Source string `json:"source"` + Description string `json:"description"` + Notification string `json:"notification"` + NeedTag bool `json:"needTag"` +} + +type BaseResponse struct { + TaskID string `json:"taskId"` + Status string `json:"status"` + Description string `json:"description"` + Duration float64 `json:"duration"` + Error struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"error"` + Lib string `json:"lib"` + Source string `json:"source"` + UpdateTime time.Time `json:"updateTime"` + StartTime time.Time `json:"startTime"` + FinishTime time.Time `json:"finishTime"` + CreateTime time.Time `json:"createTime,"` +} + +type MatchFrame struct { + Distance float64 `json:"distance"` + Position int `json:"position"` + Timestamp float64 `json:"timestamp"` +} + +type VideoClip struct { + Clip bool `json:"clip"` + ClipNum int `json:"clipNum"` + Distance float64 `json:"distance"` + FrameNum int `json:"frameNum"` + InputEndPos int `json:"inputEndPos"` + InputEndTime float64 `json:"inputEndTime"` + InputStartPos int `json:"inputStartPos"` + InputStartTime float64 `json:"inputStartTime"` + InputSumTime float64 `json:"inputSumTime"` + MatchNum int `json:"matchNum"` + OutputEndPos int `json:"outputEndPos"` + OutputEndTime float64 `json:"outputEndTime"` + OutputStartPos int `json:"outputStartPos"` + OutputStartTime float64 `json:"outputStartTime"` + OutputSumTime float64 `json:"outputSumTime"` + PreIdx int `json:"preIdx"` +} + +type SearchTaskResult struct { + Cover string `json:"cover"` + Description string `json:"description"` + Distance float64 `json:"distance"` + Duration float64 `json:"duration"` + ID string `json:"id"` + Name string `json:"name"` + Score float64 `json:"score"` + Source string `json:"source"` + Type string `json:"type"` + Frames []MatchFrame `json:"frames"` + Clips []VideoClip `json:"clips"` +} + +type SearchTaskResultResponse struct { + BaseResponse + Results []SearchTaskResult `json:"results"` + TagResults []SearchTaskResult `json:"tagResults"` +} diff --git a/bce-sdk-go/services/mms/client.go b/bce-sdk-go/services/mms/client.go new file mode 100644 index 0000000..5636f10 --- /dev/null +++ b/bce-sdk-go/services/mms/client.go @@ -0,0 +1,212 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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 mms + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/mms/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "mms." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of MMS service +type Client struct { + *bce.BceClient +} + +// NewClient make the MMS service client +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// InsertVideo - insert a video, and create a video recognize task +// PARAMS: +// - lib: vedio library name +// - args: vedio source and description +// +// RETURN: +// - BaseResponse: the result of insert a video +// - error: nil if success otherwise the specific error +func (c *Client) InsertVideo(lib string, args *api.BaseRequest) (*api.BaseResponse, error) { + return api.InsertVideo(c, lib, args) +} + +// GetInsertVideoResult - get video insert result and video recognize task result +// PARAMS: +// - lib: vedio library name +// - source: vedio source +// +// RETURN: +// - BaseResponse: the result of get result +// - error: nil if success otherwise the specific error +func (c *Client) GetInsertVideoResult(lib, source string) (*api.BaseResponse, error) { + return api.GetInsertVideoResult(c, lib, source) +} + +// GetInsertVideoResultById - get video insert result and video recognize task result by id +// PARAMS: +// - libId: vedio library id +// - mediaId: vedio id +// +// RETURN: +// - BaseResponse: the result of get result +// - error: nil if success otherwise the specific error +func (c *Client) GetInsertVideoResultById(libId, mediaId string) (*api.BaseResponse, error) { + return api.GetInsertVideoResultById(c, libId, mediaId) +} + +// DeleteVideo - delete a video +// PARAMS: +// - lib: vedio library name +// - source: vedio source +// +// RETURN: +// - BaseResponse: the result of delete video +// - error: nil if success otherwise the specific error +func (c *Client) DeleteVideo(lib, source string) (*api.BaseResponse, error) { + return api.DeleteVideo(c, lib, source) +} + +// DeleteVideoById - delete a video +// PARAMS: +// - libId: video library id +// - mediaId: video id +// +// RETURN: +// - BaseResponse: the result of delete video +// - error: nil if success otherwise the specific error +func (c *Client) DeleteVideoById(libId, mediaId string) (*api.BaseResponse, error) { + return api.DeleteVideoById(c, libId, mediaId) +} + +// InsertImage - insert a image +// PARAMS: +// - lib: image library name +// - args: image source and description +// +// RETURN: +// - BaseResponse: the result of insert a image +// - error: nil if success otherwise the specific error +func (c *Client) InsertImage(lib string, args *api.BaseRequest) (*api.BaseResponse, error) { + return api.InsertImage(c, lib, args) +} + +// DeleteImage - delete a image +// PARAMS: +// - lib: image library name +// - source: image source +// +// RETURN: +// - BaseResponse: the result of delete a image +// - error: nil if success otherwise the specific error +func (c *Client) DeleteImage(lib, source string) (*api.BaseResponse, error) { + return api.DeleteImage(c, lib, source) +} + +// DeleteImageById - delete a image +// PARAMS: +// - libId: image library id +// - mediaId: image id +// +// RETURN: +// - BaseResponse: the result of delete a image +// - error: nil if success otherwise the specific error +func (c *Client) DeleteImageById(libId, mediaId string) (*api.BaseResponse, error) { + return api.DeleteImageById(c, libId, mediaId) +} + +// SearchImageByImage - search images by a image +// PARAMS: +// - lib: image library name +// - args: image source and description +// +// RETURN: +// - BaseResponse: the result of search +// - error: nil if success otherwise the specific error +func (c *Client) SearchImageByImage(lib string, args *api.BaseRequest) (*api.SearchTaskResultResponse, error) { + return api.SearchImageByImage(c, lib, args) +} + +// SearchVideoByImage - search videos by a image +// PARAMS: +// - lib: video library name +// - args: image source and description +// +// RETURN: +// - BaseResponse: the result of search +// - error: nil if success otherwise the specific error +func (c *Client) SearchVideoByImage(lib string, args *api.BaseRequest) (*api.SearchTaskResultResponse, error) { + return api.SearchVideoByImage(c, lib, args) +} + +// SearchVideoByVideo - create a search videos by a video task +// PARAMS: +// - lib: video library name +// - args: video source and description +// +// RETURN: +// - BaseResponse: search task info +// - error: nil if success otherwise the specific error +func (c *Client) SearchVideoByVideo(lib string, args *api.BaseRequest) (*api.SearchTaskResultResponse, error) { + return api.SearchVideoByVideo(c, lib, args) +} + +// GetSearchVideoByVideoResult - get result of searching videos by video +// PARAMS: +// - lib: video library name +// - source: video source +// +// RETURN: +// - BaseResponse: the result of searching videos by video +// - error: nil if success otherwise the specific error +func (c *Client) GetSearchVideoByVideoResult(lib, source string) (*api.SearchTaskResultResponse, error) { + return api.GetSearchVideoByVideoResult(c, lib, source) +} + +// GetSearchVideoByVideoResultById - get result of searching videos by taskId +// PARAMS: +// - lib: video library name +// - taskId: search task id +// +// RETURN: +// - BaseResponse: the result of searching videos by video +// - error: nil if success otherwise the specific error +func (c *Client) GetSearchVideoByVideoResultById(lib, taskId string) (*api.SearchTaskResultResponse, error) { + return api.GetSearchVideoByVideoResultById(c, lib, taskId) +} diff --git a/bce-sdk-go/services/mms/client_test.go b/bce-sdk-go/services/mms/client_test.go new file mode 100644 index 0000000..72e64ce --- /dev/null +++ b/bce-sdk-go/services/mms/client_test.go @@ -0,0 +1,180 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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 mms + +import ( + "testing" + + "github.com/baidubce/bce-sdk-go/services/mms/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var CLIENT *Client + +const ( + AK = "" + SK = "" + VIDEO_LIB = "" + VIDEO_LIB_ID = "" + IMAGE_LIB = "" + IMAGE_LIB_ID = "" + VIDEO_SOURCE = "http://xxx.mp4" + IMAGE_SOURCE = "http://xxx.png" + ENDPOINT = "http://xxx" + VIDEO_ID = "" + IMAGE_ID = "" + TASK_ID = "" +) + +func init() { + CLIENT, _ = NewClient(AK, SK, ENDPOINT) + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) +} + +func TestInsertVideo(t *testing.T) { + args := &api.BaseRequest{Source: VIDEO_SOURCE, Notification: "test", Description: "..."} + res, err := CLIENT.InsertVideo(VIDEO_LIB, args) + if err != nil { + t.Fatalf("InsertVideo failed: %s", err) + } + if res == nil { + t.Fatal("TestInsertVideo Failed. ") + } +} + +func TestGetInsertVideoResult(t *testing.T) { + res, err := CLIENT.GetInsertVideoResult(VIDEO_LIB, VIDEO_SOURCE) + if err != nil { + t.Fatalf("TestGetInsertVideoResult failed: %s", err) + } + if res.Source != VIDEO_SOURCE { + t.Fatal("TestGetInsertVideoResult Failed.") + } +} + +func TestGetInsertVideoResultById(t *testing.T) { + res, err := CLIENT.GetInsertVideoResultById(VIDEO_LIB_ID, VIDEO_ID) + if err != nil { + t.Fatalf("TestGetInsertVideoResult failed: %s", err) + } + if res.Source != VIDEO_SOURCE { + t.Fatal("TestGetInsertVideoResult Failed.") + } +} + +func TestDeleteVideo(t *testing.T) { + res, err := CLIENT.DeleteVideo(VIDEO_LIB, VIDEO_SOURCE) + if err != nil { + t.Fatalf("TestDeleteVideo failed: %s", err) + } + if res == nil { + t.Fatal("TestDeleteVideo Failed.") + } +} + +func TestDeleteVideoById(t *testing.T) { + res, err := CLIENT.DeleteVideoById(VIDEO_LIB_ID, VIDEO_ID) + if err != nil { + t.Fatalf("DeleteVideoById failed: %s", err) + } + if res == nil { + t.Fatal("DeleteVideoById Failed.") + } +} + +func TestInsertImage(t *testing.T) { + args := &api.BaseRequest{Source: IMAGE_SOURCE, Notification: "test", Description: "..."} + res, err := CLIENT.InsertImage(IMAGE_LIB, args) + if err != nil { + t.Fatalf("TestInsertImage failed: %s", err) + } + if res == nil { + t.Fatal("TestInsertImage Failed. ") + } +} + +func TestDeleteImage(t *testing.T) { + res, err := CLIENT.DeleteVideo(IMAGE_LIB, IMAGE_SOURCE) + if err != nil { + t.Fatalf("TestDeleteImage failed: %s", err) + } + if res == nil { + t.Fatal("TestDeleteImage Failed.") + } +} + +func TestDeleteImageById(t *testing.T) { + res, err := CLIENT.DeleteVideoById(IMAGE_LIB_ID, IMAGE_ID) + if err != nil { + t.Fatalf("TestDeleteImageById failed: %s", err) + } + if res == nil { + t.Fatal("TestDeleteImageById Failed.") + } +} + +func TestSearchImageByImage(t *testing.T) { + args := &api.BaseRequest{Source: IMAGE_SOURCE, Notification: "test", Description: "..."} + res, err := CLIENT.SearchImageByImage(IMAGE_LIB, args) + if err != nil { + t.Fatalf("TestSearchImageByImage failed: %s", err) + } + if res == nil { + t.Fatal(res) + } +} + +func TestSearchVideoByImage(t *testing.T) { + args := &api.BaseRequest{Source: IMAGE_SOURCE, Notification: "test", Description: "..."} + res, err := CLIENT.SearchVideoByImage(VIDEO_LIB, args) + if err != nil { + t.Fatalf("TestSearchVideoByImage failed: %s", err) + } + if res == nil { + t.Fatal(res) + } +} + +func TestSearchVideoByVideo(t *testing.T) { + args := &api.BaseRequest{Source: VIDEO_SOURCE, Notification: "test", Description: "..."} + res, err := CLIENT.SearchVideoByVideo(VIDEO_LIB, args) + if err != nil { + t.Fatalf("TestSearchVideoByVideo failed: %s", err) + } + if res == nil { + t.Fatal(res) + } +} + +func TestGetSearchVideoByVideoResult(t *testing.T) { + res, err := CLIENT.GetSearchVideoByVideoResult(VIDEO_LIB, VIDEO_SOURCE) + if err != nil { + t.Fatalf("TestGetSearchVideoByVideoResult failed: %s", err) + } + if res == nil { + t.Fatal(res) + } +} + +func TestGetSearchVideoByVideoResultById(t *testing.T) { + res, err := CLIENT.GetSearchVideoByVideoResultById(VIDEO_LIB, TASK_ID) + if err != nil { + t.Fatalf("TestGetSearchVideoByVideoResultById failed: %s", err) + } + if res == nil { + t.Fatal(res) + } +} diff --git a/bce-sdk-go/services/quotacenter/client.go b/bce-sdk-go/services/quotacenter/client.go new file mode 100644 index 0000000..a02dc4e --- /dev/null +++ b/bce-sdk-go/services/quotacenter/client.go @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for QuotaCenter service + +// Package quotacenter defines the QuotaCenter services of BCE. The supported APIs are all defined in sub-package +package quotacenter + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "quota-center.baidubce.com" + + BASE_QUOTA_CENTER_URL = "/quota_center" + + BASE_PRODUCT_URL = "/info/product" + + BASE_INFO_URL = "/info" + + BASE_REGION_URL = "/info/region" + + BASE_APPLY_URL = "/apply" +) + +// Client of QUOTA_CENTER service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getQuotaCenterUri() string { + return URI_PREFIX + BASE_QUOTA_CENTER_URL +} + +func getProductUri() string { + return getQuotaCenterUri() + BASE_PRODUCT_URL +} + +func getInfoUri() string { + return getQuotaCenterUri() + BASE_INFO_URL +} + +func getRegionUri() string { + return getQuotaCenterUri() + BASE_REGION_URL +} + +func getApplyUri() string { + return getQuotaCenterUri() + BASE_APPLY_URL +} + +func getApplyUriWithId(id string) string { + return getApplyUri() + bce.URI_PREFIX + id +} diff --git a/bce-sdk-go/services/quotacenter/client_test.go b/bce-sdk-go/services/quotacenter/client_test.go new file mode 100644 index 0000000..dd35bfb --- /dev/null +++ b/bce-sdk-go/services/quotacenter/client_test.go @@ -0,0 +1,135 @@ +package quotacenter + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + QUOTA_CENTER_CLIENT *Client +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + QUOTA_CENTER_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +func TestClient_ListProducts(t *testing.T) { + args := &ProductQueryArgs{} + result, err := QUOTA_CENTER_CLIENT.ListProducts(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } + +} + +func TestClient_ListRegions(t *testing.T) { + args := &RegionQueryArgs{ + ProductType: "EIP", + ServiceType: "EIP_BP", + Type: "QUOTA", + } + result, err := QUOTA_CENTER_CLIENT.ListRegions(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_QuotaCenterQuery(t *testing.T) { + args := &QuotaCenterQueryArgs{ + ServiceType: "EIP", + Type: "QUOTA", + Region: "su", + } + result, err := QUOTA_CENTER_CLIENT.QuotaCenterQuery(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_InfoQuery(t *testing.T) { + args := &InfoQueryArgs{ + Region: "bj", + } + result, err := QUOTA_CENTER_CLIENT.InfoQuery(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_Apply(t *testing.T) { + args := &ApplicationCreateModel{ + ProductType: "EIP", + ServiceType: "EIP", + Region: "bj", + Name: "eipInstanceQuota", + Value: "280", + Reason: "we need more again", + } + result, err := QUOTA_CENTER_CLIENT.Apply(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_ApplicationQuery(t *testing.T) { + args := &ApplicationQueryArgs{ + Status: "EFFECTED", + } + result, err := QUOTA_CENTER_CLIENT.ApplicationQuery(args) + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} + +func TestClient_ApplicationDetail(t *testing.T) { + result, err := QUOTA_CENTER_CLIENT.ApplicationDetail("app-uh4eaumggx4q") + if err != nil { + fmt.Println(err) + } else { + r, _ := json.Marshal(result) + fmt.Println(string(r)) + } +} diff --git a/bce-sdk-go/services/quotacenter/config.json b/bce-sdk-go/services/quotacenter/config.json new file mode 100644 index 0000000..2c90a99 --- /dev/null +++ b/bce-sdk-go/services/quotacenter/config.json @@ -0,0 +1,5 @@ +{ + "AK": "", + "SK": "", + "Endpoint": "" +} diff --git a/bce-sdk-go/services/quotacenter/model.go b/bce-sdk-go/services/quotacenter/model.go new file mode 100644 index 0000000..5e8a7d3 --- /dev/null +++ b/bce-sdk-go/services/quotacenter/model.go @@ -0,0 +1,148 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model +package quotacenter + +type QuotaCenterQueryArgs struct { + Type string `json:"type"` + ServiceType string `json:"serviceType"` + Region string `json:"region"` + Name string `json:"name,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListQuotaResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []QuotaModel `json:"result"` +} + +type QuotaModel struct { + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` + Region string `json:"region"` + Name string `json:"name"` + Description string `json:"description"` + Value string `json:"value"` + Used string `json:"used"` +} + +type ProductQueryArgs struct { + ProductType string `json:"productType,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListProductResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []ProductModel `json:"result"` +} + +type ProductModel struct { + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` +} + +type RegionQueryArgs struct { + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` +} + +type ListRegionResult struct { + Regions []string `json:"regions"` +} + +type InfoQueryArgs struct { + ServiceType string `json:"serviceType,omitempty"` + Region string `json:"region,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListInfoResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []InfoModel `json:"result"` +} + +type InfoModel struct { + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` + Region string `json:"region"` + Name string `json:"name"` + Description string `json:"description"` + Apply bool `json:"apply"` +} + +type ApplicationCreateModel struct { + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` + Region string `json:"region"` + Name string `json:"name"` + Value string `json:"value"` + Reason string `json:"reason"` +} + +type IdModel struct { + id string `json:"id"` +} + +type ApplicationQueryArgs struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + ProductType string `json:"status,productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` + Region string `json:"region"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListApplicationResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []ApplicationModel `json:"result"` +} + +type ApplicationModel struct { + Id string `json:"id"` + ProductType string `json:"productType"` + ServiceType string `json:"serviceType"` + Type string `json:"type"` + Region string `json:"region"` + Name string `json:"name"` + Value string `json:"value"` + Reason string `json:"reason"` + Status string `json:"status"` + Conclusion string `json:"conclusion"` + CreateTime string `json:"createTime"` + EffectTime string `json:"effectTime,omitempty"` + ApproveTime string `json:"approveTime,omitempty"` +} diff --git a/bce-sdk-go/services/quotacenter/quota_center.go b/bce-sdk-go/services/quotacenter/quota_center.go new file mode 100644 index 0000000..c73ec36 --- /dev/null +++ b/bce-sdk-go/services/quotacenter/quota_center.go @@ -0,0 +1,238 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// quota_center.go - the quota_center APIs definition supported by the QUOTA_CENTER service +package quotacenter + +import ( + "errors" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListProducts - list quota center support products. +// +// PARAMS: +// - args: the arguments to list products. +// +// RETURNS: +// - *ListProductResult: the result of list products. +// - error: nil if success otherwise the specific error +func (c *Client) ListProducts(args *ProductQueryArgs) (*ListProductResult, error) { + if args == nil { + args = &ProductQueryArgs{} + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListProductResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getProductUri()). + WithQueryParamFilter("productType", args.ProductType). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// ListRegions - list quota center support regions with the specific parameters. +// +// PARAMS: +// - args: the arguments to list regions. +// +// RETURNS: +// - *ListRegionResult: the result of regions. +// - error: nil if success otherwise the specific error +func (c *Client) ListRegions(args *RegionQueryArgs) (*ListRegionResult, error) { + if args == nil { + args = &RegionQueryArgs{} + } + result := &ListRegionResult{} + if len(args.ProductType) == 0 { + return nil, errors.New("productType should not be empty") + } + if len(args.ServiceType) == 0 { + return nil, errors.New("serviceType should not be empty") + } + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRegionUri()). + WithQueryParamFilter("productType", args.ProductType). + WithQueryParamFilter("serviceType", args.ServiceType). + WithQueryParamFilter("type", args.Type). + WithResult(result). + Do() + + return result, err +} + +// QuotaCenterQuery - query from quota_center with the specific parameters +// +// PARAMS: +// - args: the arguments to query quota_center +// +// RETURNS: +// - *ListQuotaResult: the result of query from quota_center. +// - error: nil if success otherwise the specific error +func (c *Client) QuotaCenterQuery(args *QuotaCenterQueryArgs) (*ListQuotaResult, error) { + if args == nil { + args = &QuotaCenterQueryArgs{} + } + if len(args.ServiceType) == 0 { + return nil, errors.New("serviceType should not be empty") + } + if len(args.Region) == 0 { + return nil, errors.New("region should not be empty") + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListQuotaResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getQuotaCenterUri()). + WithQueryParamFilter("type", args.Type). + WithQueryParamFilter("serviceType", args.ServiceType). + WithQueryParamFilter("region", args.Region). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// InfoQuery - query basic infos from quota_center with the specific parameters +// +// PARAMS: +// - args: the arguments to query infos. +// +// RETURNS: +// - *ListInfoResult: the result of infos from quota_center. +// - error: nil if success otherwise the specific error +func (c *Client) InfoQuery(args *InfoQueryArgs) (*ListInfoResult, error) { + if args == nil { + args = &InfoQueryArgs{} + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListInfoResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInfoUri()). + WithQueryParamFilter("serviceType", args.ServiceType). + WithQueryParamFilter("region", args.Region). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// Apply - apply quota or whitelist with the specific parameters +// +// PARAMS: +// - args: the arguments to apply. +// RETURNS: +// - *IdModel: the id of application. +// - error: nil if success otherwise the specific error + +func (c *Client) Apply(args *ApplicationCreateModel) (*IdModel, error) { + if args == nil { + args = &ApplicationCreateModel{} + } + + result := &IdModel{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithURL(getApplyUri()). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ApplicationQuery - query applications from quota_center with the specific parameters +// +// PARAMS: +// - args: the arguments to query application. +// +// RETURNS: +// - *ListApplicationResult: the result of applications. +// - error: nil if success otherwise the specific error +func (c *Client) ApplicationQuery(args *ApplicationQueryArgs) (*ListApplicationResult, error) { + if args == nil { + args = &ApplicationQueryArgs{} + } + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + result := &ListApplicationResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getApplyUri()). + WithQueryParamFilter("id", args.Id). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("status", args.Status). + WithQueryParamFilter("productType", args.ProductType). + WithQueryParamFilter("serviceType", args.ServiceType). + WithQueryParamFilter("type", args.Type). + WithQueryParamFilter("region", args.Region). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// ApplicationDetail - query application detail from quota_center with id. +// +// PARAMS: +// - id: the application's id. +// +// RETURNS: +// - *ApplicationModel: the result of application. +// - error: nil if success otherwise the specific error +func (c *Client) ApplicationDetail(id string) (*ApplicationModel, error) { + if len(id) == 0 { + return nil, fmt.Errorf("please set id argment") + } + + result := &ApplicationModel{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getApplyUriWithId(id)). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/rds/client.go b/bce-sdk-go/services/rds/client.go new file mode 100644 index 0000000..2341568 --- /dev/null +++ b/bce-sdk-go/services/rds/client.go @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for RDS service + +// Package rds defines the RDS services of BCE. The supported APIs are all defined in sub-package +package rds + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + DEFAULT_ENDPOINT = "rds.bj.baidubce.com" + REQUEST_RDS_URL = "/instance" +) + +// Client of RDS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getRdsUri() string { + return URI_PREFIX + REQUEST_RDS_URL +} + +func getRdsUriWithInstanceId(instanceId string) string { + return URI_PREFIX + REQUEST_RDS_URL + "/" + instanceId +} diff --git a/bce-sdk-go/services/rds/client_test.go b/bce-sdk-go/services/rds/client_test.go new file mode 100644 index 0000000..3dd07db --- /dev/null +++ b/bce-sdk-go/services/rds/client_test.go @@ -0,0 +1,1179 @@ +package rds + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + RDS_CLIENT *Client + RDS_ID = "rds-W3NYp4m9" + ORDERID string + // set this value before start test + ACCOUNT_NAME = "baidu" + PASSWORD = "testpassword" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +const ( + SDK_NAME_PREFIX = "sdk_rds_" +) + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 1; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + RDS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateRds(t *testing.T) { + id := strconv.FormatInt(time.Now().Unix(), 10) + args := &CreateRdsArgs{ + Engine: "mysql", + EngineVersion: "5.6", + Category: "Standard", + InstanceName: SDK_NAME_PREFIX + id, + CpuCount: 1, + DiskIoType: "normal_io", + MemoryCapacity: 1, + VolumeCapacity: 5, + VpcId: "vpc-it3v6qt3jhvj", + ZoneNames: []string{"cn-bj-d"}, + Subnets: []SubnetMap{ + { + ZoneName: "cn-bj-d", + SubnetId: "sbn-na4tmg4v11hs", + }, + }, + Billing: Billing{ + PaymentTiming: "Postpaid", + }, + ClientToken: getClientToken(), + IsDirectPay: true, + } + result, err := RDS_CLIENT.CreateRds(args) + + ExpectEqual(t.Errorf, nil, err) + + RDS_ID = result.InstanceIds[0] + ORDERID = result.OrderId + fmt.Println("RDS: ", RDS_ID) + fmt.Println("ORDERID: ", ORDERID) + // isAvailable(RDS_ID) +} + +func TestClient_ResizeRds(t *testing.T) { + args := &ResizeRdsArgs{ + CpuCount: 1, + MemoryCapacity: 2, + VolumeCapacity: 10, + } + err := RDS_CLIENT.ResizeRds(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) + time.Sleep(30 * time.Second) + isAvailable(RDS_ID) +} + +func TestClient_ListRds(t *testing.T) { + args := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(args) + ExpectEqual(t.Errorf, nil, err) + jsonData, err := json.Marshal(result) + fmt.Println(string(jsonData)) + for _, e := range result.Instances { + if e.InstanceId == RDS_ID { + ExpectEqual(t.Errorf, "MySQL", e.Engine) + ExpectEqual(t.Errorf, "5.6", e.EngineVersion) + } + } +} + +func TestClient_GetDetail(t *testing.T) { + result, err := RDS_CLIENT.GetDetail("rds-W3NYp4m9") + re, error := json.Marshal(result) + fmt.Print(error) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "MySQL", result.Engine) + ExpectEqual(t.Errorf, "5.6", result.EngineVersion) +} + +func TestClient_CreateAccount(t *testing.T) { + + args := &CreateAccountArgs{ + AccountName: ACCOUNT_NAME, + Password: PASSWORD, + ClientToken: getClientToken(), + } + + isAvailable(RDS_ID) + err := RDS_CLIENT.CreateAccount(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListAccount(t *testing.T) { + isAvailable(RDS_ID) + result, err := RDS_CLIENT.ListAccount(RDS_ID) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Accounts { + if e.AccountName == ACCOUNT_NAME { + ExpectEqual(t.Errorf, "Available", e.Status) + } + } +} + +func TestClient_GetAccount(t *testing.T) { + result, err := RDS_CLIENT.GetAccount(RDS_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Available", result.Status) +} + +func TestClient_ModifyAccountDesc(t *testing.T) { + args := &ModifyAccountDesc{ + Remark: "test", + } + err := RDS_CLIENT.ModifyAccountDesc(RDS_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteAccount(t *testing.T) { + err := RDS_CLIENT.DeleteAccount(RDS_ID, ACCOUNT_NAME) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateAccountPassword(t *testing.T) { + args := &UpdatePasswordArgs{ + Password: "test", + } + err := RDS_CLIENT.UpdateAccountPassword(RDS_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_UpdateAccountPrivileges(t *testing.T) { + args := &UpdateAccountPrivileges{ + DatabasePrivileges: []DatabasePrivilege{{ + DbName: "test_db", + AuthType: "ReadOnly", + }}, + } + err := RDS_CLIENT.UpdateAccountPrivileges(RDS_ID, ACCOUNT_NAME, args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_CreateReadReplica(t *testing.T) { + args := &CreateReadReplicaArgs{ + SourceInstanceId: RDS_ID, + CpuCount: 1, + MemoryCapacity: 2, + VolumeCapacity: 10, + Billing: Billing{ + PaymentTiming: "Postpaid", + }, + ClientToken: getClientToken(), + } + time.Sleep(30 * time.Second) + isAvailable(RDS_ID) + _, err := RDS_CLIENT.CreateReadReplica(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateRdsProxy(t *testing.T) { + args := &CreateRdsProxyArgs{ + SourceInstanceId: RDS_ID, + NodeAmount: 2, + Billing: Billing{ + PaymentTiming: "Postpaid", + }, + ClientToken: getClientToken(), + } + isAvailable(RDS_ID) + _, err := RDS_CLIENT.CreateRdsProxy(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RebootInstance(t *testing.T) { + isAvailable(RDS_ID) + err := RDS_CLIENT.RebootInstance(RDS_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateInstanceName(t *testing.T) { + isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + args := &UpdateInstanceNameArgs{ + InstanceName: e.InstanceName + "_new", + } + err := RDS_CLIENT.UpdateInstanceName(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ModifySyncMode(t *testing.T) { + isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + args := &ModifySyncModeArgs{ + SyncMode: "Async", + } + err := RDS_CLIENT.ModifySyncMode(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ModifyEndpoint(t *testing.T) { + isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + args := &ModifyEndpointArgs{ + Address: "newsdkrds", + } + err := RDS_CLIENT.ModifyEndpoint(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ModifyPublicAccess(t *testing.T) { + isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + args := &ModifyPublicAccessArgs{ + PublicAccess: false, + } + err := RDS_CLIENT.ModifyPublicAccess(e.InstanceId, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ModifyBackupPolicy(t *testing.T) { + isAvailable(RDS_ID) + modifyBackupPolicyArgs := &ModifyBackupPolicyArgs{ + ExpireInDays: 10, + BackupDays: "0,1,2,3,4,5,6", + BackupTime: "17:00:00Z", + Persistent: true, + } + err := RDS_CLIENT.ModifyBackupPolicy(RDS_ID, modifyBackupPolicyArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBackupList(t *testing.T) { + isAvailable(RDS_ID) + getBackupListArgs := &GetBackupListArgs{ + Marker: RDS_ID, + MaxKeys: 100, + } + result, err := RDS_CLIENT.GetBackupList(RDS_ID, getBackupListArgs) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBackupDetail(t *testing.T) { + isAvailable(RDS_ID) + result, err := RDS_CLIENT.GetBackupDetail(RDS_ID, "1691679661534780403") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteBackup(t *testing.T) { + isAvailable(RDS_ID) + err := RDS_CLIENT.DeleteBackup(RDS_ID, "1691734023130272802") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBinlogList(t *testing.T) { + result, err := RDS_CLIENT.GetBinlogList(RDS_ID, "2022-07-12T23:59:59Z") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetBinlogInfo(t *testing.T) { + result, err := RDS_CLIENT.GetBinlogInfo(RDS_ID, "1691734023130272802", "1800") + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RecoveryToSourceInstanceByDatetime(t *testing.T) { + isAvailable(RDS_ID) + recoveryByDatetimeArgs := &RecoveryByDatetimeArgs{ + Datetime: "2022-01-11T16:05:52Z", + Data: []RecoveryData{ + { + DbName: "test_db", + NewDbname: "new_test_db", + RestoreMode: "database", + Tables: []TableData{ + { + TableName: "table_name", + NewTablename: "new_table_name", + }, + }, + }, + }, + } + err := RDS_CLIENT.RecoveryToSourceInstanceByDatetime(RDS_ID, recoveryByDatetimeArgs) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RecoveryToSourceInstanceBySnapshot(t *testing.T) { + isAvailable(RDS_ID) + recoveryBySnapshotArgs := &RecoveryBySnapshotArgs{ + SnapshotId: "1691734023130272802", + Data: []RecoveryData{ + { + DbName: "test_db", + NewDbname: "new_test_db", + RestoreMode: "database", + Tables: []TableData{ + { + TableName: "table_name", + NewTablename: "new_table_name", + }, + }, + }, + }, + } + err := RDS_CLIENT.RecoveryToSourceInstanceBySnapshot(RDS_ID, recoveryBySnapshotArgs) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_GetZoneList(t *testing.T) { + isAvailable(RDS_ID) + _, err := RDS_CLIENT.GetZoneList() + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListSubnets(t *testing.T) { + isAvailable(RDS_ID) + args := &ListSubnetsArgs{ + VpcId: "vpc-it3v6qt3jhvj", + ZoneName: "cn-bj-d", + } + _, err := RDS_CLIENT.ListSubnets(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSecurityIps(t *testing.T) { + //isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + res, err := RDS_CLIENT.GetSecurityIps(e.InstanceId) + fmt.Println(res.SecurityIps) + fmt.Println(res.Etag) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_SecurityIps(t *testing.T) { + //isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + res, err := RDS_CLIENT.GetSecurityIps(e.InstanceId) + ExpectEqual(t.Errorf, nil, err) + args := &UpdateSecurityIpsArgs{ + SecurityIps: []string{ + "%", + "192.0.0.1", + "192.0.0.2", + }, + } + er := RDS_CLIENT.UpdateSecurityIps(e.InstanceId, res.Etag, args) + ExpectEqual(t.Errorf, nil, er) + } + } +} + +func TestClient_ListParameters(t *testing.T) { + //isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{ + Marker: "0", + MaxKeys: 100, + } + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + res, err := RDS_CLIENT.ListParameters(e.InstanceId) + data, _ := json.Marshal(res) + fmt.Println(string(data)) + fmt.Println(res.Etag) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_UpdateParameter(t *testing.T) { + //isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + res, err := RDS_CLIENT.ListParameters(e.InstanceId) + ExpectEqual(t.Errorf, nil, err) + args := &UpdateParameterArgs{ + Parameters: []KVParameter{ + { + Name: "connect_timeout", + Value: "15", + }, + }, + } + er := RDS_CLIENT.UpdateParameter(e.InstanceId, res.Etag, args) + ExpectEqual(t.Errorf, nil, er) + } + } +} + +func TestClient_ParameterHistory(t *testing.T) { + result, err := RDS_CLIENT.ParameterHistory(RDS_ID) + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteRds(t *testing.T) { + time.Sleep(30 * time.Second) + isAvailable(RDS_ID) + listRdsArgs := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRds(listRdsArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Available" == e.InstanceStatus { + err := RDS_CLIENT.DeleteRds(e.InstanceId) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func getClientToken() string { + return util.NewUUID() +} + +func isAvailable(instanceId string) { + for { + result, err := RDS_CLIENT.GetDetail(instanceId) + if err == nil && result.InstanceStatus == "Available" { + break + } + } +} + +func TestClient_AutoRenew(t *testing.T) { + err := RDS_CLIENT.AutoRenew(&AutoRenewArgs{ + AutoRenewTimeUnit: "month", + AutoRenewTime: 1, + InstanceIds: []string{ + "rds-rbmh6gJl", + }, + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowLogDownloadTaskList(t *testing.T) { + res, err := RDS_CLIENT.GetSlowLogDownloadTaskList("rdsmv5aumcrpynd", "2022-11-14T16:00:00Z") + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowLogDownloadDetail(t *testing.T) { + res, err := RDS_CLIENT.GetSlowLogDownloadDetail("rds-qJG8sHPY", "slowlog.202211141158", "60") + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_newApi(t *testing.T) { + result, err := RDS_CLIENT.Request("GET", "/v1/instance/rds-TSIlv3Sd/performance/processlist", nil) + ExpectEqual(t.Errorf, nil, err) + if result != nil { + fmt.Println(result) + } +} + +func TestClient_UpdateMaintainTime(t *testing.T) { + err := RDS_CLIENT.UpdateMaintainTime(RDS_ID, &MaintainTimeArgs{ + MaintainStartTime: "14:00:00", + MaintainDuration: 2, + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ConfigDiskAutoResize(t *testing.T) { + err := RDS_CLIENT.ConfigDiskAutoResize(RDS_ID, "open", &DiskAutoResizeArgs{ + FreeSpaceThreshold: 10, + DiskMaxLimit: 2000, + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetAutoResizeConfig(t *testing.T) { + res, err := RDS_CLIENT.GetAutoResizeConfig(RDS_ID) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EnableAutoExpansion(t *testing.T) { + res, err := RDS_CLIENT.EnableAutoExpansion(RDS_ID) + fmt.Print(res) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AzoneMigration(t *testing.T) { + err := RDS_CLIENT.AzoneMigration(RDS_ID, &AzoneMigration{ + MasterAzone: "cn-bj-d", + BackupAzone: "cn-bj-e", + ZoneNames: []string{"cn-bj-d", "cn-bj-e"}, + Subnets: []SubnetMap{ + { + ZoneName: "cn-bj-d", + SubnetId: "sbn-nedt51qre6r2", + }, + { + ZoneName: "cn-bj-e", + SubnetId: "sbn-hc20wss3idai", + }, + }, + EffectiveTime: "timewindow", + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateDatabasePort(t *testing.T) { + err := RDS_CLIENT.UpdateDatabasePort(RDS_ID, &UpdateDatabasePortArgs{ + EntryPort: 3309, + }) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListDatabases(t *testing.T) { + result, err := RDS_CLIENT.ListDatabases(RDS_ID) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ModifyDatabaseDesc(t *testing.T) { + args := &ModifyDatabaseDesc{ + Remark: "test", + } + err := RDS_CLIENT.ModifyDatabaseDesc(RDS_ID, "test_db", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteDatabase(t *testing.T) { + err := RDS_CLIENT.DeleteDatabase(RDS_ID, "test_db") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateDatabase(t *testing.T) { + + args := &CreateDatabaseArgs{ + CharacterSetName: "utf8", + DbName: "test_db", + Remark: "test_db", + AccountPrivileges: []AccountPrivilege{ + { + AccountName: "baidu", + AuthType: "ReadOnly", + }, + }, + } + + isAvailable(RDS_ID) + err := RDS_CLIENT.CreateDatabase(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_TaskList(t *testing.T) { + args := &TaskListArgs{ + InstanceId: RDS_ID, + } + result, err := RDS_CLIENT.TaskList(args) + re, _ := json.Marshal(result) + fmt.Println(string(re)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListRecyclerInstance(t *testing.T) { + args := &ListRdsArgs{} + result, err := RDS_CLIENT.ListRecyclerInstance(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RecyclerRecover(t *testing.T) { + args := &RecyclerRecoverArgs{ + InstanceIds: []string{RDS_ID}, + } + err := RDS_CLIENT.RecyclerRecover(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteRecyclerInstance(t *testing.T) { + err := RDS_CLIENT.DeleteRecyclerInstance(RDS_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateInstanceGroup(t *testing.T) { + args := &InstanceGroupArgs{ + Name: "test_group", + LeaderId: RDS_ID, + } + result, err := RDS_CLIENT.CreateInstanceGroup(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListInstanceGroup(t *testing.T) { + args := &ListInstanceGroupArgs{ + Manner: "page", + } + result, err := RDS_CLIENT.ListInstanceGroup(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupDetail(t *testing.T) { + result, err := RDS_CLIENT.InstanceGroupDetail("rdcg6034psv") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupCheckGtid(t *testing.T) { + args := &CheckGtidArgs{ + InstanceId: RDS_ID, + } + result, err := RDS_CLIENT.InstanceGroupCheckGtid(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupCheckPing(t *testing.T) { + args := &CheckPingArgs{ + SourceId: RDS_ID, + TargetId: RDS_ID, + } + result, err := RDS_CLIENT.InstanceGroupCheckPing(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupCheckData(t *testing.T) { + args := &CheckDataArgs{ + InstanceId: RDS_ID, + } + result, err := RDS_CLIENT.InstanceGroupCheckData(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupCheckVersion(t *testing.T) { + args := &CheckVersionArgs{ + LeaderId: RDS_ID, + FollowerId: RDS_ID, + } + result, err := RDS_CLIENT.InstanceGroupCheckVersion(args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateInstanceGroupName(t *testing.T) { + args := &InstanceGroupNameArgs{ + Name: "test_group_name", + } + err := RDS_CLIENT.UpdateInstanceGroupName("rdcg6034psv", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupAdd(t *testing.T) { + args := &InstanceGroupAddArgs{ + FollowerId: RDS_ID, + } + err := RDS_CLIENT.InstanceGroupAdd("rdcg6034psv", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupBatchAdd(t *testing.T) { + args := &InstanceGroupBatchAddArgs{ + FollowerIds: []string{RDS_ID}, + Name: "test_group_name", + LeaderId: RDS_ID, + } + err := RDS_CLIENT.InstanceGroupBatchAdd(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupForceChange(t *testing.T) { + args := &ForceChangeArgs{ + LeaderId: RDS_ID, + Force: 0, + } + result, err := RDS_CLIENT.InstanceGroupForceChange("rdcg6034psv", args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupLeaderChange(t *testing.T) { + args := &GroupLeaderChangeArgs{ + LeaderId: RDS_ID, + } + err := RDS_CLIENT.InstanceGroupLeaderChange("rdcg6034psv", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceGroupRemove(t *testing.T) { + err := RDS_CLIENT.InstanceGroupRemove("rdcg6034psv", RDS_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteInstanceGroup(t *testing.T) { + err := RDS_CLIENT.DeleteInstanceGroup("rdcg6034psv") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceMinorVersionList(t *testing.T) { + result, err := RDS_CLIENT.InstanceMinorVersionList(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InstanceUpgradeMinorVersion(t *testing.T) { + args := &UpgradeMinorVersionArgs{ + TargetMinorVersion: "5.7.38", + EffectiveTime: "immediate", + } + err := RDS_CLIENT.InstanceUpgradeMinorVersion(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SlowSqlFlowStatus(t *testing.T) { + result, err := RDS_CLIENT.SlowSqlFlowStatus(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EnableSlowSqlFlow(t *testing.T) { + err := RDS_CLIENT.EnableSlowSqlFlow(RDS_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DisableSlowSqlFlow(t *testing.T) { + err := RDS_CLIENT.DisableSlowSqlFlow(RDS_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlList(t *testing.T) { + args := &GetSlowSqlArgs{} + result, err := RDS_CLIENT.GetSlowSqlList(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlBySqlId(t *testing.T) { + result, err := RDS_CLIENT.GetSlowSqlBySqlId(RDS_ID, "sqlidxxx") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlExplain(t *testing.T) { + result, err := RDS_CLIENT.GetSlowSqlExplain(RDS_ID, "sqlidxxx", "db1") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlStatsDigest(t *testing.T) { + args := &GetSlowSqlArgs{} + result, err := RDS_CLIENT.GetSlowSqlStatsDigest(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlDuration(t *testing.T) { + args := &GetSlowSqlDurationArgs{} + result, err := RDS_CLIENT.GetSlowSqlDuration(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlSource(t *testing.T) { + args := &GetSlowSqlSourceArgs{} + result, err := RDS_CLIENT.GetSlowSqlSource(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlSchema(t *testing.T) { + result, err := RDS_CLIENT.GetSlowSqlSchema(RDS_ID, "sqlidxxx", "db1") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlTable(t *testing.T) { + result, err := RDS_CLIENT.GetSlowSqlTable(RDS_ID, "sqlidxxx", "db1", "table1") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlIndex(t *testing.T) { + args := &GetSlowSqlIndexArgs{ + SqlId: "e9fa9802-0d0e-41b4-b3ba-6496466b6cad", + Schema: "db1", + Table: "table1", + } + result, err := RDS_CLIENT.GetSlowSqlIndex(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlTrend(t *testing.T) { + args := &GetSlowSqlTrendArgs{ + Start: "2023-05-05T05:30:13.000Z", + End: "2023-05-06T05:30:13.000Z", + } + result, err := RDS_CLIENT.GetSlowSqlTrend(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSlowSqlAdvice(t *testing.T) { + result, err := RDS_CLIENT.GetSlowSqlAdvice(RDS_ID, "sqlidxxx", "db1") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetDiskInfo(t *testing.T) { + result, err := RDS_CLIENT.GetDiskInfo(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetDbListSize(t *testing.T) { + result, err := RDS_CLIENT.GetDbListSize(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetTableListInfo(t *testing.T) { + args := &GetTableListArgs{ + DbName: "db1", + } + result, err := RDS_CLIENT.GetTableListInfo(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetKillSessionTypes(t *testing.T) { + result, err := RDS_CLIENT.GetKillSessionTypes(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSessionSummary(t *testing.T) { + result, err := RDS_CLIENT.GetSessionSummary(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSessionDetail(t *testing.T) { + args := &SessionDetailArgs{} + result, err := RDS_CLIENT.GetSessionDetail(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CheckKillSessionAuth(t *testing.T) { + args := &KillSessionAuthArgs{} + result, err := RDS_CLIENT.CheckKillSessionAuth(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetKillSessionHistory(t *testing.T) { + args := &KillSessionHistory{} + result, err := RDS_CLIENT.GetKillSessionHistory(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_KillSession(t *testing.T) { + args := &KillSessionArgs{} + result, err := RDS_CLIENT.KillSession(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSessionStatistics(t *testing.T) { + result, err := RDS_CLIENT.GetSessionStatistics(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetErrorLogStatus(t *testing.T) { + result, err := RDS_CLIENT.GetErrorLogStatus(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_EnableErrorLog(t *testing.T) { + result, err := RDS_CLIENT.EnableErrorLog(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DisableErrorLog(t *testing.T) { + result, err := RDS_CLIENT.DisableErrorLog(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetErrorLogList(t *testing.T) { + args := &ErrorLogListArgs{} + result, err := RDS_CLIENT.GetErrorLogList(RDS_ID, args) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSqlFilterList(t *testing.T) { + result, err := RDS_CLIENT.GetSqlFilterList(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetSqlFilterDetail(t *testing.T) { + result, err := RDS_CLIENT.GetSqlFilterDetail(RDS_ID, "83") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AddSqlFilter(t *testing.T) { + args := &SqlFilterArgs{ + FilterType: "SELECT", + FilterKey: "123", + FilterLimit: 0, + } + err := RDS_CLIENT.AddSqlFilter(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateSqlFilter(t *testing.T) { + args := &SqlFilterArgs{ + FilterType: "SELECT", + FilterKey: "1234", + FilterLimit: 0, + } + err := RDS_CLIENT.UpdateSqlFilter(RDS_ID, "83", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_StartOrStopSqlFilter(t *testing.T) { + args := &StartOrStopSqlFilterArgs{ + Action: "OFF", + } + err := RDS_CLIENT.StartOrStopSqlFilter(RDS_ID, "83", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteSqlFilter(t *testing.T) { + err := RDS_CLIENT.DeleteSqlFilter(RDS_ID, "83") + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_IsAllowedSqlFilter(t *testing.T) { + result, err := RDS_CLIENT.IsAllowedSqlFilter(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ProcessKill(t *testing.T) { + args := &ProcessArgs{ + Ids: []int64{123}, + } + err := RDS_CLIENT.ProcessKill(RDS_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_InnodbStatus(t *testing.T) { + result, err := RDS_CLIENT.InnodbStatus(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ProcessList(t *testing.T) { + result, err := RDS_CLIENT.ProcessList(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_TransactionList(t *testing.T) { + result, err := RDS_CLIENT.TransactionList(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ConnectionList(t *testing.T) { + result, err := RDS_CLIENT.ConnectionList(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_FailInjectWhiteList(t *testing.T) { + result, err := RDS_CLIENT.FailInjectWhiteList() + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_AddToFailInjectWhiteList(t *testing.T) { + args := &FailInjectArgs{ + AppList: []string{RDS_ID}, + } + err := RDS_CLIENT.AddToFailInjectWhiteList(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RemoveFailInjectWhiteList(t *testing.T) { + args := &FailInjectArgs{ + AppList: []string{RDS_ID}, + } + err := RDS_CLIENT.RemoveFailInjectWhiteList(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_FailInjectStart(t *testing.T) { + result, err := RDS_CLIENT.FailInjectStart(RDS_ID) + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetOrderStatus(t *testing.T) { + result, err := RDS_CLIENT.GetOrderStatus("xxx") + jsonData, _ := json.Marshal(result) + fmt.Println(string(jsonData)) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/rds/model.go b/bce-sdk-go/services/rds/model.go new file mode 100644 index 0000000..c5347ef --- /dev/null +++ b/bce-sdk-go/services/rds/model.go @@ -0,0 +1,1212 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package rds + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type CreateRdsArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + PurchaseCount int `json:"purchaseCount,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + Category string `json:"category,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + DiskIoType string `json:"diskIoType"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + BgwGroupId string `json:"bgwGroupId,omitempty"` + BgwGroupExclusive bool `json:"bgwGroupExclusive,omitempty"` + CharacterSetName string `json:"characterSetName,omitempty"` + LowerCaseTableNames int `json:"lowerCaseTableNames,omitempty"` + ParameterTemplateId string `json:"parameterTemplateId,omitempty"` + Ovip string `json:"ovip,omitempty"` + EntryPort string `json:"entryPort,omitempty"` + ReplicationType string `json:"replicationType,omitempty"` + ResourceGroupId string `json:"resourceGroupId,omitempty"` + InitialDataReference *InitialData `json:"initialDataReference,omitempty"` + Data []RecoveryToSourceInstanceModel `json:"data,omitempty"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation Reservation `json:"reservation,omitempty"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength,omitempty"` + ReservationTimeUnit string `json:"reservationTimeUnit,omitempty"` +} + +type SubnetMap struct { + ZoneName string `json:"zoneName"` + SubnetId string `json:"subnetId"` +} + +type InitialData struct { + InstanceId string `json:"instanceId,omitempty"` + ReferenceType string `json:"referenceType,omitempty"` + Datetime string `json:"datetime,omitempty"` + SnapshotId string `json:"snapshotId,omitempty"` +} + +type RecoveryToSourceInstanceModel struct { + RestoreMode string `json:"restoreMode,omitempty"` + DbName string `json:"dbName,omitempty"` + NewDbname string `json:"newDbname,omitempty"` + Tables []Table `json:"tables,omitempty"` +} + +type Table struct { + TableName string `json:"tableName,omitempty"` + NewTablename string `json:"newTablename,omitempty"` +} +type CreateResult struct { + InstanceIds []string `json:"instanceIds"` + OrderId string `json:"orderId"` +} + +type CreateReadReplicaArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + PurchaseCount int `json:"purchaseCount,omitempty"` + SourceInstanceId string `json:"sourceInstanceId"` + InstanceName string `json:"instanceName,omitempty"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + DiskIoType string `json:"diskIoType,omitempty"` + Ovip string `json:"ovip,omitempty"` + EntryPort string `json:"entryPort,omitempty"` + ResourceGroupId string `json:"resourceGroupId,omitempty"` +} + +type CreateRdsProxyArgs struct { + ClientToken string `json:"-"` + Billing Billing `json:"billing"` + SourceInstanceId string `json:"sourceInstanceId"` + InstanceName string `json:"instanceName,omitempty"` + NodeAmount int `json:"nodeAmount"` + ZoneNames []string `json:"zoneNames,omitempty"` + VpcId string `json:"vpcId,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` + Ovip string `json:"ovip,omitempty"` + EntryPort string `json:"entryPort,omitempty"` + ResourceGroupId string `json:"resourceGroupId,omitempty"` +} + +type ListRdsArgs struct { + Marker string + MaxKeys int +} + +type Instance struct { + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + RdsMinorVersion string `json:"rdsMinorVersion"` + CharacterSetName string `json:"characterSetName"` + InstanceClass string `json:"instanceClass"` + AllocatedMemoryInMB int `json:"allocatedMemoryInMB"` + AllocatedMemoryInGB float64 `json:"allocatedMemoryInGB"` + AllocatedStorageInGB int `json:"allocatedStorageInGB"` + Category string `json:"category"` + InstanceStatus string `json:"instanceStatus"` + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + TotalStorageInGB int `json:"totalStorageInGB"` + NodeAmount int `json:"nodeAmount"` + UsedStorage float64 `json:"usedStorage"` + PublicAccessStatus string `json:"publicAccessStatus"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Endpoint Endpoint `json:"endpoint"` + SyncMode string `json:"syncMode"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + Region string `json:"region"` + InstanceType string `json:"instanceType"` + SourceInstanceId string `json:"sourceInstanceId"` + SourceRegion string `json:"sourceRegion"` + ZoneNames []string `json:"zoneNames"` + VpcId string `json:"vpcId"` + Subnets []Subnet `json:"subnets"` + Topology Topology `json:"topology"` + Task string `json:"task"` + PaymentTiming string `json:"paymentTiming"` + BgwGroupId string `json:"bgwGroupId"` + ReadReplicaNum int `json:"readReplicaNum"` + ReadReplica []string `json:"readReplica"` + LockMode string `json:"lockMode"` + EipStatus string `json:"eipStatus"` + SuperUserFlag string `json:"superUserFlag"` + ReplicationType string `json:"replicationType"` + Azone string `json:"azone"` + ApplicationType string `json:"applicationType"` + OnlineStatus int `json:"onlineStatus"` + IsSingle bool `json:"isSingle"` + NodeType string `json:"nodeType"` + DiskIoType string `json:"diskIoType"` + GroupId string `json:"groupId"` + GroupName string `json:"groupName"` + DiskType string `json:"diskType"` + CdsType string `json:"cdsType"` + MaintainStartTime string `json:"maintainStartTime"` + MaintainDuration int `json:"maintainDuration"` + HaStrategy int `json:"haStrategy"` + VpcName string `json:"vpcName"` + Tag []model.TagModel `json:"tag"` +} + +type ListRdsResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Instances []Instance `json:"instances"` +} + +type Subnet struct { + Name string `json:"name"` + SubnetId string `json:"subnetId"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + VpcId string `json:"vpcId"` +} + +type Endpoint struct { + Address string `json:"address"` + Port int `json:"port"` + VnetIp string `json:"vnetIp"` + InetIp string `json:"inetIp"` +} + +type BackupPolicy struct { + BackupDays string `json:"backupDays"` + BackupTime string `json:"backupTime"` + Persistent bool `json:"persistent"` + ExpireInDays int `json:"expireInDays"` + FreeSpaceInGB int `json:"freeSpaceInGb"` +} + +type Topology struct { + Rdsproxy []string `json:"rdsproxy"` + Master []string `json:"master"` + ReadReplica []string `json:"readReplica"` +} + +type ResizeRdsArgs struct { + CpuCount int `json:"cpuCount"` + MemoryCapacity float64 `json:"memoryCapacity"` + VolumeCapacity int `json:"volumeCapacity"` + NodeAmount int `json:"nodeAmount,omitempty"` + IsDirectPay bool `json:"isDirectPay,omitempty"` + AllocatedMemoryInMB string `json:"allocatedMemoryInMB,omitempty"` + IsEnhanced bool `json:"isEnhanced,omitempty"` + EffectiveTime string `json:"effectiveTime,omitempty"` + MasterAzone string `json:"masterAzone,omitempty"` + BackupAzone string `json:"backupAzone,omitempty"` + DiskIoType string `json:"diskIoType,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + EdgeSubnetId string `json:"edgeSubnetId,omitempty"` + Subnets []SubnetMap `json:"subnets,omitempty"` +} + +type CreateAccountArgs struct { + ClientToken string `json:"-"` + AccountName string `json:"accountName"` + Password string `json:"password"` + AccountType string `json:"accountType,omitempty"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges,omitempty"` + Desc string `json:"desc,omitempty"` + Type string `json:"type,omitempty"` +} + +type DatabasePrivilege struct { + DbName string `json:"dbName"` + AuthType string `json:"authType"` +} + +type Account struct { + AccountName string `json:"accountName"` + Status string `json:"status"` + Type string `json:"type"` + AccountType string `json:"accountType"` + DatabasePrivileges []DatabasePrivilege `json:"databasePrivileges"` + Desc string `json:"desc"` +} + +type ListAccountResult struct { + Accounts []Account `json:"accounts"` +} + +type ModifyAccountDesc struct { + Remark string `json:"remark"` +} + +type UpdateAccountPrivileges struct { + DatabasePrivileges []DatabasePrivilege `json:"privileges"` +} + +type UpdatePasswordArgs struct { + Password string `json:"password"` +} +type UpdateInstanceNameArgs struct { + InstanceName string `json:"instanceName"` +} + +type ModifySyncModeArgs struct { + SyncMode string `json:"syncMode"` +} + +type ModifyEndpointArgs struct { + Address string `json:"address"` +} + +type ModifyPublicAccessArgs struct { + PublicAccess bool `json:"publicAccess"` +} + +type ModifyBackupPolicyArgs struct { + BackupDays string `json:"backupDays"` + BackupTime string `json:"backupTime"` + Persistent bool `json:"persistent"` + ExpireInDays int `json:"expireInDays"` +} + +type GetBackupListArgs struct { + Marker string + MaxKeys int +} + +type Snapshot struct { + SnapshotId string `json:"backupId"` + SnapshotSizeInBytes int64 `json:"backupSize"` + SnapshotType string `json:"backupType"` + SnapshotStatus string `json:"backupStatus"` + SnapshotStartTime string `json:"backupStartTime"` + SnapshotEndTime string `json:"backupEndTime"` + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} + +type BackupDetail struct { + BackupSize int `json:"backupSize"` + BackupStatus string `json:"backupStatus"` + BackupId string `json:"backupId"` + BackupEndTime string `json:"backupEndTime"` + DownloadUrl string `json:"downloadUrl"` + BackupType string `json:"backupType"` + BackupStartTime string `json:"backupStartTime"` + DownloadExpires string `json:"downloadExpires"` +} + +type GetBackupListResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + Backups []BackupDetail `json:"backups"` +} + +type GetBinlogListResult struct { + Binlogs []Binlog `json:"binlogs"` +} + +type GetBinlogInfoResult struct { + Binlog BinlogInfo `json:"binlog"` +} + +type BinlogInfo struct { + DownloadUrl string `json:"downloadUrl"` + DownloadExpires string `json:"downloadExpires"` +} +type Binlog struct { + BinlogId string `json:"binlogId"` + BinlogSizeInBytes int64 `json:"binlogSize"` + BinlogStatus string `json:"binlogStatus"` + BinlogStartTime string `json:"binlogStartTime"` + BinlogEndTime string `json:"binlogEndTime"` +} + +type RecoveryByDatetimeArgs struct { + Datetime string `json:"dateTime"` + Data []RecoveryData `json:"data"` +} + +type RecoveryBySnapshotArgs struct { + SnapshotId string `json:"snapshotId"` + Data []RecoveryData `json:"data"` +} +type RecoveryData struct { + DbName string `json:"dbName"` + NewDbname string `json:"newDbname"` + RestoreMode string `json:"restoreMode"` + Tables []TableData `json:"tables"` +} + +type TableData struct { + TableName string `json:"tableName"` + NewTablename string `json:"newTablename"` +} +type GetZoneListResult struct { + Zones []ZoneName `json:"zones"` +} + +type ZoneName struct { + ZoneNames []string `json:"zoneNames"` +} + +type ListSubnetsArgs struct { + VpcId string `json:"vpcId"` + ZoneName string `json:"zoneName"` +} + +type ListSubnetsResult struct { + Subnets []Subnet `json:"subnets"` +} + +type GetSecurityIpsResult struct { + Etag string `json:"etag"` + SecurityIps []string `json:"securityIps"` +} + +type UpdateSecurityIpsArgs struct { + SecurityIps []string `json:"securityIps"` +} + +type ListParametersResult struct { + Etag string `json:"etag"` + Parameters []Parameter `json:"parameters"` +} + +type Parameter struct { + Name string `json:"name"` + DefaultValue string `json:"defaultValue"` + Value string `json:"value"` + PendingValue string `json:"pendingValue"` + Type string `json:"type"` + Dynamic string `json:"dynamic"` + Modifiable string `json:"modifiable"` + AllowedValues string `json:"allowedValues"` + Desc string `json:"desc"` +} + +type UpdateParameterArgs struct { + Parameters []KVParameter `json:"parameters"` +} + +type KVParameter struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type ParameterHistoryResult struct { + Parameters []ParameterHistory `json:"parameters"` +} + +type ParameterHistory struct { + Name string `json:"name"` + BeforeValue string `json:"beforeValue"` + AfterValue string `json:"afterValue"` + Status string `json:"status"` + UpdateTime string `json:"updateTime"` +} +type AutoRenewArgs struct { + InstanceIds []string `json:"instanceIds"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit"` + AutoRenewTime int `json:"autoRenewTime"` +} + +type SlowLogDownloadTaskListResult struct { + Slowlogs []Slowlog `json:"slowlogs"` +} + +type SlowLogDownloadDetail struct { + Slowlogs []SlowlogDetail `json:"slowlogs"` +} +type Slowlog struct { + SlowlogId string `json:"slowlogId"` + SlowlogSizeInBytes int `json:"slowlogSizeInBytes"` + SlowlogStartTime string `json:"slowlogStartTime"` + SlowlogEndTime string `json:"slowlogEndTime"` +} + +type SlowlogDetail struct { + Url string `json:"url"` + DownloadExpires string `json:"downloadExpires"` +} + +type MaintainTimeArgs struct { + MaintainStartTime string `json:"maintainStartTime"` + MaintainDuration int `json:"maintainDuration"` +} + +type DiskAutoResizeArgs struct { + FreeSpaceThreshold int `json:"freeSpaceThreshold,omitempty"` + DiskMaxLimit int `json:"diskMaxLimit,omitempty"` +} + +type AutoResizeConfigResult struct { + AutoResizeDisk int `json:"autoResizeDisk"` + FreeSpaceThreshold int `json:"freeSpaceThreshold"` + DiskMaxLimit int `json:"diskMaxLimit"` + ExtendStepPercent int `json:"extendStepPercent"` +} + +type EnableAutoExpansionResult struct { + SupportEnableDiskAutoResize int `json:"supportEnableDiskAutoResize"` +} + +type AzoneMigration struct { + MasterAzone string `json:"master_azone"` + BackupAzone string `json:"backup_azone"` + ZoneNames []string `json:"zoneNames"` + Subnets []SubnetMap `json:"subnets"` + EffectiveTime string `json:"effectiveTime"` +} + +type UpdateDatabasePortArgs struct { + EntryPort int `json:"entryPort"` +} + +type ListDatabasesResult struct { + Databases []Database `json:"databases"` +} + +type Database struct { + DbName string `json:"dbName"` + CharacterSetName string `json:"characterSetName"` + Remark string `json:"remark"` + DbStatus string `json:"dbStatus"` + AccountPrivileges []AccountPrivilege `json:"accountPrivileges"` +} + +type AccountPrivilege struct { + AccountName string `json:"accountName"` + AuthType string `json:"authType"` +} + +type ModifyDatabaseDesc struct { + Remark string `json:"remark"` +} + +type CreateDatabaseArgs struct { + CharacterSetName string `json:"characterSetName"` + DbName string `json:"dbName"` + Remark string `json:"remark"` + AccountPrivileges []AccountPrivilege `json:"accountPrivileges"` +} + +type TaskListArgs struct { + PageSize string `json:"pageSize,omitempty"` + PageNo string `json:"pageNo,omitempty"` + InstanceId string `json:"instanceId,omitempty"` + InstanceName string `json:"instanceName,omitempty"` + TaskId int `json:"taskId,omitempty"` + TaskType string `json:"taskType,omitempty"` + TaskStatus string `json:"taskStatus,omitempty"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` +} + +type TaskListResult struct { + Tasks []Task `json:"tasks"` + Count int `json:"count"` +} + +type Task struct { + TaskId int `json:"taskId"` + TaskType string `json:"taskType"` + TaskName string `json:"taskName"` + InstanceId string `json:"instanceId"` + InstanceName string `json:"instanceName"` + UserId string `json:"userId"` + Region string `json:"region"` + TaskStatus string `json:"taskStatus"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` + FinishTime string `json:"finishTime"` + CancelFlag int `json:"cancelFlag"` + Progress []ProgressItem `json:"progress"` +} + +type ProgressItem struct { + Step string `json:"step"` + Status string `json:"status"` + Description string `json:"description"` +} + +type RecyclerListResult struct { + NextMarker string `json:"nextMarker"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + IsTruncated bool `json:"isTruncated"` + Instances []Instance `json:"instances"` + InstanceType string `json:"instanceType"` + ZoneNames []string `json:"zoneNames"` + PaymentTiming string `json:"paymentTiming"` +} + +type RecyclerRecoverArgs struct { + InstanceIds []string `json:"instanceIds"` +} + +type InstanceGroupArgs struct { + Name string `json:"name"` + LeaderId string `json:"leaderId"` +} + +type CreateInstanceGroupResult struct { + Result int `json:"result"` +} + +type ListInstanceGroupArgs struct { + Manner string `json:"manner"` + Order string `json:"order,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` +} + +type InstanceGroupListResult struct { + Result []InstanceGroup `json:"result"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + TotalCount int `json:"totalCount"` +} + +type InstanceGroup struct { + GroupId string `json:"groupId"` + Name string `json:"name"` + Count int `json:"count"` + Leader GroupInstance `json:"leader"` +} + +type GroupInstance struct { + InstanceIdShort string `json:"instanceIdShort"` + Region string `json:"region"` + Azone string `json:"azone"` + Status string `json:"status"` + LockMode string `json:"lockMode"` + Name string `json:"name"` +} + +type InstanceGroupDetailResult struct { + Group InstanceGroupDetail `json:"group"` +} + +type InstanceGroupDetail struct { + InstanceGroup + Fllowers []GroupInstance `json:"fllowers"` +} + +type CheckGtidArgs struct { + InstanceId string `json:"instanceId"` +} + +type CheckGtidResult struct { + Result bool `json:"result"` +} + +type CheckPingArgs struct { + SourceId string `json:"sourceId"` + TargetId string `json:"targetId"` +} + +type CheckPingResult struct { + Result bool `json:"result"` +} + +type CheckDataArgs struct { + InstanceId string `json:"instanceId"` +} + +type CheckDataResult struct { + Result bool `json:"result"` +} + +type CheckVersionArgs struct { + LeaderId string `json:"leaderId"` + FollowerId string `json:"followerId"` +} + +type CheckVersionResult struct { + Result bool `json:"result"` +} + +type InstanceGroupNameArgs struct { + Name string `json:"name"` +} + +type InstanceGroupAddArgs struct { + FollowerId string `json:"followerId"` +} + +type InstanceGroupBatchAddArgs struct { + FollowerIds []string `json:"followerIds"` + Name string `json:"name"` + LeaderId string `json:"leaderId"` +} + +type ForceChangeArgs struct { + LeaderId string `json:"leaderId"` + Force int `json:"force,omitempty"` + MaxBehind int `json:"maxBehind,omitempty"` +} + +type ForceChangeResult struct { + BehindMaster int `json:"behind_master"` +} + +type GroupLeaderChangeArgs struct { + LeaderId string `json:"leaderId"` +} + +type MinorVersionListResult struct { + RdsMinorVersionList []RdsMinorVersion `json:"rdsMinorVersionList"` +} + +type RdsMinorVersion struct { + DbVersion string `json:"dbVersion"` + MinorVersion string `json:"minorVersion"` + RdsMinorVersion string `json:"rdsMinorVersion"` + FeatureDescription string `json:"featureDescription"` +} + +type UpgradeMinorVersionArgs struct { + TargetMinorVersion string `json:"targetMinorVersion"` + EffectiveTime string `json:"effectiveTime"` +} + +type SlowSqlFlowStatusResult struct { + Enabled int `json:"enabled"` +} + +type GetSlowSqlArgs struct { + Page int `json:"page,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Sort string `json:"sort,omitempty"` + Schema string `json:"schema,omitempty"` + Digest string `json:"digest,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type SlowSqlListResult struct { + Items []SlowSqlItem `json:"items"` + TotalCount int `json:"totalCount"` +} + +type SlowSqlItem struct { + AffectedRows int64 `json:"affectedRows"` + ClientHost string `json:"clientHost"` + ClientIP string `json:"clientIP"` + Cluster string `json:"cluster"` + ConnectionId int64 `json:"connectionId"` + CurrentDB string `json:"currentDB"` + Digest string `json:"digest"` + Duration float32 `json:"duration"` + ExaminedRows int64 `json:"examinedRows"` + LockTime int64 `json:"lockTime"` + Node string `json:"node"` + NumRows int `json:"numRows"` + Sql string `json:"sql"` + SqlId string `json:"sqlId"` + Start string `json:"start"` + User string `json:"user"` +} + +type SlowSqlExplainResult struct { + List []SlowSqlExplainItem `json:"list"` +} + +type SlowSqlExplainItem struct { + ExplainId int64 `json:"explainId"` + Extra string `json:"extra"` + Filtered int64 `json:"filtered"` + Key string `json:"key"` + KeyLen string `json:"keyLen"` + Partitions string `json:"partitions"` + PossibleKeys string `json:"possibleKeys"` + Ref string `json:"ref"` + Rows int `json:"rows"` + SelectType string `json:"selectType"` + Table string `json:"table"` + Type string `json:"type"` +} + +type SlowSqlDigestResult struct { + Items []SlowSqlDigestItem `json:"items"` + Summary SlowSqlDigestItem `json:"summary"` + TotalCount int `json:"totalCount"` +} + +type SlowSqlDigestItem struct { + AvgExamRows int64 `json:"avgExamRows"` + AvgLockTime int64 `json:"avgLockTime"` + AvgNumRows int64 `json:"avgNumRows"` + AvgTime float64 `json:"avgTime"` + Digest string `json:"digest"` + ExecuteTimes int64 `json:"executeTimes"` + MaxExamRows int64 `json:"maxExamRows"` + MaxLockTime float64 `json:"maxLockTime"` + MaxNumRows int64 `json:"maxNumRows"` + MaxTime float64 `json:"maxTime"` + NormalSql string `json:"normalSql"` + Schema string `json:"schema"` + TotalExamRows int64 `json:"totalExamRows"` + TotalLockTime float64 `json:"totalLockTime"` + TotalNumRows int64 `json:"totalNumRows"` + TotalTime float64 `json:"totalTime"` +} + +type GetSlowSqlDurationArgs struct { + Schema string `json:"schema,omitempty"` + Digest string `json:"digest,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type SlowSqlDurationResult struct { + List []SlowSqlDurationItem `json:"list"` +} + +type SlowSqlDurationItem struct { + End int64 `json:"end"` + Nums int64 `json:"nums"` + Percentage float64 `json:"percentage"` + Start int64 `json:"start"` + Title string `json:"title"` +} + +type GetSlowSqlSourceArgs struct { + Schema string `json:"schema,omitempty"` + Digest string `json:"digest,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type SlowSqlSourceResult struct { + List []SlowSqlSourceItem `json:"list"` +} + +type SlowSqlSourceItem struct { + Nums int64 `json:"nums"` + Percentage float64 `json:"percentage"` + Host string `json:"host"` + Ip string `json:"ip"` +} + +type SlowSqlSchemaResult struct { + List []SlowSqlSchemaItem `json:"list"` +} + +type SlowSqlSchemaItem struct { + Schema string `json:"schema"` + Table string `json:"table"` +} + +type SlowSqlTableResult struct { + List []SlowSqlTableItem `json:"list"` +} + +type SlowSqlTableItem struct { + Schema string `json:"schema"` + Table string `json:"table"` + Charset string `json:"charset"` + Collation string `json:"collation"` + Column string `json:"column"` + Comment string `json:"comment"` + DefaultValue string `json:"defaultValue"` + Extra string `json:"extra"` + Key string `json:"key"` + Nullable string `json:"nullable"` + Position int64 `json:"position"` + Type string `json:"type"` +} + +type GetSlowSqlIndexArgs struct { + SqlId string `json:"sqlId"` + Schema string `json:"schema"` + Table string `json:"table"` + Index string `json:"index,omitempty"` +} + +type SlowSqlIndexResult struct { + List []SlowSqlIndexItem `json:"list"` +} + +type SlowSqlIndexItem struct { + Schema string `json:"schema"` + Table string `json:"table"` + Collation string `json:"collation"` + Column string `json:"column"` + Comment string `json:"comment"` + Nullable string `json:"nullable"` + Type string `json:"type"` + Cardinality int64 `json:"cardinality"` + Index string `json:"index"` + NonUnique string `json:"nonUnique"` + Sequence string `json:"sequence"` +} + +type GetSlowSqlTrendArgs struct { + Schema string `json:"schema,omitempty"` + Interval string `json:"interval,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type SlowSqlTrendResult struct { + Interval int `json:"interval"` + Items []SlowSqlTrendItem `json:"items"` +} + +type SlowSqlTrendItem struct { + Datetime string `json:"datetime"` + Times int64 `json:"times"` +} + +type SlowSqlAdviceResult struct { + IndexAdvice []SlowSqlIndexAdviceItem `json:"indexAdvice"` + StatementAdvice []SlowSqlIndexAdviceItem `json:"statementAdvice"` +} + +type SlowSqlIndexAdviceItem struct { + Advice string `json:"advice"` + Level string `json:"level"` +} + +type DiskInfoResult struct { + Result DiskInfo `json:"result"` +} + +type DiskInfo struct { + Grow float64 `json:"grow"` + UseDay float64 `json:"useDay"` + DiskFree float64 `json:"diskFree"` + DiskUse float64 `json:"diskUse"` + DiskQuota float64 `json:"diskQuota"` +} + +type DbListResult struct { + Result DbInfo `json:"result"` +} + +type DbInfo struct { + DbInfoItems []DbInfoItem `json:"dbList"` +} + +type DbInfoItem struct { + Size string `json:"size"` + TableSchema string `json:"tableSchema"` +} + +type GetTableListArgs struct { + DbName string `json:"dbName"` + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` + OrderBy string `json:"orderBy,omitempty"` + Sort string `json:"sort,omitempty"` + SearchKey string `json:"searchKey,omitempty"` +} + +type TableListResult struct { + Result TableInfo `json:"result"` +} +type TableInfo struct { + Data []TableInfoItem `json:"data"` + TotalCount int64 `json:"totalCount"` +} + +type TableInfoItem struct { + IndexLength string `json:"indexLength"` + TableSchema string `json:"tableSchema"` + TableName string `json:"tableName"` + DataLength string `json:"dataLength"` + Engine string `json:"engine"` + TableRows string `json:"tableRows"` + DataFree string `json:"dataFree"` + DataFreePer string `json:"dataFreePer"` + AvgRowLength string `json:"avgRowLength"` + TableLength string `json:"tableLength"` + TableLengthPer string `json:"tableLengthPer"` +} + +type KillSessionTypesResult struct { + Types Commands `json:"types"` +} +type Commands struct { + Command []string `json:"command"` +} + +type SessionSummaryResult struct { + ActiveTotalCount int64 `json:"activeTotalCount"` + CpuUtilizationRate float64 `json:"cpuUtilizationRate"` + MaxExecuteTime float64 `json:"maxExecuteTime"` + TotalCount int64 `json:"totalCount"` +} + +type SessionDetailArgs struct { + Page int `json:"page,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Sort string `json:"sort,omitempty"` + ExecuteTime int64 `json:"executeTime,omitempty"` + Operator string `json:"operator,omitempty"` + IsActive bool `json:"isActive,omitempty"` + SessionId string `json:"sessionId,omitempty"` + User string `json:"user,omitempty"` + Host string `json:"host,omitempty"` + Db string `json:"db,omitempty"` + Command string `json:"command,omitempty"` + State string `json:"state,omitempty"` + SqlStmt string `json:"sqlStmt,omitempty"` +} + +type SessionDetailResult struct { + Items []SessionDetailItem `json:"items"` + TotalCount int64 `json:"totalCount"` +} + +type SessionDetailItem struct { + Command string `json:"command"` + Db string `json:"db"` + Host string `json:"host"` + Id int64 `json:"id"` + Sqlstmt string `json:"sqlStmt"` + State string `json:"state"` + Time string `json:"time"` + User string `json:"user"` +} + +type KillSessionAuthArgs struct { + DbHost string `json:"dbHost,omitempty"` + DbPort int `json:"dbPort,omitempty"` + Items []string `json:"items,omitempty"` + DbUser string `json:"dbUser,omitempty"` + DbPassword string `json:"dbPassword,omitempty"` +} + +type KillSessionAuthResult struct { + Success bool `json:"success"` +} + +type KillSessionHistory struct { + Page int `json:"page,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +type KillSessionHistoryResult struct { + Items []KillSessionHistoryItem `json:"items"` + TotalCount int64 `json:"totalCount"` +} + +type KillSessionHistoryItem struct { + OperateDbUser string `json:"operateDbUser"` + OperateTime string `json:"operateTime"` + OperateUser string `json:"operateUser"` + SessionCommand string `json:"sessionCommand"` + SessionDb string `json:"sessionDb"` + SessionExecuteTime string `json:"sessionExecuteTime"` + SessionHost string `json:"sessionHost"` + SessionId int64 `json:"sessionId"` + SessionSql string `json:"sessionSql"` + SessionState string `json:"sessionState"` + SessionUser string `json:"sessionUser"` + Status int `json:"status"` + StatusDesc string `json:"statusDesc"` + StatusInfo string `json:"statusInfo"` +} + +type KillSessionArgs struct { + DbHost string `json:"dbHost,omitempty"` + DbPort int `json:"dbPort,omitempty"` + Items []string `json:"items,omitempty"` +} + +type KillSessionResult struct { + Success bool `json:"success"` +} + +type SessionStatisticsResult map[string][]SessionStatisticsItem + +type SessionStatisticsItem struct { + ActiveAverageExecuteTime int64 `json:"activeAverageExecuteTime"` + ActiveTotalCount int64 `json:"activeTotalCount"` + ActiveTotalExecuteTime int64 `json:"activeTotalExecuteTime"` + DimensionKey string `json:"dimensionKey"` + DimensionValue string `json:"dimensionValue"` + TotalCount int64 `json:"totalCount"` +} + +type ErrorLogStatusResult struct { + Enabled int `json:"enabled"` +} + +type ErrorLogResult struct { + Success bool `json:"success"` +} + +type ErrorLogListArgs struct { + Page int `json:"page,omitempty"` + PageSize int `json:"pageSize,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + Label string `json:"label,omitempty"` +} +type ErrorLogListResult struct { + Items []ErrorLogItem `json:"items"` + TotalCount int64 `json:"totalCount"` +} + +type ErrorLogItem struct { + ConnectionId string `json:"connectionId"` + ErrCode string `json:"errCode"` + ErrInfo string `json:"errInfo"` + Label string `json:"label"` + LogId string `json:"logId"` + Subsystem string `json:"subsystem"` + Time string `json:"time"` +} + +type SqlFilterListResult struct { + SqlFilterList []SqlFilterItem `json:"sqlFilterList"` +} +type SqlFilterItem struct { + Id int64 `json:"id"` + FilterType string `json:"filterType"` + FilterKey string `json:"filterKey"` + FilterLimit int64 `json:"filterLimit"` + FilterStatus string `json:"filterStatus"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` +} + +type SqlFilterArgs struct { + FilterType string `json:"filterType"` + FilterKey string `json:"filterKey"` + FilterLimit int64 `json:"filterLimit"` +} + +type StartOrStopSqlFilterArgs struct { + Action string `json:"action"` +} + +type IsAllowedResult struct { + Allowed bool `json:"allowed"` +} + +type ProcessArgs struct { + Ids []int64 `json:"ids,omitempty"` +} + +type InnodbStatusResult struct { + Datatime string `json:"datatime"` + Name string `json:"name"` + Type string `json:"type"` + Status string `json:"status"` +} + +type ProcessListResult struct { + Datetime string `json:"datetime"` + ProcessList []ProcessItem `json:"processList"` +} + +type LockHold map[string][]int64 + +type LockWait struct { + LocakId string `json:"lockId"` + Id int64 `json:"id"` +} +type ProcessItem struct { + Sql string `json:"sql"` + Db string `json:"db"` + State string `json:"state"` + Host string `json:"host"` + Command string `json:"command"` + User string `json:"user"` + Time int64 `json:"time"` + ID int64 `json:"id"` + LockHold LockHold `json:"lockHold"` + LockWait []LockWait +} + +type TransactionListResult struct { + Datetime string `json:"datetime"` + InnodbTrxList []InnodbTrxItem `json:"innodbTrxList"` +} +type InnodbTrxItem struct { + TrxRequestedLockId string `json:"trxRequestedLockId"` + TrxStarted string `json:"trxStarted"` + TrxMysqlThreadId int64 `json:"trxMysqlThreadId"` + TrxRowsLocked int64 `json:"trxRowsLocked"` + TrxWaitStarted string `json:"trxWaitStarted"` + TrxState string `json:"trxState"` + TrxTablesInUse int64 `json:"trxTablesInUse"` + TrxId string `json:"trxId"` + TrxQuery string `json:"trxQuery"` + TrxTablesLocked int `json:"trxTablesLocked"` +} + +type ConnectionListResult struct { + Datetime string `json:"datetime"` + ConnectList []ConnectionItem `json:"connectList"` +} +type ConnectionItem struct { + LocalAddress string `json:"localAddress"` + Proto string `json:"proto"` + SendQ int `json:"sendQ"` + ForeignAddress string `json:"foreignAddress"` + RecvQ int `json:"recvQ"` +} + +type FailInjectWhiteListResult struct { + AppList []string `json:"appList"` +} + +type FailInjectArgs struct { + AppList []string `json:"appList"` +} + +type TaskResult struct { + TaskId int64 `json:"taskId"` +} + +type OrderStatusResult struct { + Status string `json:"status"` +} diff --git a/bce-sdk-go/services/rds/rds.go b/bce-sdk-go/services/rds/rds.go new file mode 100644 index 0000000..b8318d9 --- /dev/null +++ b/bce-sdk-go/services/rds/rds.go @@ -0,0 +1,2308 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// rds.go - the rds APIs definition supported by the RDS service +package rds + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + DEFAULT_PAGE_SIZE = 10 + DEFAULT_PAGE_NUM = 1 +) + +// CreateRds - create a RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to create a rds +// +// RETURNS: +// - *InstanceIds: the result of create RDS, contains new RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateRds(args *CreateRdsArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.Engine == "" { + return nil, fmt.Errorf("unset Engine") + } + + if args.EngineVersion == "" { + return nil, fmt.Errorf("unset EngineVersion") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateReadReplica - create a readReplica RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica rds +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica RDS, contains the readReplica RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateReadReplica(args *CreateReadReplicaArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.SourceInstanceId == "" { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("readReplica", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// CreateRdsProxy - create a proxy RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to create a readReplica rds +// +// RETURNS: +// - *InstanceIds: the result of create a readReplica RDS, contains the readReplica RDS's instanceIds +// - error: nil if success otherwise the specific error +func (c *Client) CreateRdsProxy(args *CreateRdsProxyArgs) (*CreateResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + + if args.SourceInstanceId == "" { + return nil, fmt.Errorf("unset SourceInstanceId") + } + + if args.Billing.PaymentTiming == "" { + return nil, fmt.Errorf("unset PaymentTiming") + } + + result := &CreateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("rdsproxy", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListRds - list all RDS with the specific parameters +// +// PARAMS: +// - args: the arguments to list all RDS +// +// RETURNS: +// - *ListRdsResult: the result of list all RDS, contains all rds' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListRds(args *ListRdsArgs) (*ListRdsResult, error) { + if args == nil { + args = &ListRdsArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListRdsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUri()). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetDetail - get a specific rds Instance's detail +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *Instance: the specific rdsInstance's detail +// - error: nil if success otherwise the specific error +func (c *Client) GetDetail(instanceId string) (*Instance, error) { + result := &Instance{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithResult(result). + Do() + + return result, err +} + +// DeleteRds - delete a rds +// +// PARAMS: +// - instanceIds: the specific instanceIds +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRds(instanceIds string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUri()). + WithQueryParamFilter("instanceIds", instanceIds). + Do() +} + +// ResizeRds - resize an RDS with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to resize an RDS +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeRds(instanceId string, args *ResizeRdsArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("resize", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// CreateAccount - create a account with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a account +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateAccount(instanceId string, args *CreateAccountArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.AccountName == "" { + return fmt.Errorf("unset AccountName") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.Password) + if err != nil { + return err + } + args.Password = cryptedPass + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId)+"/account"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ListAccount - list all account of a RDS instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListAccountResult: the result of list all account, contains all accounts' meta +// - error: nil if success otherwise the specific error +func (c *Client) ListAccount(instanceId string) (*ListAccountResult, error) { + result := &ListAccountResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/account"). + WithResult(result). + Do() + + return result, err +} + +// GetAccount - get an account of a RDS instance with the specific parameters +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - accountName: the specific account's name +// +// RETURNS: +// - *Account: the account's meta +// - error: nil if success otherwise the specific error +func (c *Client) GetAccount(instanceId, accountName string) (*Account, error) { + result := &Account{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/account/" + accountName). + WithResult(result). + Do() + + return result, err +} + +// AccountDesc - modify account's description +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - accountName: the specific account's name +// - args: the arguments used to modify account's description +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyAccountDesc(instanceId, accountName string, args *ModifyAccountDesc) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/account/"+accountName+"/desc"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountPrivileges - upate account's privileges +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - accountName: the specific account's name +// - args: the arguments used to modify account's privileges +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPrivileges(instanceId, accountName string, args *UpdateAccountPrivileges) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/account/"+accountName+"/privileges"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateAccountPassword - update account's password +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific account's name +// - args: the arguments to update account's password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAccountPassword(instanceId, accountName string, args *UpdatePasswordArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + + if args.Password == "" { + return fmt.Errorf("unset Password") + } + + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.Password) + if err != nil { + return err + } + args.Password = cryptedPass + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/account/"+accountName+"/password"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteAccount - delete an account of a RDS instance +// +// PARAMS: +// - instanceId: the specific instanceId +// - accountName: the specific account's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAccount(instanceId, accountName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId) + "/account/" + accountName). + Do() +} + +// RebootInstance - reboot a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be rebooted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RebootInstance(instanceId string) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("reboot", ""). + Do() +} + +// UpdateInstanceName - update name of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceName(instanceId string, args *UpdateInstanceNameArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("rename", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateSyncMode - update sync mode of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update syncMode +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifySyncMode(instanceId string, args *ModifySyncModeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("modifySyncMode", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ModifyEndpoint - modify the prefix of endpoint +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify endpoint +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyEndpoint(instanceId string, args *ModifyEndpointArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("modifyEndpoint", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ModifyPublicAccess - modify public access +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify public access +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyPublicAccess(instanceId string, args *ModifyPublicAccessArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("modifyPublicAccess", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ModifyBackupPolicy - modify backup policy +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify public access +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyBackupPolicy(instanceId string, args *ModifyBackupPolicyArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)). + WithQueryParam("modifyBackupPolicy", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetBackupList - get backup list of the instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetBackupListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupList(instanceId string, args *GetBackupListArgs) (*GetBackupListResult, error) { + + if args == nil { + args = &GetBackupListArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &GetBackupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId)+"/backup"). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetBackupDetail - get backup detail of the instance's backup +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - backupId: id of the backup +// +// RETURNS: +// - *Snapshot: result of the backup detail +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupDetail(instanceId string, backupId string) (*BackupDetail, error) { + result := &BackupDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/backup/" + backupId). + WithResult(result). + Do() + + return result, err +} + +// DeleteBackup - delete backup detail +// +// PARAMS: +// - instanceId: the specific instanceId +// - backupId: the specific backupId +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteBackup(instanceId, backupId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId) + "/backup/" + backupId). + Do() +} + +// GetBinlogList - get binlog list of the instance +// +// PARAMS: +// - instanceId: the specific instanceId +// - datetime: datetime of the binlog +// +// RETURNS: +// - *BinlogResult: result of the binlog list +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogList(instanceId, dateTime string) (*GetBinlogListResult, error) { + result := &GetBinlogListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/binlogs/" + dateTime). + WithResult(result). + Do() + + return result, err +} + +// GetBinlogInfo - get binlog info of the instance +// +// PARAMS: +// - instanceId: the specific instanceId +// - logId: the specific logId +// - downloadValidTimeInSec: download valid time in seconds +// +// RETURNS: +// - *BinlogInfo: result of the binlog info +// - error: nil if success otherwise the specific error +func (c *Client) GetBinlogInfo(instanceId, logId string, downloadValidTimeInSec string) (*GetBinlogInfoResult, error) { + result := &GetBinlogInfoResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/binlogs/" + logId + "/" + downloadValidTimeInSec). + WithResult(result). + Do() + + return result, err +} + +// RecoveryToSourceInstanceByDatetime - recover by datetime +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to recover the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoveryToSourceInstanceByDatetime(instanceId string, args *RecoveryByDatetimeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/recoveryToSourceInstanceByDatetime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// RecoveryToSourceInstanceBySnapshot - recover by snapshot +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to recover the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoveryToSourceInstanceBySnapshot(instanceId string, args *RecoveryBySnapshotArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/recoveryToSourceInstanceBySnapshot"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetZoneList - list all zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *GetZoneListResult: result of the zone list +// - error: nil if success otherwise the specific error +func (c *Client) GetZoneList() (*GetZoneListResult, error) { + result := &GetZoneListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX + "/zone"). + WithResult(result). + Do() + + return result, err +} + +// ListsSubnet - list all Subnets +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list all subnets, not necessary +// +// RETURNS: +// - *ListSubnetsResult: result of the subnet list +// - error: nil if success otherwise the specific error +func (c *Client) ListSubnets(args *ListSubnetsArgs) (*ListSubnetsResult, error) { + if args == nil { + args = &ListSubnetsArgs{} + } + + result := &ListSubnetsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(URI_PREFIX+"/subnet"). + WithQueryParamFilter("vpcId", args.VpcId). + WithQueryParamFilter("zoneName", args.ZoneName). + WithResult(result). + Do() + + return result, err +} + +// GetSecurityIps - get all SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *GetSecurityIpsResult: all security IP +// - error: nil if success otherwise the specific error +func (c *Client) GetSecurityIps(instanceId string) (*GetSecurityIpsResult, error) { + result := &GetSecurityIpsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/securityIp"). + WithResult(result). + Do() + + return result, err +} + +// UpdateSecurityIps - update SecurityIps +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Etag: get latest etag by GetSecurityIps +// - Args: all SecurityIps +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSecurityIps(instanceId, Etag string, args *UpdateSecurityIpsArgs) error { + + headers := map[string]string{"x-bce-if-match": Etag} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/securityIp"). + WithHeaders(headers). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ListParameters - list all parameters of a RDS instance +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ListParametersResult: the result of list all parameters +// - error: nil if success otherwise the specific error +func (c *Client) ListParameters(instanceId string) (*ListParametersResult, error) { + result := &ListParametersResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/parameter"). + WithResult(result). + Do() + + return result, err +} + +// UpdateParameter - update Parameter +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - Etag: get latest etag by ListParameters +// - Args: *UpdateParameterArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateParameter(instanceId, Etag string, args *UpdateParameterArgs) error { + + headers := map[string]string{"x-bce-if-match": Etag} + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/parameter"). + WithHeaders(headers). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ParameterHistory - list all parameter history +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *ParameterHistory: the result of paremeters history +// - error: nil if success otherwise the specific error +func (c *Client) ParameterHistory(instanceId string) (*ParameterHistoryResult, error) { + result := &ParameterHistoryResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/parameter/history"). + WithResult(result). + Do() + + return result, err +} + +// autoRenew - create autoRenew +// +// PARAMS: +// - Args: *autoRenewArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AutoRenew(args *AutoRenewArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUri()). + WithQueryParam("autoRenew", ""). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// getSlowLogDownloadTaskList +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - datetime: the log time. range(datetime, datetime + 24 hours) +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowLogDownloadTaskList(instanceId, datetime string) (*SlowLogDownloadTaskListResult, error) { + fmt.Println(getRdsUriWithInstanceId(instanceId) + "/slowlogs/logList/" + datetime) + result := &SlowLogDownloadTaskListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/slowlogs/logList/" + datetime). + WithResult(result). + Do() + fmt.Println(result, err) + return result, err +} + +// getSlowLogDownloadDetail +// +// PARAMS: +// - Args: *slowLogDownloadTaskListArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowLogDownloadDetail(instanceId, logId, downloadValidTimeInSec string) (*SlowLogDownloadDetail, error) { + result := &SlowLogDownloadDetail{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/slowlogs/download_url/" + logId + "/" + downloadValidTimeInSec). + WithResult(result). + Do() + return result, err +} + +// maintaintime - update maintaintime +// +// PARAMS: +// - Args: *maintainTimeArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateMaintainTime(instanceId string, args *MaintainTimeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/maintaintime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// diskAutoResizeConfig - config disk auto resize +// +// PARAMS: +// - Args: *diskAutoResizeArgs +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ConfigDiskAutoResize(instanceId, action string, args *DiskAutoResizeArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/diskAutoResize/config/"+action). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// AutoResizeConfig - show disk auto resize config +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *AutoResizeConfigResult: the result of autoResizeConfig +// - error: nil if success otherwise the specific error +func (c *Client) GetAutoResizeConfig(instanceId string) (*AutoResizeConfigResult, error) { + result := &AutoResizeConfigResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/autoResizeConfig"). + WithResult(result). + Do() + + return result, err +} + +// EnableAutoExpansion - is support auto expansion +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// +// RETURNS: +// - *supportEnableDiskAutoResize: the result of list all parameters +// - error: nil if success otherwise the specific error +func (c *Client) EnableAutoExpansion(instanceId string) (*EnableAutoExpansionResult, error) { + result := &EnableAutoExpansionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/autoExpansion"). + WithResult(result). + Do() + + return result, err +} + +// AzoneMigration - azone migration +// +// PARAMS: +// - instanceId: the specific rds Instance's ID +// - args: the arguments to set azone migration +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AzoneMigration(instanceId string, args *AzoneMigration) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/azoneMigration"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// UpdateDatabasePort - update database port +// +// PARAMS: +// - instanceId: id of the instance +// - args: the arguments to update database port +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateDatabasePort(instanceId string, args *UpdateDatabasePortArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/port"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// ListDatabases - list all databases +// +// PARAMS: +// - instanceId: id of the instance +// +// RETURNS: +// - *ListDatabasesResult: result of the database list +// - error: nil if success otherwise the specific error +func (c *Client) ListDatabases(instanceId string) (*ListDatabasesResult, error) { + result := &ListDatabasesResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/databases"). + WithResult(result). + Do() + + return result, err +} + +// DatabaseRemark - update database's remark +// +// PARAMS: +// - instanceIds: the specific instanceIds +// - dbName: the specific database's name +// - args: the arguments used to modify database's description +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyDatabaseDesc(instanceId, dbName string, args *ModifyDatabaseDesc) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/databases/"+dbName+"/remark"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteDatabase - delete database of RDS instance +// +// PARAMS: +// - instanceId: the specific instanceId +// - dbName: the specific database's name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteDatabase(instanceId, dbName string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId) + "/databases/" + dbName). + Do() +} + +// CreateDatabase - create a database with the specific parameters +// +// PARAMS: +// - instanceId: the specific instanceId +// - args: the arguments to create a database +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateDatabase(instanceId string, args *CreateDatabaseArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId)+"/databases"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// TaskList - task list +// +// PARAMS: +// - args: the arguments to list tasks +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) TaskList(args *TaskListArgs) (*TaskListResult, error) { + result := &TaskListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/task"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListRecyclerInstance - list all recycler instance +// +// PARAMS: +// - marker: the specific marker +// - maxKeys: the specific max keys +// +// RETURNS: +// - *RecyclerListResult: the result of list all recycler instance +// - error: nil if success otherwise the specific error +func (c *Client) ListRecyclerInstance(args *ListRdsArgs) (*RecyclerListResult, error) { + result := &RecyclerListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUri()+"/recycler/list"). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// RecyclerRecover - recover recycler instance +// +// PARAMS: +// - args: the arguments used to recover recycler instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecyclerRecover(args *RecyclerRecoverArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUri()+"/recycler/recover"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteRecyclerInstance - delete recycler instance +// +// PARAMS: +// - instanceId: the specific instanceId +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecyclerInstance(instanceId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUri() + "/recycler/" + instanceId). + Do() +} + +// CreateInstanceGroup - create instance group +// +// PARAMS: +// - args: the arguments to group create +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstanceGroup(args *InstanceGroupArgs) (*CreateInstanceGroupResult, error) { + result := &CreateInstanceGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListInstanceGroup - list all instace group +// +// PARAMS: +// - manner: the specific manner +// - order: asc or desc +// - orderBy: the specific orderBy +// - pageNo: the specific pageNo +// - pageSize: the specific pageSize +// +// RETURNS: +// - *InstanceGroupResult: the result of list all instance group +// - error: nil if success otherwise the specific error +func (c *Client) ListInstanceGroup(args *ListInstanceGroupArgs) (*InstanceGroupListResult, error) { + result := &InstanceGroupListResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.PageNo == 0 { + args.PageNo = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUri()+"/group"). + WithQueryParamFilter("manner", args.Manner). + WithQueryParamFilter("order", args.Order). + WithQueryParamFilter("orderBy", args.OrderBy). + WithQueryParamFilter("pageNo", strconv.Itoa(args.PageNo)). + WithQueryParamFilter("pageSize", strconv.Itoa(args.PageSize)). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupDetail - show the detail of instance group +// +// PARAMS: +// +// RETURNS: +// - *InstanceGroupDetailResult: the result of instance group detail +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupDetail(groupId string) (*InstanceGroupDetailResult, error) { + result := &InstanceGroupDetailResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUri() + "/group/" + groupId). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupCheckGtid - check the gtid of instance group +// +// PARAMS: +// - args: the arguments to check gtid +// +// RETURNS: +// - *CheckGtidResult: the result of check gtid +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupCheckGtid(args *CheckGtidArgs) (*CheckGtidResult, error) { + result := &CheckGtidResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/checkGtid"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupCheckPing - instance group connectivity check +// +// PARAMS: +// - args: the arguments of connectivity check +// +// RETURNS: +// - *CheckPingResult: the result of connectivity check +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupCheckPing(args *CheckPingArgs) (*CheckPingResult, error) { + result := &CheckPingResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/checkPing"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupCheckData - instance group data check +// +// PARAMS: +// - args: the arguments of data check +// +// RETURNS: +// - *CheckDataResult: the result of data check +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupCheckData(args *CheckDataArgs) (*CheckDataResult, error) { + result := &CheckDataResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/checkData"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupCheckVersion - instance group version check +// +// PARAMS: +// - args: the arguments of version check +// +// RETURNS: +// - *CheckVersionResult: the result of data check +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupCheckVersion(args *CheckVersionArgs) (*CheckVersionResult, error) { + result := &CheckVersionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/checkVersion"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateInstanceGroupName - update instance group name +// +// PARAMS: +// - args: the arguments of update instance group name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceGroupName(groupId string, args *InstanceGroupNameArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/"+groupId+"/name"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// InstanceGroupAdd - add instance to instance group +// +// PARAMS: +// - args: the arguments of add instance to instance group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupAdd(groupId string, args *InstanceGroupAddArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/"+groupId+"/instance"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// InstanceGroupBatchAdd - batch add instance to instance group +// +// PARAMS: +// - args: the arguments of batch add instance to instance group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupBatchAdd(args *InstanceGroupBatchAddArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/batchjoin"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// InstanceGroupForceChange - force change instance +// +// PARAMS: +// - args: the arguments used to force change instance +// +// RETURNS: +// - *ForceChangeResult: the result of force change +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupForceChange(groupId string, args *ForceChangeArgs) (*ForceChangeResult, error) { + result := &ForceChangeResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUri()+"/group/"+groupId+"/forceChange"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// InstanceGroupChangeLeader - change leader of instance group +// +// PARAMS: +// - args: the arguments used to change leader of instance group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupLeaderChange(groupId string, args *GroupLeaderChangeArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUri()+"/group/"+groupId+"/instance"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + + return err +} + +// InstanceGroupRemove - remove instance to instance group +// +// PARAMS: +// - args: the arguments of remove instance to instance group +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceGroupRemove(groupId, instanceId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUri()+"/group/"+groupId+"/instance/"+instanceId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// DeleteInstanceGroup - delete instance group +// +// PARAMS: +// - groupId: the instance group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstanceGroup(groupId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUri()+"/group/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// InstanceMinorVersionList - list minor versions available for instance +// +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *MinorVersionListResult: the result of list minor versions available for instance +// - error: nil if success otherwise the specific error +func (c *Client) InstanceMinorVersionList(instanceId string) (*MinorVersionListResult, error) { + result := &MinorVersionListResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v1/rds/instance/" + instanceId + "/upgradeMinorVersionList"). + WithResult(result). + Do() + + return result, err +} + +// InstanceUpgradeMinorVersion - upgrade minor version +// +// PARAMS: +// - instanceId: the instance id +// - args: the arguments used to upgrade minor version +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) InstanceUpgradeMinorVersion(instanceId string, args *UpgradeMinorVersionArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL("/v1/rds/instance/"+instanceId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithQueryParam("upgradeMinorVersion", ""). + Do() + + return err +} + +// SlowSqlFlowStatus - get slow sql flow status +// +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *SlowSqlFlowStatusResult: the result of slow sql flow status +// - error: nil if success otherwise the specific error +func (c *Client) SlowSqlFlowStatus(instanceId string) (*SlowSqlFlowStatusResult, error) { + result := &SlowSqlFlowStatusResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsqlflow"). + WithResult(result). + Do() + + return result, err +} + +// EnableSlowSqlFlow - enable slow sql flow +// +// PARAMS: +// - instanceId: the instance id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) EnableSlowSqlFlow(instanceId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId)+"/smartdba/slowsqlflow"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// DisableSlowSqlFlow - disable slow sql flow +// +// PARAMS: +// - instanceId: the instance id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DisableSlowSqlFlow(instanceId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId)+"/smartdba/slowsqlflow"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + + return err +} + +// GetSlowSqlList - get slow sql list +// +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlArgs: the arguments of slow sql list +// +// RETURNS: +// - *SlowSqlListResult: the result of slow sql list +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlList(instanceId string, args *GetSlowSqlArgs) (*SlowSqlListResult, error) { + result := &SlowSqlListResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.Page == 0 { + args.Page = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/list"). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlBySqlId - get slow sql by sql id +// PARAMS: +// - InstanceId: instance id +// - SqlId: the sql id +// +// RETURNS: +// - *SlowSqlItem: the result of slow sql detail +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlBySqlId(instanceId, sqlId string) (*SlowSqlItem, error) { + result := &SlowSqlItem{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/" + sqlId). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlExplain - get slow sql explain +// PARAMS: +// - InstanceId: instance id +// - SqlId: the sql id +// - Schema: the schema +// +// RETURNS: +// - *SlowSqlExplainResult: the result of slow sql explain +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlExplain(instanceId, sqlId, schema string) (*SlowSqlExplainResult, error) { + result := &SlowSqlExplainResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/explain/" + sqlId + "/" + schema). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlStatsDigest - get slow sql stats digest +// +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlArgs: the arguments of slow sql +// +// RETURNS: +// - *SlowSqlDigestResult: the result of slow sql stats digest +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlStatsDigest(instanceId string, args *GetSlowSqlArgs) (*SlowSqlDigestResult, error) { + result := &SlowSqlDigestResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.Page == 0 { + args.Page = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/stats/digest"). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlDuration - get slow sql duration +// +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlDurationArgs: gs query arguments of slow sql duration +// +// RETURNS: +// - *SlowSqlDurationResult: the result of slow sql duration +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlDuration(instanceId string, args *GetSlowSqlDurationArgs) (*SlowSqlDurationResult, error) { + result := &SlowSqlDurationResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/stats/duration"). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlSource - get slow sql source +// +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlSourceArgs: gs query arguments of slow sql srouce +// +// RETURNS: +// - *SlowSqlSourceResult: the result of slow sql souce +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlSource(instanceId string, args *GetSlowSqlSourceArgs) (*SlowSqlSourceResult, error) { + result := &SlowSqlSourceResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/stats/source"). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlSchema - get slow sql schema +// PARAMS: +// - InstanceId: instance id +// - SqlId: the sql id +// - Schema: the schema +// +// RETURNS: +// - *SlowSqlSchemaResult: the result of slow sql schema +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlSchema(instanceId, sqlId, schema string) (*SlowSqlSchemaResult, error) { + result := &SlowSqlSchemaResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/" + sqlId + "/" + schema). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlTable - get slow sql table +// PARAMS: +// - InstanceId: instance id +// - SqlId: the sql id +// - Schema: the schema +// - Table: the table +// +// RETURNS: +// - *SlowSqlTableResult: the result of slow sql table +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlTable(instanceId, sqlId, schema, table string) (*SlowSqlTableResult, error) { + result := &SlowSqlTableResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/" + sqlId + "/" + schema + "/" + table). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlIndex - get slow sql index +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlIndexArgs: the arguments of slow sql index +// +// RETURNS: +// - *SlowSqlIndexResult: the result of slow sql index +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlIndex(instanceId string, args *GetSlowSqlIndexArgs) (*SlowSqlIndexResult, error) { + result := &SlowSqlIndexResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/table/index"). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlTrend - get slow sql trend +// PARAMS: +// - InstanceId: instance id +// - *GetSlowSqlTrendArgs: the arguments of slow sql trend +// +// RETURNS: +// - *SlowSqlTrendResult: the result of slow sql trend +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlTrend(instanceId string, args *GetSlowSqlTrendArgs) (*SlowSqlTrendResult, error) { + result := &SlowSqlTrendResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/trend"). + WithResult(result). + Do() + + return result, err +} + +// GetSlowSqlAdvice - get slow sql advice +// PARAMS: +// - InstanceId: instance id +// - SqlId: the sql id +// - Schema: the schema +// +// RETURNS: +// - *SlowSqlAdviceResult: the result of slow sql advice +// - error: nil if success otherwise the specific error +func (c *Client) GetSlowSqlAdvice(instanceId, sqlId, schema string) (*SlowSqlAdviceResult, error) { + result := &SlowSqlAdviceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/slowsql/tuning/" + sqlId + "/" + schema). + WithResult(result). + Do() + + return result, err +} + +// GetDiskInfo - get disk info +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *DiskInfoResult: the result of disk info +// - error: nil if success otherwise the specific error +func (c *Client) GetDiskInfo(instanceId string) (*DiskInfoResult, error) { + result := &DiskInfoResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/disk/list"). + WithResult(result). + Do() + + return result, err +} + +// GetDbListSize - get database list size +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *DbListResult: the result of database list size +// - error: nil if success otherwise the specific error +func (c *Client) GetDbListSize(instanceId string) (*DbListResult, error) { + result := &DbListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/db/list"). + WithResult(result). + Do() + + return result, err +} + +// GetTableListInfo - get table list info +// PARAMS: +// - InstanceId: instance id +// - *GetTableListArgs: the arguments of table list info +// +// RETURNS: +// - *TableListResult: the result of table list info +// - error: nil if success otherwise the specific error +func (c *Client) GetTableListInfo(instanceId string, args *GetTableListArgs) (*TableListResult, error) { + result := &TableListResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.PageNo == 0 { + args.PageNo = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/tb/list"). + WithResult(result). + Do() + + return result, err +} + +// GetKillSessionTypes - get kill session types +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *KillSessionTypesResult: the result of the kill session types +// - error: nil if success otherwise the specific error +func (c *Client) GetKillSessionTypes(instanceId string) (*KillSessionTypesResult, error) { + result := &KillSessionTypesResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/kill/types"). + WithResult(result). + Do() + + return result, err +} + +// GetSessionSummary - get session summary +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *SessionSummaryResult: the result of the session summary +// - error: nil if success otherwise the specific error +func (c *Client) GetSessionSummary(instanceId string) (*SessionSummaryResult, error) { + result := &SessionSummaryResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/summary"). + WithResult(result). + Do() + + return result, err +} + +// GetSessionDetail - get session detail +// PARAMS: +// - InstanceId: instance id +// - *SessionDetailArgs: the arguments of session detail +// +// RETURNS: +// - *SessionDetailResult: the result of the session detail +// - error: nil if success otherwise the specific error +func (c *Client) GetSessionDetail(instanceId string, args *SessionDetailArgs) (*SessionDetailResult, error) { + result := &SessionDetailResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.Page == 0 { + args.Page = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/detail"). + WithResult(result). + Do() + + return result, err +} + +// CheckKillSessionAuth - check kill session auth +// PARAMS: +// - InstanceId: instance id +// - *KillSessionAuthArgs: the arguments of kill session auth +// +// RETURNS: +// - *KillSessionAuthResult: the result of kill session auth +// - error: nil if success otherwise the specific error +func (c *Client) CheckKillSessionAuth(instanceId string, args *KillSessionAuthArgs) (*KillSessionAuthResult, error) { + result := &KillSessionAuthResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/kill/authority"). + WithResult(result). + Do() + + return result, err +} + +// GetKillSessionHistory - get kill session history +// PARAMS: +// - InstanceId: instance id +// - *KillSessionHistory: the arguments of kill session history +// +// RETURNS: +// - *KillSessionHistoryResult: the result of kill session history +// - error: nil if success otherwise the specific error +func (c *Client) GetKillSessionHistory(instanceId string, args *KillSessionHistory) (*KillSessionHistoryResult, error) { + result := &KillSessionHistoryResult{} + if args.PageSize == 0 { + args.PageSize = DEFAULT_PAGE_SIZE + } + if args.Page == 0 { + args.Page = DEFAULT_PAGE_NUM + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/kill/history"). + WithResult(result). + Do() + + return result, err +} + +// KillSession - kill session +// PARAMS: +// - InstanceId: instance id +// - *KillSessionArgs: the arguments of kill session +// +// RETURNS: +// - *KillSessionResult: the result of kill session +// - error: nil if success otherwise the specific error +func (c *Client) KillSession(instanceId string, args *KillSessionArgs) (*KillSessionResult, error) { + result := &KillSessionResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/kill"). + WithResult(result). + Do() + + return result, err +} + +// GetSessionStatistics - get session statistics +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *SessionStatisticsResult: the result of the session statistics +// - error: nil if success otherwise the specific error +func (c *Client) GetSessionStatistics(instanceId string) (*SessionStatisticsResult, error) { + result := &SessionStatisticsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/session/statistics"). + WithResult(result). + Do() + + return result, err +} + +// GetErrorLogStatus - get error log status +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *ErrorLogStatusResult: the result of error log status +// - error: nil if success otherwise the specific error +func (c *Client) GetErrorLogStatus(instanceId string) (*ErrorLogStatusResult, error) { + result := &ErrorLogStatusResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/errorlogflow"). + WithResult(result). + Do() + + return result, err +} + +// EnableErrorLog - enable error log +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *ErrorLogResult: the result of enable error log +// - error: nil if success otherwise the specific error +func (c *Client) EnableErrorLog(instanceId string) (*ErrorLogResult, error) { + result := &ErrorLogResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/errorlogflow"). + WithResult(result). + Do() + + return result, err +} + +// DisableErrorLog - disable error log +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *ErrorLogResult: the result of disable error log +// - error: nil if success otherwise the specific error +func (c *Client) DisableErrorLog(instanceId string) (*ErrorLogResult, error) { + result := &ErrorLogResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/errorlogflow"). + WithResult(result). + Do() + + return result, err +} + +// GetErrorLogList - get error log list +// PARAMS: +// - InstanceId: instance id +// - *ErrorLogListArgs: error log list arguments +// +// RETURNS: +// - *ErrorLogListResult: the result of error log list +// - error: nil if success otherwise the specific error +func (c *Client) GetErrorLogList(instanceId string, args *ErrorLogListArgs) (*ErrorLogListResult, error) { + result := &ErrorLogListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/smartdba/errorlog/detail"). + WithResult(result). + Do() + + return result, err +} + +// GetSqlFilterList - get sql filter list +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *SqlFilterListResult: the result of sql filter list +// - error: nil if success otherwise the specific error +func (c *Client) GetSqlFilterList(instanceId string) (*SqlFilterListResult, error) { + result := &SqlFilterListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter"). + WithResult(result). + Do() + + return result, err +} + +// GetSqlFilterDetail - get sql filter detail +// PARAMS: +// - InstanceId: instance id +// - SqlFilterId: sql filter id +// +// RETURNS: +// - *SqlFilterDetailResult: the result of sql filter detail +// - error: nil if success otherwise the specific error +func (c *Client) GetSqlFilterDetail(instanceId, sqlFilterId string) (*SqlFilterItem, error) { + result := &SqlFilterItem{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter/" + sqlFilterId). + WithResult(result). + Do() + + return result, err +} + +// AddSqlFilter - add sql filter +// PARAMS: +// - InstanceId: instance id +// - *AddSqlFilterArgs: add sql filter arguments +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AddSqlFilter(instanceId string, args *SqlFilterArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter"). + Do() + + return err +} + +// UpdateSqlFilter - update sql filter +// PARAMS: +// - InstanceId: instance id +// - SqlFilterId: sql filter id +// - *AddSqlFilterArgs: add sql filter arguments +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSqlFilter(instanceId, sqlFilterId string, args *SqlFilterArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter/" + sqlFilterId). + Do() + + return err +} + +// StartOrStopSqlFilter - start or stop sql filter +// - InstanceId: instance id +// - SqlFilterId: sql filter id +// - *StartOrStopSqlFilterArgs: start or stop sql filter arguments +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) StartOrStopSqlFilter(instanceId, sqlFilterId string, args *StartOrStopSqlFilterArgs) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter/" + sqlFilterId). + Do() + + return err +} + +// DeleteSqlFilter - delete sql filter +// - InstanceId: instance id +// - SqlFilterId: sql filter id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSqlFilter(instanceId, sqlFilterId string) error { + + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter/" + sqlFilterId). + Do() + + return err +} + +// IsAllowedSqlFilter - check if sql filter is allowed +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *IsAllowedResult: the result of sql filter allowed +// - error: nil if success otherwise the specific error +func (c *Client) IsAllowedSqlFilter(instanceId string) (*IsAllowedResult, error) { + result := &IsAllowedResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/sqlfilter/allowed"). + WithResult(result). + Do() + + return result, err +} + +// ProcessKill - kill process +// PARAMS: +// - InstanceId: instance id +// - *ProcessArgs: process arguments +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ProcessKill(instanceId string, args *ProcessArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithBody(args). + WithURL(getRdsUriWithInstanceId(instanceId) + "/performance/process/kill"). + Do() + + return err +} + +// InnodbStatus - get innodb status +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *InnodbStatusResult: the result of innodb status +// - error: nil if success otherwise the specific error +func (c *Client) InnodbStatus(instanceId string) (*InnodbStatusResult, error) { + result := &InnodbStatusResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/performance/innodbstatus"). + WithResult(result). + Do() + + return result, err +} + +// ProcessList - get process list +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *ProcessListResult: the result of process list +// - error: nil if success otherwise the specific error +func (c *Client) ProcessList(instanceId string) (*ProcessListResult, error) { + result := &ProcessListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/performance/processlist"). + WithResult(result). + Do() + + return result, err +} + +// TransactionList - get transaction list +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *TransactionListResult: the result of transaction list +// - error: nil if success otherwise the specific error +func (c *Client) TransactionList(instanceId string) (*TransactionListResult, error) { + result := &TransactionListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/performance/transaction"). + WithResult(result). + Do() + + return result, err +} + +// ConnectionList - get connection list +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *ConnectionListResult: the result of transaction list +// - error: nil if success otherwise the specific error +func (c *Client) ConnectionList(instanceId string) (*ConnectionListResult, error) { + result := &ConnectionListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getRdsUriWithInstanceId(instanceId) + "/performance/connection"). + WithResult(result). + Do() + + return result, err +} + +// FailInjectWhiteList - get fail inject white list +// +// RETURNS: +// - *FailInjectWhiteListResult: the result of transaction list +// - error: nil if success otherwise the specific error +func (c *Client) FailInjectWhiteList() (*FailInjectWhiteListResult, error) { + result := &FailInjectWhiteListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v1/failinject/whitelist"). + WithResult(result). + Do() + + return result, err +} + +// AddToFailInjectWhiteList - add instance to failinject whitelist +// PARAMS: +// - *AddFailInjectArgs: add failinject args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AddToFailInjectWhiteList(args *FailInjectArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithBody(args). + WithURL("/v1/failinject/whitelist"). + Do() + + return err +} + +// RemoveFailInjectWhiteList - remove instance to failinject whitelist +// PARAMS: +// - *RemoveFailInjectArgs: remove failinject args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RemoveFailInjectWhiteList(args *FailInjectArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithBody(args). + WithURL("/v1/failinject/whitelist/remove"). + Do() + + return err +} + +// FailInjectStart - start failinject instance +// PARAMS: +// - InstanceId: instance id +// +// RETURNS: +// - *TaskResult: the result of task +// - error: nil if success otherwise the specific error +func (c *Client) FailInjectStart(instanceId string) (*TaskResult, error) { + result := &TaskResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL("/v1/failinject/" + instanceId). + WithResult(result). + Do() + + return result, err +} + +// GetOrderStatus - get order status +// PARAMS: +// - OrderId: order id +// +// RETURNS: +// - *OrderStatusResult: the result of order status +// - error: nil if success otherwise the specific error +func (c *Client) GetOrderStatus(orderId string) (*OrderStatusResult, error) { + result := &OrderStatusResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v1/instance/order/" + orderId). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) Request(method, uri string, body interface{}) (interface{}, error) { + res := struct{}{} + req := bce.NewRequestBuilder(c). + WithMethod(method). + WithURL(uri). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + var err error + if body != nil { + err = req. + WithBody(body). + WithResult(res). + Do() + } else { + err = req. + WithResult(res). + Do() + } + + return res, err +} diff --git a/bce-sdk-go/services/rds/util.go b/bce-sdk-go/services/rds/util.go new file mode 100644 index 0000000..4b0f618 --- /dev/null +++ b/bce-sdk-go/services/rds/util.go @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of RDS service +package rds + +import ( + "encoding/hex" + "fmt" + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} diff --git a/bce-sdk-go/services/scs/client.go b/bce-sdk-go/services/scs/client.go new file mode 100644 index 0000000..ded3609 --- /dev/null +++ b/bce-sdk-go/services/scs/client.go @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for SCS service + +// Package scs defines the SCS services of BCE. The supported APIs are all defined in sub-package +package scs + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + DEFAULT_ENDPOINT = "redis." + bce.DEFAULT_REGION + ".baidubce.com" +) + +// Client of SCS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} diff --git a/bce-sdk-go/services/scs/client_test.go b/bce-sdk-go/services/scs/client_test.go new file mode 100644 index 0000000..e6ef83a --- /dev/null +++ b/bce-sdk-go/services/scs/client_test.go @@ -0,0 +1,1191 @@ +package scs + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/model" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + SCS_CLIENT *Client + SCS_TEST_ID string + client *Client +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +const ( + SDK_NAME_PREFIX = "sdk_scs_" +) + +var instanceId = SCS_TEST_ID + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 1; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + SCS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) + client = SCS_CLIENT + SCS_TEST_ID = "scs-bj-rlmhqtbehihj" + instanceId = SCS_TEST_ID +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateInstance(t *testing.T) { + id := strconv.FormatInt(time.Now().Unix(), 10) + args := &CreateInstanceArgs{ + Billing: Billing{ + PaymentTiming: "Postpaid", + Reservation: &Reservation{ + ReservationLength: 1, + }, + }, + ClientToken: getClientToken(), + PurchaseCount: 1, + InstanceName: SDK_NAME_PREFIX + id, + Port: 6379, + Engine: 3, + // EngineVersion: "5.0", + NodeType: "pega.g4s1.micro", + ClusterType: "cluster", + ReplicationNum: 2, + ShardNum: 1, + ProxyNum: 2, + DiskFlavor: 60, + DiskType: "essd", + BgwGroupId: "", + StoreType: 3, + EnableReadOnly: 1, + ClientAuth: "ABlockIs16Bytes!", + } + result, err := SCS_CLIENT.CreateInstance(args) + ExpectEqual(t.Errorf, nil, err) + + if len(result.InstanceIds) > 0 { + SCS_TEST_ID = result.InstanceIds[0] + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_ListInstances(t *testing.T) { + args := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(args) + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) + for _, e := range result.Instances { + if e.InstanceID == SCS_TEST_ID { + ExpectEqual(t.Errorf, "Postpaid", e.PaymentTiming) + } + } +} + +func TestClient_GetInstanceDetail(t *testing.T) { + result, err := SCS_CLIENT.GetInstanceDetail(SCS_TEST_ID) + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_UpdateInstanceName(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &UpdateInstanceNameArgs{ + InstanceName: e.InstanceName + "_new", + ClientToken: getClientToken(), + } + err := SCS_CLIENT.UpdateInstanceName(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ResizeInstance(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &ResizeInstanceArgs{ + NodeType: "cache.n1.mirco", + ShardNum: 4, + ClientToken: getClientToken(), + IsDefer: true, + } + result, err := SCS_CLIENT.GetInstanceDetail(SCS_TEST_ID) + ExpectEqual(t.Errorf, nil, err) + if result.InstanceStatus == "Running" { + err := SCS_CLIENT.ResizeInstance(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) + } +} + +func TestClient_GetCreatePrice(t *testing.T) { + args := &CreatePriceArgs{ + Engine: 2, + Period: 1, + ChargeType: "prepay", + NodeType: "cache.n1.small", + ReplicationNum: 2, + ClusterType: "cluster", + } + result, err := SCS_CLIENT.GetCreatePrice(args) + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_GetResizePrice(t *testing.T) { + args := &ResizePriceArgs{ + ChangeType: "nodeModify", + ShardNum: 2, + ReplicationNum: 1, + NodeType: "cache.n1.small", + Period: 1, + } + result, err := SCS_CLIENT.GetResizePrice(instanceId, args) + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) + +} + +func TestClient_AddReplication(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &ReplicationArgs{ + ResizeType: "add", + ReplicationInfo: []Replication{ + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 1}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + }, + ClientToken: getClientToken(), + } + result, err := SCS_CLIENT.GetInstanceDetail(SCS_TEST_ID) + ExpectEqual(t.Errorf, nil, err) + if result.InstanceStatus == "Running" { + err := SCS_CLIENT.AddReplication(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) + } +} + +func TestClient_Deletelication(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &ReplicationArgs{ + ResizeType: "delete", + ReplicationInfo: []Replication{ + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 1}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + {AvailabilityZone: "cn-bj-a", SubnetId: "sbn-fh56wbtv1ycw", IsMaster: 0}, + }, + ClientToken: getClientToken(), + } + result, err := SCS_CLIENT.GetInstanceDetail(SCS_TEST_ID) + ExpectEqual(t.Errorf, nil, err) + if result.InstanceStatus == "Running" { + err := SCS_CLIENT.DeleteReplication(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) + } +} + +func TestClient_RestartInstance(t *testing.T) { + args := &RestartInstanceArgs{ + IsDefer: true, + } + err := SCS_CLIENT.RestartInstance(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetNodeTypeList(t *testing.T) { + _, err := SCS_CLIENT.GetNodeTypeList() + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} + +func TestClient_ListSubnets(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &ListSubnetsArgs{} + _, err := SCS_CLIENT.ListSubnets(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateInstanceDomainName(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &UpdateInstanceDomainNameArgs{ + Domain: "new" + e.Domain, + ClientToken: getClientToken(), + } + err := SCS_CLIENT.UpdateInstanceDomainName(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_GetZoneList(t *testing.T) { + //isAvailable(SCS_TEST_ID) + _, err := SCS_CLIENT.GetZoneList() + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ModifyPassword(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &ModifyPasswordArgs{ + Password: "1234qweR", + ClientToken: getClientToken(), + } + err := SCS_CLIENT.ModifyPassword(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_RenameDomain(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &RenameDomainArgs{ + Domain: "newDomain", + ClientToken: getClientToken(), + } + err := SCS_CLIENT.RenameDomain(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SwapDomain(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &SwapDomainArgs{ + SourceInstanceId: SCS_TEST_ID, + TargetInstanceId: "scs-bj-xeelkkdsx", + ClientToken: getClientToken(), + } + err := SCS_CLIENT.SwapDomain(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_FlushInstance(t *testing.T) { + isAvailable(SCS_TEST_ID) + time.Sleep(30 * time.Second) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &FlushInstanceArgs{ + Password: "1234qweR", + ClientToken: getClientToken(), + } + err := SCS_CLIENT.FlushInstance(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_BindingTag(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &BindingTagArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, + } + err := SCS_CLIENT.BindingTag(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_UnBindingTag(t *testing.T) { + isAvailable(SCS_TEST_ID) + time.Sleep(30 * time.Second) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &BindingTagArgs{ + ChangeTags: []model.TagModel{ + { + TagKey: "tag1", + TagValue: "var1", + }, + }, + } + err := SCS_CLIENT.UnBindingTag(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_SetAsMaster(t *testing.T) { + isAvailable(SCS_TEST_ID) + err := SCS_CLIENT.SetAsMaster(SCS_TEST_ID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SetAsSlave(t *testing.T) { + isAvailable(SCS_TEST_ID) + args := &SetAsSlaveArgs{ + MasterDomain: "masterDomain", + MasterPort: 6379, + } + err := SCS_CLIENT.SetAsSlave(SCS_TEST_ID, args) + ExpectEqual(t.Errorf, nil, err) +} +func TestClient_GetSecurityIp(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + _, err := SCS_CLIENT.GetSecurityIp(e.InstanceID) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_AddSecurityIp(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &SecurityIpArgs{ + SecurityIps: []string{ + "192.0.0.1", + }, + ClientToken: getClientToken(), + } + err := SCS_CLIENT.AddSecurityIp(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_DeleteSecurityIp(t *testing.T) { + isAvailable(SCS_TEST_ID) + time.Sleep(30 * time.Second) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &SecurityIpArgs{ + SecurityIps: []string{ + "192.0.0.1", + }, + ClientToken: getClientToken(), + } + err := SCS_CLIENT.DeleteSecurityIp(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_GetParameters(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + _, err := SCS_CLIENT.GetParameters(e.InstanceID) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_ModifyParameters(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &ModifyParametersArgs{ + Parameter: InstanceParam{ + Name: "timeout", + Value: "0", + }, + ClientToken: getClientToken(), + } + err := SCS_CLIENT.ModifyParameters(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_GetBackupList(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + _, err := SCS_CLIENT.GetBackupList(e.InstanceID) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_GetBackupDetail(t *testing.T) { + isAvailable(SCS_TEST_ID) + result, err := SCS_CLIENT.GetBackupDetail(SCS_TEST_ID, "2587532") + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} +func TestClient_ModifyBackupPolicy(t *testing.T) { + isAvailable(SCS_TEST_ID) + listInstancesArgs := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(listInstancesArgs) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Running" == e.InstanceStatus { + args := &ModifyBackupPolicyArgs{ + BackupDays: "Sun,Mon,Tue,Wed,Thu,Fri,Sta", + BackupTime: "01:05:00", + ExpireDay: 7, + ClientToken: getClientToken(), + } + err := SCS_CLIENT.ModifyBackupPolicy(e.InstanceID, args) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func TestClient_DeleteInstance(t *testing.T) { + //isAvailable(SCS_TEST_ID) + //time.Sleep(50*time.Second) + args := &ListInstancesArgs{} + result, err := SCS_CLIENT.ListInstances(args) + ExpectEqual(t.Errorf, nil, err) + for _, e := range result.Instances { + if strings.HasPrefix(e.InstanceName, SDK_NAME_PREFIX) && "Run"+ + "ning" == e.InstanceStatus && "Postpaid" == e.PaymentTiming { + err := SCS_CLIENT.DeleteInstance(e.InstanceID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) + } + } +} + +func isAvailable(instanceId string) { + for { + result, err := SCS_CLIENT.GetInstanceDetail(instanceId) + fmt.Println(instanceId, " => ", result.InstanceStatus) + if err == nil && result.InstanceStatus == "Running" { + break + } + } +} + +func TestClient_ListSecurityGroupByVpcId(t *testing.T) { + vpcId := "vpc-t7yi6xyrapjz" + securityGroups, err := client.ListSecurityGroupByVpcId(vpcId) + if err != nil { + fmt.Printf("list security group by vpcId error: %+v\n", err) + return + } + for _, group := range securityGroups.Groups { + fmt.Println("+-------------------------------------------+") + fmt.Println("id: ", group.SecurityGroupID) + fmt.Println("name: ", group.Name) + fmt.Println("description: ", group.Description) + fmt.Println("associateNum: ", group.AssociateNum) + fmt.Println("createdTime: ", group.CreatedTime) + fmt.Println("version: ", group.Version) + fmt.Println("defaultSecurityGroup: ", group.DefaultSecurityGroup) + fmt.Println("vpc name: ", group.VpcName) + fmt.Println("vpc id: ", group.VpcShortID) + fmt.Println("tenantId: ", group.TenantID) + } + fmt.Println("list security group by vpcId success.") +} + +func TestClient_ListSecurityGroupByInstanceId(t *testing.T) { + instanceId := "scs-su-bbjhgxyqyddd" + result, err := client.ListSecurityGroupByInstanceId(instanceId) + if err != nil { + fmt.Printf("list security group by instanceId error: %+v\n", err) + return + } + for _, group := range result.Groups { + fmt.Println("+-------------------------------------------+") + fmt.Println("securityGroupId: ", group.SecurityGroupID) + fmt.Println("securityGroupName: ", group.SecurityGroupName) + fmt.Println("securityGroupRemark: ", group.SecurityGroupRemark) + fmt.Println("projectId: ", group.ProjectID) + fmt.Println("vpcId: ", group.VpcID) + fmt.Println("vpcName: ", group.VpcName) + fmt.Println("inbound: ", group.Inbound) + fmt.Println("outbound: ", group.Outbound) + } + fmt.Println("list security group by instanceId success.") +} + +func TestClient_BindSecurityGroups(t *testing.T) { + instanceIds := []string{ + "scs-su-bbjhgxyqyddd", + } + securityGroupIds := []string{ + "g-eun39daa38qf", + } + args := &SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, + } + + err := client.BindSecurityGroups(args) + if err != nil { + fmt.Printf("bind security groups to instances error: %+v\n", err) + return + } + fmt.Println("bind security groups to instances success.") +} + +func TestClient_UnBindSecurityGroups(t *testing.T) { + securityGroupIds := []string{ + "g-gtj7wknuw3h9", + } + args := &UnbindSecurityGroupArgs{ + InstanceId: "scs-su-bbjhgxyqyddd", + SecurityGroupIds: securityGroupIds, + } + + err := client.UnBindSecurityGroups(args) + if err != nil { + fmt.Printf("unbind security groups to instances error: %+v\n", err) + return + } + fmt.Println("unbind security groups to instances success.") +} + +func TestClient_ReplaceSecurityGroups(t *testing.T) { + instanceIds := []string{ + "scs-mjafcdu0", + } + securityGroupIds := []string{ + "g-iutg5rtcydsk", + } + args := &SecurityGroupArgs{ + InstanceIds: instanceIds, + SecurityGroupIds: securityGroupIds, + } + + err := client.ReplaceSecurityGroups(args) + if err != nil { + fmt.Printf("replace security groups to instances error: %+v\n", err) + return + } + fmt.Println("replace security groups to instances success.") +} + +func TestClient_ListRecycleInstances(t *testing.T) { + marker := &Marker{MaxKeys: 10} + instances, err := client.ListRecycleInstances(marker) + if err != nil { + fmt.Printf("list recycler instances error: %+v\n", err) + return + } + for _, instance := range instances.Result { + fmt.Println("+-------------------------------------------+") + fmt.Println("instanceId: ", instance.InstanceID) + fmt.Println("instanceName: ", instance.InstanceName) + fmt.Println("engine: ", instance.Engine) + fmt.Println("engineVersion: ", instance.EngineVersion) + fmt.Println("instanceStatus: ", instance.InstanceStatus) + fmt.Println("isolatedStatus: ", instance.IsolatedStatus) + fmt.Println("PaymentTiming: ", instance.PaymentTiming) + fmt.Println("ClusterType: ", instance.ClusterType) + fmt.Println("Domain: ", instance.Domain) + fmt.Println("Port: ", instance.Port) + fmt.Println("VnetIP: ", instance.VnetIP) + fmt.Println("InstanceCreateTime: ", instance.InstanceCreateTime) + fmt.Println("UsedCapacity: ", instance.UsedCapacity) + fmt.Println("ZoneNames: ", instance.ZoneNames) + fmt.Println("tags: ", instance.Tags) + } +} + +func TestClient_RecoverRecyclerInstances(t *testing.T) { + instanceIds := []string{ + "scs-bj-xjgriqupoftn", + } + err := client.RecoverRecyclerInstances(instanceIds) + if err != nil { + fmt.Printf("recover recycler instances error: %+v\n", err) + return + } + fmt.Println("recover recycler instances success.") +} + +func TestClient_DeleteRecyclerInstances(t *testing.T) { + instanceIds := []string{ + "scs-bj-xuuasbccatzr", + } + err := client.DeleteRecyclerInstances(instanceIds) + if err != nil { + fmt.Printf("delete recycler instances error: %+v\n", err) + return + } + fmt.Println("delete recycler instances success.") +} + +func TestClient_RenewInstances(t *testing.T) { + instanceIds := []string{ + "scs-bj-xuuasbccatzr", + } + args := &RenewInstanceArgs{ + // 实例Id列表 + InstanceIds: instanceIds, + // 续费周期,单位为月 + Duration: 1, + } + result, err := client.RenewInstances(args) + if err != nil { + fmt.Printf("renew instances error: %+v\n", err) + return + } + fmt.Println("renew instances success. orderId:" + result.OrderId) +} +func TestClient_ListLogByInstanceId(t *testing.T) { + // 一天前 + date := time.Now(). + AddDate(0, 0, -1). + Format("2006-01-02 03:04:05") + fmt.Println(date) + args := &ListLogArgs{ + // 运行日志 runlog 慢日志 slowlog + FileType: "runlog", + // 开始时间格式 "yyyy-MM-dd hh:mm:ss" + StartTime: date, + // 结束时间,可选,默认返回开始时间+24小时内的日志 + // EndTime: date, + } + listLogResult, err := client.ListLogByInstanceId(instanceId, args) + if err != nil { + fmt.Printf("list logs of instance error: %+v\n", err) + return + } + fmt.Println("list logs of instance success.") + for _, shardLog := range listLogResult.LogList { + fmt.Println("+-------------------------------------------+") + fmt.Println("shard id: ", shardLog.ShardID) + fmt.Println("logs size: ", shardLog.TotalNum) + for _, log := range shardLog.LogItem { + fmt.Println("log id: ", log.LogID) + fmt.Println("size: ", log.LogSizeInBytes) + fmt.Println("start time: ", log.LogStartTime) + fmt.Println("end time: ", log.LogEndTime) + fmt.Println("download url: ", log.DownloadURL) + fmt.Println("download url expires: ", log.DownloadExpires) + } + } +} + +func TestClient_GetLogById(t *testing.T) { + args := &GetLogArgs{ + // 下载链接有效时间,单位为秒,可选,默认为1800秒 + ValidSeconds: 60, + } + logId := "scs-bj-mktaypucksot_8742_slowlog_202104160330" + log, err := client.GetLogById(instanceId, logId, args) + if err != nil { + fmt.Printf("get log detail of instance error: %+v\n", err) + return + } + fmt.Println("get log detail success.") + fmt.Println("+-------------------------------------------+") + fmt.Println("id: ", log.LogID) + fmt.Println("download url: ", log.DownloadURL) + fmt.Println("download url expires: ", log.DownloadExpires) +} + +func TestClient_GetMaintainTime(t *testing.T) { + resp, err := client.GetMaintainTime(instanceId) + if err != nil { + fmt.Printf("get maintainTime of instance error: %+v\n", err) + return + } + fmt.Println("get maintainTime success.") + fmt.Println("+-------------------------------------------+") + fmt.Println("start time: ", resp.MaintainTime.StartTime) + fmt.Println("dutation: ", resp.MaintainTime.Duration) + fmt.Println("period: ", resp.MaintainTime.Period) +} + +func TestClient_ModifyMaintainTime(t *testing.T) { + newMaintainTime := &MaintainTime{ + StartTime: "16:00", + Duration: 1, + Period: []int{1, 2, 3}, + } + err := client.ModifyMaintainTime(instanceId, newMaintainTime) + if err != nil { + fmt.Printf("modify maintainTime of instance error: %+v\n", err) + return + } + fmt.Println("modify maintainTime success.") +} + +func TestClient_GroupPreCheck(t *testing.T) { + args := &GroupPreCheckArgs{ + Leader: GroupLeader{ + LeaderRegion: "bj", + LeaderId: SCS_TEST_ID, + }, + Followers: []GroupFollower{ + { + FollowerId: "scs-bdbl-dzkqigawuhzy", + FollowerRegion: "bd", + }, + }, + } + result, err := client.GroupPreCheck(args) + if err != nil { + fmt.Printf("group pre check error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_CreateGroup(t *testing.T) { + args := &CreateGroupArgs{ + Leader: CreateGroupLeader{ + GroupName: "test_create", + LeaderRegion: "bj", + LeaderId: SCS_TEST_ID, + }, + } + result, err := client.CreateGroup(args) + if err != nil { + fmt.Printf("group create error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_GetGroupList(t *testing.T) { + args := &GetGroupListArgs{ + PageNo: 1, + PageSize: 10, + } + result, err := client.GetGroupList(args) + if err != nil { + fmt.Printf("get group list error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_GetGroupDetail(t *testing.T) { + result, err := client.GetGroupDetail("scs-group-vobpzinoqadm") + if err != nil { + fmt.Printf("get group detail error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_DeleteGroup(t *testing.T) { + err := client.DeleteGroup("scs-group-ekeveqhmekvd") + if err != nil { + fmt.Printf("delete group error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupAddFollower(t *testing.T) { + args := &FollowerInfo{ + FollowerId: "scs-bdbl-dzkqigawuhzy", + FollowerRegion: "bd", + SyncMaster: "sync", + } + err := client.GroupAddFollower("scs-group-ekeveqhmekvd", args) + if err != nil { + fmt.Printf("join group error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupRemoveFollower(t *testing.T) { + err := client.GroupRemoveFollower("scs-group-ekeveqhmekvd", SCS_TEST_ID) + if err != nil { + fmt.Printf("quit group error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateGroupName(t *testing.T) { + args := &GroupNameArgs{ + GroupName: "test_group", + } + err := client.UpdateGroupName("scs-group-nqkkmbdjlacx", args) + if err != nil { + fmt.Printf("update group name error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_SetAsLeader(t *testing.T) { + err := client.SetAsLeader("scs-group-nqkkmbdjlacx", SCS_TEST_ID) + if err != nil { + fmt.Printf("set as leader error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupForbidWrite(t *testing.T) { + args := &ForbidWriteArgs{ + ForbidWriteFlag: true, + } + err := client.GroupForbidWrite("scs-group-nqkkmbdjlacx", args) + if err != nil { + fmt.Printf("forbid write error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupSetQps(t *testing.T) { + args := &GroupSetQpsArgs{ + ClusterShowId: "scs-bj-bftgjzjxbmex", + QpsWrite: 10, + QpsRead: 20, + } + err := client.GroupSetQps("scs-group-nqkkmbdjlacx", args) + if err != nil { + fmt.Printf("group set qps error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupSyncStatus(t *testing.T) { + result, err := client.GroupSyncStatus("scs-group-szqbjupjjhpl") + if err != nil { + fmt.Printf("group sync status error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_GroupWhiteList(t *testing.T) { + result, err := client.GroupWhiteList("scs-group-szqbjupjjhpl") + if err != nil { + fmt.Printf("get group white list error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) + data, _ := json.Marshal(result) + fmt.Println(string(data)) +} + +func TestClient_GroupWhiteListAdd(t *testing.T) { + args := &GroupWhiteList{ + WhiteLists: []string{"127.0.0.1"}, + } + err := client.GroupWhiteListAdd("scs-group-szqbjupjjhpl", args) + if err != nil { + fmt.Printf("group white list add error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupWhiteListDelete(t *testing.T) { + args := &GroupWhiteList{ + WhiteLists: []string{"127.0.0.1"}, + } + err := client.GroupWhiteListDelete("scs-group-szqbjupjjhpl", args) + if err != nil { + fmt.Printf("group white list delete error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GroupStaleReadable(t *testing.T) { + args := &StaleReadableArgs{ + FollowerId: SCS_TEST_ID, + StaleReadable: true, + } + err := client.GroupStaleReadable("scs-group-szqbjupjjhpl", args) + if err != nil { + fmt.Printf("group stale readable error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateParamsTemplate(t *testing.T) { + args := &CreateTemplateArgs{ + EngineVersion: "5.0", + TemplateType: 1, + ClusterType: "master_slave", + Engine: "redis", + Name: "test_template", + Comment: "test template", + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, + } + result, err := client.CreateParamsTemplate(args) + if err != nil { + fmt.Printf("create params template error: %+v\n", err) + return + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamsTemplateList(t *testing.T) { + args := &Marker{ + Marker: "-1", + MaxKeys: 1000, + } + result, err := client.GetParamsTemplateList(args) + if err != nil { + fmt.Printf("get params template error: %+v\n", err) + return + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetParamsTemplateDetail(t *testing.T) { + + result, err := client.GetParamsTemplateDetail("scs-tmpl-kctbndsfdhya") + if err != nil { + fmt.Printf("get params template detail error: %+v\n", err) + return + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteParamsTemplate(t *testing.T) { + err := client.DeleteParamsTemplate("scs-tmpl-vxslemqppzuz") + if err != nil { + fmt.Printf("delete params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_RenameParamsTemplate(t *testing.T) { + args := &RenameTemplateArgs{ + Name: "scs-test-template", + } + err := client.RenameParamsTemplate("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("rename params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ApplyParamsTemplate(t *testing.T) { + args := &ApplyTemplateArgs{ + RebootType: 0, + Extra: "0", + CacheClusterShowIdItem: []CacheClusterShowId{ + { + CacheClusterShowId: SCS_TEST_ID, + Region: "bj", + }, + }, + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, + } + err := client.ApplyParamsTemplate("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("apply params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_TemplateAddParams(t *testing.T) { + args := &AddParamsArgs{ + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, + } + err := client.TemplateAddParams("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("add params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_TemplateModifyParams(t *testing.T) { + args := &ModifyParamsArgs{ + Parameters: []ParameterItem{ + { + ConfName: "disable_commands", + ConfModule: 1, + ConfValue: "flushall,flushdb", + ConfType: 3, + }, + }, + } + err := client.TemplateModifyParams("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("modify params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_TemplateDeleteParams(t *testing.T) { + args := &DeleteParamsArgs{ + Parameters: []string{"appendonly"}, + } + err := client.TemplateDeleteParams("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("delete params template error: %+v\n", err) + return + } + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetapplyTemplate(t *testing.T) { + args := &GetSystemTemplateArgs{ + Engine: "redis", + EngineVersion: "5.0", + ClusterType: "master_slave", + } + result, err := client.GetSystemTemplate(args) + if err != nil { + fmt.Printf("get system template error: %+v\n", err) + return + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetApplyRecords(t *testing.T) { + args := &Marker{ + Marker: "-1", + MaxKeys: 100, + } + result, err := client.GetApplyRecords("scs-tmpl-kctbndsfdhya", args) + if err != nil { + fmt.Printf("get apply record error: %+v\n", err) + return + } + data, _ := json.Marshal(result) + fmt.Println(string(data)) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/scs/model.go b/bce-sdk-go/services/scs/model.go new file mode 100644 index 0000000..4cde1eb --- /dev/null +++ b/bce-sdk-go/services/scs/model.go @@ -0,0 +1,786 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package scs + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type Billing struct { + PaymentTiming string `json:"paymentTiming"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +type Subnet struct { + ZoneName string `json:"zoneName"` + SubnetID string `json:"subnetId"` +} + +type CreateInstanceArgs struct { + Billing Billing `json:"billing"` + PurchaseCount int `json:"purchaseCount"` + InstanceName string `json:"instanceName"` + NodeType string `json:"nodeType"` + ShardNum int `json:"shardNum"` + ProxyNum int `json:"proxyNum"` + ClusterType string `json:"clusterType"` + ReplicationNum int `json:"replicationNum"` + ReplicationInfo []Replication `json:"replicationInfo"` + Port int `json:"port"` + Engine int `json:"engine,omitempty"` + EngineVersion string `json:"engineVersion"` + DiskFlavor int `json:"diskFlavor,omitempty"` + DiskType string `json:"diskType,omitempty"` + VpcID string `json:"vpcId"` + Subnets []Subnet `json:"subnets,omitempty"` + AutoRenewTimeUnit string `json:"autoRenewTimeUnit,omitempty"` + AutoRenewTime int `json:"autoRenewTime,omitempty"` + BgwGroupId string `json:"bgwGroupId,omitempty"` + ClientToken string `json:"-"` + ClientAuth string `json:"clientAuth"` + StoreType int `json:"storeType"` + EnableReadOnly int `json:"enableReadOnly,omitempty"` + Tags []model.TagModel `json:"tags"` +} + +type CreateInstanceResult struct { + InstanceIds []string `json:"instanceIds"` +} + +type InstanceModel struct { + InstanceID string `json:"instanceId"` + InstanceName string `json:"instanceName"` + InstanceStatus string `json:"instanceStatus"` + InstanceExpireTime string `json:"instanceExpireTime"` + ShardNum int `json:"shardNum"` + ReplicationNum int `json:"replicationNum"` + ClusterType string `json:"clusterType"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + VnetIP string `json:"vnetIp"` + Domain string `json:"domain"` + Port int `json:"port"` + InstanceCreateTime string `json:"instanceCreateTime"` + Capacity float64 `json:"capacity"` + UsedCapacity float64 `json:"usedCapacity"` + PaymentTiming string `json:"paymentTiming"` + ZoneNames []string `json:"zoneNames"` + Tags []model.TagModel `json:"tags"` +} + +type ListInstancesArgs struct { + Marker string + MaxKeys int +} + +type ListInstancesResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Instances []InstanceModel `json:"instances"` +} + +type ResizeInstanceArgs struct { + NodeType string `json:"nodeType"` + ShardNum int `json:"shardNum"` + IsDefer bool `json:"isDefer"` + ClientToken string `json:"-"` + DiskFlavor int `json:"diskFlavor"` + DiskType string `json:"diskType"` +} + +type ReplicationArgs struct { + ResizeType string `json:"resizeType"` + ReplicationInfo []Replication `json:"replicationInfo"` + ClientToken string `json:"-"` +} + +type Replication struct { + AvailabilityZone string `json:"availabilityZone"` + SubnetId string `json:"subnetId"` + IsMaster int `json:"isMaster"` +} +type RestartInstanceArgs struct { + IsDefer bool `json:"isDefer"` +} + +type GetInstanceDetailResult struct { + InstanceID string `json:"instanceId"` + InstanceName string `json:"instanceName"` + InstanceStatus string `json:"instanceStatus"` + ClusterType string `json:"clusterType"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + VnetIP string `json:"vnetIp"` + Domain string `json:"domain"` + Port int `json:"port"` + InstanceCreateTime string `json:"instanceCreateTime"` + InstanceExpireTime string `json:"instanceExpireTime"` + Capacity float64 `json:"capacity"` + UsedCapacity float64 `json:"usedCapacity"` + PaymentTiming string `json:"paymentTiming"` + VpcID string `json:"vpcId"` + ZoneNames []string `json:"zoneNames"` + Subnets []Subnet `json:"subnets"` + AutoRenew string `json:"autoRenew"` + Tags []model.TagModel `json:"tags"` + ShardNum int `json:"shardNum"` + ReplicationNum int `json:"replicationNum"` + NodeType string `json:"nodeType"` + DiskFlavor int `json:"diskFlavor"` + DiskType string `json:"diskType"` + StoreType int `json:"storeType"` + Eip string `json:"eip"` + PublicDomain string `json:"publicDomain"` + EnableReadOnly int `json:"enableReadOnly"` + ReplicationInfo []Replication `json:"replicationInfo"` + BnsGroup string `json:"bnsGroup"` +} + +type UpdateInstanceNameArgs struct { + InstanceName string `json:"instanceName"` + ClientToken string `json:"-"` +} + +type NodeType struct { + InstanceFlavor int `json:"instanceFlavor"` + NodeType string `json:"nodeType"` + CPUNum int `json:"cpuNum"` + NetworkThroughputInGbps float64 `json:"networkThroughputInGbps"` + PeakQPS int `json:"peakQps"` + MaxConnections int `json:"maxConnections"` + AllowedNodeNumList []int `json:"allowedNodeNumList"` +} + +type GetNodeTypeListResult struct { + ClusterNodeTypeList []NodeType `json:"clusterNodeTypeList"` + DefaultNodeTypeList []NodeType `json:"defaultNodeTypeList"` + HsdbNodeTypeList []NodeType `json:"hsdbNodeTypeList"` +} + +type ListSubnetsArgs struct { + VpcID string `json:"vpcId"` + ZoneName string `json:"zoneName"` +} + +type ListSubnetsResult struct { + SubnetOriginals []SubnetOriginal `json:"subnets"` +} + +type SubnetOriginal struct { + Name string `json:"name"` + SubnetID string `json:"subnetId"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + VpcID string `json:"vpcId"` +} + +type UpdateInstanceDomainNameArgs struct { + Domain string `json:"domain"` + ClientToken string `json:"-"` +} + +type GetZoneListResult struct { + Zones []ZoneNames `json:"zones"` +} + +type ZoneNames struct { + ZoneNames []string `json:"zoneNames"` +} + +type FlushInstanceArgs struct { + Password string `json:"password"` + ClientToken string `json:"-"` +} + +type BindingTagArgs struct { + ChangeTags []model.TagModel `json:"changeTags"` +} + +type GetSecurityIpResult struct { + SecurityIps []string `json:"securityIps"` +} + +type SecurityIpArgs struct { + SecurityIps []string `json:"securityIps"` + ClientToken string `json:"-"` +} + +type ModifyPasswordArgs struct { + Password string `json:"password"` + ClientToken string `json:"-"` +} + +type GetParametersResult struct { + Parameters []Parameter `json:"parameters"` +} + +type Parameter struct { + Default string `json:"default"` + ForceRestart string `json:"forceRestart"` + Name string `json:"name"` + Value string `json:"value"` +} + +type ModifyParametersArgs struct { + Parameter InstanceParam `json:"parameter"` + ClientToken string `json:"-"` +} + +type InstanceParam struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type GetBackupListResult struct { + TotalCount string `json:"totalCount"` + Backups []BackupInfo `json:"backups"` +} + +type BackupInfo struct { + BackupType string `json:"backupType"` + Comment string `json:"comment"` + StartTime string `json:"startTime"` + Records []BackupRecord `json:"records"` +} + +type BackupRecord struct { + BackupRecordId string `json:"backupRecordId"` + BackupStatus string `json:"backupStatus"` + Duration string `json:"duration"` + ObjectSize string `json:"objectSize"` + ShardName string `json:"shardName"` + StartTime string `json:"startTime"` +} + +type ModifyBackupPolicyArgs struct { + BackupDays string `json:"backupDays"` + BackupTime string `json:"backupTime"` + ClientToken string `json:"clientToken"` + ExpireDay int `json:"expireDay"` +} + +type ListVpcSecurityGroupsResult struct { + Groups []SecurityGroup `json:"groups"` +} + +type SecurityGroup struct { + Name string `json:"name"` + SecurityGroupID string `json:"securityGroupId"` + Description string `json:"description"` + TenantID string `json:"tenantId"` + AssociateNum int `json:"associateNum"` + VpcID string `json:"vpcId"` + VpcShortID string `json:"vpcShortId"` + VpcName string `json:"vpcName"` + CreatedTime string `json:"createdTime"` + Version int `json:"version"` + DefaultSecurityGroup int `json:"defaultSecurityGroup"` +} + +type SecurityGroupArgs struct { + InstanceIds []string `json:"instanceIds"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type UnbindSecurityGroupArgs struct { + InstanceId string `json:"instanceId"` + SecurityGroupIds []string `json:"securityGroupIds"` +} + +type ListSecurityGroupResult struct { + Groups []SecurityGroupDetail `json:"groups"` +} + +type SecurityGroupRule struct { + PortRange string `json:"portRange"` + Protocol string `json:"protocol"` + RemoteGroupID string `json:"remoteGroupId"` + RemoteIP string `json:"remoteIP"` + Ethertype string `json:"ethertype"` + TenantID string `json:"tenantId"` + Name string `json:"name"` + ID string `json:"id"` + SecurityGroupRuleID string `json:"securityGroupRuleId"` + Direction string `json:"direction"` +} + +type SecurityGroupDetail struct { + SecurityGroupName string `json:"securityGroupName"` + SecurityGroupID string `json:"securityGroupId"` + SecurityGroupRemark string `json:"securityGroupRemark"` + Inbound []SecurityGroupRule `json:"inbound"` + Outbound []SecurityGroupRule `json:"outbound"` + VpcName string `json:"vpcName"` + VpcID string `json:"vpcId"` + ProjectID string `json:"projectId"` +} + +type RequestBuilder struct { +} + +type GetPriceRequest struct { + Engine string `json:"engine,omitempty"` + ShardNum int `json:"shardNum,omitempty"` + Period int `json:"period,omitempty"` + ChargeType string `json:"chargeType,omitempty"` + NodeType string `json:"nodeType,omitempty"` + ReplicationNum int `json:"replicationNum,omitempty"` + ClusterType string `json:"clusterType,omitempty"` +} + +type GetPriceResult struct { + Price float64 `json:"price,omitempty"` +} + +type Marker struct { + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ListResultWithMarker struct { + IsTruncated bool `json:"isTruncated"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` +} + +type RecycleInstance struct { + InstanceID string `json:"cacheClusterShowId"` + InstanceName string `json:"instanceName"` + InstanceStatus string `json:"instanceStatus"` + IsolatedStatus string `json:"isolatedStatus"` + ClusterType string `json:"clusterType"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + VnetIP string `json:"vnetIp"` + Domain string `json:"domain"` + Port string `json:"port"` + InstanceCreateTime string `json:"instanceCreateTime"` + Capacity float64 `json:"capacity"` + UsedCapacity float64 `json:"usedCapacity"` + PaymentTiming string `json:"paymentTiming"` + ZoneNames []string `json:"zoneNames"` + Tags []model.TagModel `json:"tags"` +} + +type RecyclerInstanceList struct { + ListResultWithMarker + Result []RecycleInstance `json:"result"` +} + +type BatchInstanceIds struct { + InstanceIds []string `json:"cacheClusterShowIds,omitempty"` +} + +type RenewInstanceArgs struct { + Duration int `json:"duration,omitempty"` + InstanceIds []string `json:"instanceIds,omitempty"` +} + +type OrderIdResult struct { + OrderId string `json:"orderId"` +} + +type ListLogArgs struct { + FileType string `json:"fileType"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type ListLogResult struct { + LogList []ShardLog `json:"logList"` +} +type LogItem struct { + LogStartTime string `json:"logStartTime"` + LogEndTime string `json:"logEndTime"` + DownloadURL string `json:"downloadUrl"` + LogID string `json:"logId"` + LogSizeInBytes int `json:"logSizeInBytes"` + DownloadExpires string `json:"downloadExpires"` +} +type ShardLog struct { + ShardShowID string `json:"shardShowId"` + TotalNum int `json:"totalNum"` + ShardID int `json:"shardId"` + LogItem []LogItem `json:"logItem"` +} + +type GetLogArgs struct { + ValidSeconds int `json:"validSeconds"` +} +type GetMaintainTimeResult struct { + CacheClusterShowId string `json:"cacheClusterShowId"` + MaintainTime MaintainTime `json:"maintainTime"` +} +type MaintainTime struct { + StartTime string `json:"startTime"` + Duration int `json:"duration"` + Period []int `json:"period"` +} + +type CreatePriceArgs struct { + Engine int `json:"engine,omitempty"` + ClusterType string `json:"clusterType,omitempty"` + NodeType string `json:"nodeType,omitempty"` + ShardNum int `json:"shardNum,omitempty"` + ReplicationNum int `json:"replicationNum,omitempty"` + InstanceNum int `json:"instanceNum,omitempty"` + DiskType int `json:"diskType,omitempty"` + DiskFlavor int `json:"diskFlavor,omitempty"` + ChargeType string `json:"chargeType,omitempty"` + Period int `json:"period,omitempty"` +} +type CreatePriceResult struct { + Price float64 `json:"price,omitempty"` +} + +type ResizePriceArgs struct { + NodeType string `json:"nodeType"` + ShardNum int `json:"shardNum,omitempty"` + ReplicationNum int `json:"replicationNum,omitempty"` + DiskFlavor int `json:"diskFlavor,omitempty"` + ChargeType string `json:"chargeType,omitempty"` + Period int `json:"period,omitempty"` + ChangeType string `json:"changeType,omitempty"` +} +type ResizePriceResult struct { + Price float64 `json:"price,omitempty"` +} + +type SetAsSlaveArgs struct { + MasterDomain string `json:"masterDomain"` + MasterPort int `json:"masterPort"` +} + +type RenameDomainArgs struct { + Domain string `json:"domain"` + ClientToken string `json:"clientToken,omitempty"` +} + +type SwapDomainArgs struct { + SourceInstanceId string `json:"sourceInstanceId"` + TargetInstanceId string `json:"targetInstanceId"` + ClientToken string `json:"clientToken,omitempty"` +} + +type GetBackupDetailResult struct { + Url string `json:"url"` + UrlExpiration string `json:"urlExpiration"` +} + +type GroupPreCheckArgs struct { + Leader GroupLeader `json:"leader"` + Followers []GroupFollower `json:"followers"` +} +type GroupLeader struct { + LeaderRegion string `json:"leaderRegion"` + LeaderId string `json:"leaderId"` +} +type GroupFollower struct { + FollowerId string `json:"followerId"` + FollowerRegion string `json:"followerRegion"` +} +type GroupPreCheckResult struct { + LeaderResult GroupLeaderResult `json:"leaderResult"` + FollowerResult []GroupFollowerResult `json:"followerResult"` + ConnectionResults []GroupConnectionResult `json:"connectionResults"` +} +type GroupLeaderResult struct { + Version bool `json:"version"` + ClusterStatus bool `json:"clusterStatus"` + ReplicationNum bool `json:"replicationNum"` + Flavor bool `json:"flavor"` + Joined bool `json:"joined"` + NoPasswd bool `json:"noPasswd"` + NoSecurityGroup bool `json:"noSecurityGroup"` + IsHitX1 bool `json:"isHitX1"` +} +type GroupFollowerResult struct { + FollowerId string `json:"followerId"` + NoData bool `json:"noData"` + Version bool `json:"version"` + EngineVersion bool `json:"engineVersion"` + ClusterStatus bool `json:"clusterStatus"` + ShardNum bool `json:"shardNum"` + ReplicationNum bool `json:"replicationNum"` + Flavor bool `json:"flavor"` + Joined bool `json:"joined"` + NoPasswd bool `json:"noPasswd"` + NoSecurityGroup bool `json:"noSecurityGroup"` + IsHitX1 bool `json:"isHitX1"` +} +type GroupConnectionResult struct { + SourceId string `json:"sourceId"` + SourceRole string `json:"sourceRole"` + TargetId string `json:"targetId"` + TargetRole string `json:"targetRole"` + Connectable bool `json:"connectable"` +} + +type CreateGroupArgs struct { + Leader CreateGroupLeader `json:"leader"` +} +type CreateGroupLeader struct { + GroupName string `json:"groupName"` + LeaderRegion string `json:"leaderRegion"` + LeaderId string `json:"leaderId"` +} +type CreateGroupResult struct { + GroupId string `json:"groupId"` +} +type GroupListResult struct { + TotalCount int `json:"totalCount"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + Result []GroupResult `json:"result"` +} +type GroupResult struct { + GroupId string `json:"groupId"` + GroupName string `json:"groupName"` + GroupStatus string `json:"groupStatus"` + ClusterNum int `json:"clusterNum"` + GroupCreateTime string `json:"groupCreateTime"` + ForbidWrite int `json:"forbidWrite"` + GroupType string `json:"groupType"` + LeaderName string `json:"leaderName"` + LeaderShowId string `json:"leaderShowId"` + LeaderRegion string `json:"leaderRegion"` +} +type GetGroupListArgs struct { + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` +} + +type GroupDetailResult struct { + GroupId string `json:"groupId"` + GroupName string `json:"groupName"` + GroupStatus string `json:"groupStatus"` + ClusterNum int `json:"clusterNum"` + GroupCreateTime string `json:"groupCreateTime"` + ForbidWrite int `json:"forbidWrite"` + GroupType string `json:"groupType"` + Leader GroupLeaderInfo `json:"leader"` + Followers []GroupFollowerInfo `json:"followers"` +} +type GroupLeaderInfo struct { + ClusterName string `json:"clusterName"` + ClusterShowId string `json:"clusterShowId"` + Region string `json:"region"` + Status string `json:"status"` + TotalCapacityInGB float64 `json:"totalCapacityInGB"` + UsedCapacityInGB int `json:"usedCapacityInGB"` + ShardNum int `json:"shardNum"` + Flavor int `json:"flavor"` + QpsWrite int64 `json:"qpsWrite"` + QpsRead int64 `json:"qpsRead"` + StableReadable bool `json:"stableReadable"` + ForbidWrite int `json:"forbidWrite"` + AvailabilityZone string `json:"availabilityZone"` + ExpiredTime string `json:"expiredTime"` +} +type GroupFollowerInfo struct { + ClusterName string `json:"clusterName"` + ClusterShowId string `json:"clusterShowId"` + Region string `json:"region"` + Status string `json:"status"` + TotalCapacityInGB float64 `json:"totalCapacityInGB"` + UsedCapacityInGB int `json:"usedCapacityInGB"` + ShardNum int `json:"shardNum"` + Flavor int `json:"flavor"` + QpsWrite int64 `json:"qpsWrite"` + QpsRead int64 `json:"qpsRead"` + StableReadable bool `json:"stableReadable"` + ForbidWrite int `json:"forbidWrite"` + AvailabilityZone string `json:"availabilityZone"` + ExpiredTime string `json:"expiredTime"` +} + +type FollowerInfo struct { + FollowerId string `json:"followerId"` + FollowerRegion string `json:"followerRegion"` + SyncMaster string `json:"syncMaster"` +} + +type GroupNameArgs struct { + GroupName string `json:"groupName"` +} + +type ForbidWriteArgs struct { + ForbidWriteFlag bool `json:"forbidWriteFlag"` +} + +type GroupSetQpsArgs struct { + ClusterShowId string `json:"clusterShowId"` + QpsWrite int `json:"qpsWrite"` + QpsRead int `json:"qpsRead"` +} + +type GroupSyncStatusResult struct { + Followers []FollowerSyncInfo `json:"followers"` +} + +type FollowerSyncInfo struct { + ClusterShowId string `json:"clusterShowId"` + SyncStatus string `json:"syncStatus"` + MaxOffset int `json:"maxOffset"` + Lag int `json:"lag"` +} + +type GroupWhiteList struct { + WhiteLists []string `json:"whiteLists"` +} + +type StaleReadableArgs struct { + FollowerId string `json:"followerId"` + StaleReadable bool `json:"staleReadable"` +} + +type CreateTemplateArgs struct { + EngineVersion string `json:"engineVersion"` + TemplateType int `json:"templateType"` + ClusterType string `json:"clusterType"` + Engine string `json:"engine"` + Name string `json:"name"` + Comment string `json:"comment"` + Parameters []ParameterItem `json:"parameters"` +} +type ParameterItem struct { + ConfName string `json:"confName"` + ConfModule int `json:"confModule"` + ConfValue string `json:"confValue"` + ConfType int `json:"confType"` +} +type CreateParamsTemplateResult struct { + TemplateId int `json:"templateId"` + TemplateShowId string `json:"templateShowId"` +} + +type ParamsTemplateListResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []ResultItem `json:"result"` +} +type ResultItem struct { + EngineVersion string `json:"engineVersion"` + TemplateType int `json:"templateType"` + ClusterType string `json:"clusterType"` + NeedReboot int `json:"needReboot"` + TemplateShowId string `json:"templateShowId"` + UpdateTime string `json:"updateTime"` + TemplateId int `json:"templateId"` + ParameterNum int `json:"parameterNum"` + TemplateName string `json:"templateName"` + Engine string `json:"engine"` + CreateTime string `json:"createTime"` + Comment string `json:"comment"` + Parameters []ParamItem `json:"parameters"` +} + +type ParamItem struct { + ConfName string `json:"confName"` + ConfModule int `json:"confModule"` + ConfCacheVersion int `json:"confCacheVersion"` + ConfValue string `json:"confValue"` + NeedReboot int `json:"needReboot"` + ConfRedisVersion string `json:"confRedisVersion"` + ConfDefault string `json:"confDefault"` + ConfType int `json:"confType"` + ConfRange string `json:"confRange"` + ConfDesc string `json:"confDesc"` + ConfUserVisible int `json:"confUserVisible"` +} + +type RenameTemplateArgs struct { + Name string `json:"name"` +} + +type ApplyTemplateArgs struct { + RebootType int `json:"rebootType"` + Extra string `json:"extra"` + CacheClusterShowIdItem []CacheClusterShowId `json:"cacheClusterShowId"` + Parameters []ParameterItem `json:"parameters"` +} +type CacheClusterShowId struct { + CacheClusterShowId string `json:"cacheClusterShowId"` + Region string `json:"region"` +} + +type AddParamsArgs struct { + Parameters []ParameterItem `json:"parameters"` +} + +type ModifyParamsArgs struct { + Parameters []ParameterItem `json:"parameters"` +} + +type DeleteParamsArgs struct { + Parameters []string `json:"parameters"` +} + +type GetSystemTemplateArgs struct { + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + ClusterType string `json:"clusterType"` +} + +type SystemTemplateResult struct { + Success bool `json:"success"` + Result []SystemTemplate `json:"result"` +} + +type SystemTemplate struct { + ConfName string `json:"confName"` + ConfDefault string `json:"confDefault"` + ConfValue string `json:"confValue"` + ConfType int `json:"confType"` + ConfRange string `json:"confRange"` + ConfModule int `json:"confModule"` + ConfDesc string `json:"confDesc"` + NeedReboot int `json:"needReboot"` + ConfRedisVersion string `json:"confRedisVersion"` + ConfCacheVersion int `json:"confCacheVersion"` +} + +type GetApplyRecordsResult struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + NextMarker string `json:"nextMarker"` + IsTruncated bool `json:"isTruncated"` + Result []ApplyRecord `json:"result"` +} + +type ApplyRecord struct { + CacheClusterShowId string `json:"cacheClusterShowId"` + CacheClusterName string `json:"cacheClusterName"` + AvailabilityZone string `json:"availabilityZone"` + Version int `json:"version"` + Status string `json:"status"` + Engine string `json:"engine"` + EngineVersion string `json:"engineVersion"` + ClusterType string `json:"clusterType"` + CreateTime string `json:"createTime"` + ApplyTime string `json:"applyTime"` +} diff --git a/bce-sdk-go/services/scs/scs.go b/bce-sdk-go/services/scs/scs.go new file mode 100644 index 0000000..367885b --- /dev/null +++ b/bce-sdk-go/services/scs/scs.go @@ -0,0 +1,1632 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// scs.go - the SCS for Redis APIs definition supported by the redis service +package scs + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + KEY_MARKER = "marker" + KEY_MAX_KEYS = "maxKeys" + INSTANCE_URL_V1 = bce.URI_PREFIX + "v1" + "/instance" + INSTANCE_URL_V2 = bce.URI_PREFIX + "v2" + "/instance" + URI_PREFIX_V2 = bce.URI_PREFIX + "v2" + URI_PREFIX_V1 = bce.URI_PREFIX + "v1" + REQUEST_SECURITYGROUP_URL = "/security" + REQUEST_RECYCLER_URL = "/recycler" +) + +func (c *Client) request(method, url string, result, body interface{}) (interface{}, error) { + var err error + if result != nil { + err = bce.NewRequestBuilder(c). + WithMethod(method). + WithURL(url). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(body). + WithResult(result). + Do() + } else { + err = bce.NewRequestBuilder(c). + WithMethod(method). + WithURL(url). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(body). + Do() + } + // fmt.Println(Json(result)) + return result, err +} + +func getInstanceUrlWithId(instanceId string) string { + return INSTANCE_URL_V1 + "/" + instanceId +} + +// List Security Group By Vpc URL +func getSecurityGroupWithVpcIdUrl(vpcId string) string { + return URI_PREFIX_V2 + REQUEST_SECURITYGROUP_URL + "/vpc/" + vpcId +} + +// List Security Group By Instance URL +func getSecurityGroupWithInstanceIdUrl(instanceId string) string { + return URI_PREFIX_V2 + REQUEST_SECURITYGROUP_URL + "/instance/" + instanceId +} + +// Bind Security Group To Instance URL +func getBindSecurityGroupWithUrl() string { + return URI_PREFIX_V2 + REQUEST_SECURITYGROUP_URL + "/bind" +} + +// UnBind Security Group To Instance URL +func getUnBindSecurityGroupWithUrl() string { + return URI_PREFIX_V2 + REQUEST_SECURITYGROUP_URL + "/unbind" +} + +// Batch Replace Security Group URL +func getReplaceSecurityGroupWithUrl() string { + return URI_PREFIX_V2 + REQUEST_SECURITYGROUP_URL + "/update" +} + +// Recycler URL +func getRecyclerUrl() string { + return URI_PREFIX_V2 + REQUEST_RECYCLER_URL + "/list" +} + +// Recycler Recover URL +func getRecyclerRecoverUrl() string { + return URI_PREFIX_V2 + REQUEST_RECYCLER_URL + "/recover" +} + +// Recycler Recover URL +func getRecyclerDeleteUrl() string { + return URI_PREFIX_V2 + REQUEST_RECYCLER_URL + "/delete" +} + +// Renew Instance URL +func getRenewUrl() string { + return INSTANCE_URL_V2 + "/renew" +} + +func getLogsUrlWithInstanceId(instanceId string) string { + return INSTANCE_URL_V1 + "/" + instanceId + "/log" +} + +func getLogsUrlWithLogId(instanceId, logId string) string { + return INSTANCE_URL_V1 + "/" + instanceId + "/log/" + logId +} + +func getGroupUrl() string { + return "/v2/group" +} +func getTemplateUrl() string { + return "/v2/template" +} + +func Json(v interface{}) string { + jsonStr, err := json.Marshal(v) + if err != nil { + panic("convert to json faild") + } + return string(jsonStr) +} + +// Convert marker to request params +func getMarkerParams(marker *Marker) map[string]string { + if marker == nil { + marker = &Marker{Marker: "-1"} + } + params := make(map[string]string, 2) + params[KEY_MARKER] = marker.Marker + if marker.MaxKeys > 0 { + params[KEY_MAX_KEYS] = strconv.Itoa(marker.MaxKeys) + } + return params +} + +// Convert struct to request params +func getQueryParams(val interface{}) (map[string]string, error) { + var params map[string]string + if val != nil { + err := json.Unmarshal([]byte(Json(val)), ¶ms) + if err != nil { + return nil, err + } + } + return params, nil +} + +// CreateInstance - create an instance with specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to create instance +// +// RETURNS: +// - *CreateInstanceResult: result of the instance ids newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateInstance(args *CreateInstanceArgs) (*CreateInstanceResult, error) { + if args == nil { + return nil, fmt.Errorf("please set create scs argments") + } + if len(args.ClientAuth) != 0 { + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.ClientAuth) + if err != nil { + return nil, err + } + args.ClientAuth = cryptedPass + } + result := &CreateInstanceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(INSTANCE_URL_V2). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// ListInstances - list all instances with the specified parameters +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list instances +// +// RETURNS: +// - *ListInstanceResult: result of the instance list +// - error: nil if success otherwise the specific error +func (c *Client) ListInstances(args *ListInstancesArgs) (*ListInstancesResult, error) { + if args == nil { + args = &ListInstancesArgs{} + } + + if args.MaxKeys <= 0 || args.MaxKeys > 1000 { + args.MaxKeys = 1000 + } + + result := &ListInstancesResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V2). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetInstanceDetail - get details of the specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetInstanceDetailResult: result of the instance details +// - error: nil if success otherwise the specific error +func (c *Client) GetInstanceDetail(instanceId string) (*GetInstanceDetailResult, error) { + result := &GetInstanceDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V2 + "/" + instanceId). + WithResult(result). + Do() + + return result, err +} + +// ResizeInstance - resize a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be resized +// - reqBody: the request body to resize instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeInstance(instanceId string, args *ResizeInstanceArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/change"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// GetCreatePrice - get create price +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - reqBody: the request body to get price +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetCreatePriceResult: result of the create price +func (c *Client) GetCreatePrice(args *CreatePriceArgs) (*CreatePriceResult, error) { + result := &CreatePriceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL("/v2/price"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// GetResizePrice - get resize price +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be resized +// - reqBody: the request body to get resize price +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetResizePriceResult: result of the resize price +func (c *Client) GetResizePrice(instanceId string, args *ResizePriceArgs) (*ResizePriceResult, error) { + result := &ResizePriceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(INSTANCE_URL_V2+"/"+instanceId+"/price"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// AddReplication - add replications +// +// PARAMS: +// - instanceId: id of the instance to be resized +// - args: replicationInfo +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AddReplication(instanceId string, args *ReplicationArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(INSTANCE_URL_V2+"/"+instanceId+"/resizeReplication"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteReplication - delete replications +// +// PARAMS: +// - instanceId: id of the instance to be resized +// - args: replicationInfo +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteReplication(instanceId string, args *ReplicationArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(INSTANCE_URL_V2+"/"+instanceId+"/resizeReplication"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// RestartInstance - restart a specified instance +// +// PARAMS: +// - instanceId: id of the instance to be resized +// - args: specify restart immediately or postpone restart to time window +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RestartInstance(instanceId string, args *RestartInstanceArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceUrlWithId(instanceId)+"/restart"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() +} + +// DeleteInstance - delete a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteInstance(instanceId string, clientToken string) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(INSTANCE_URL_V1+"/"+instanceId). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// UpdateInstanceName - update name of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// - args: the arguments to Update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceName(instanceId string, args *UpdateInstanceNameArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/rename"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// GetNodeTypeList - list all nodetype +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance to be deleted +// - args: the arguments to Update instanceName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GetNodeTypeList() (*GetNodeTypeListResult, error) { + getNodeTypeListResult := &GetNodeTypeListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v2/nodetypes"). + WithResult(getNodeTypeListResult). + Do() + + return getNodeTypeListResult, err +} + +// ListsSubnet - list all Subnets +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to list all subnets, not necessary +// +// RETURNS: +// - *ListSubnetsResult: result of the subnet list +// - error: nil if success otherwise the specific error +func (c *Client) ListSubnets(args *ListSubnetsArgs) (*ListSubnetsResult, error) { + if args == nil { + args = &ListSubnetsArgs{} + } + + result := &ListSubnetsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v1/subnet"). + WithQueryParamFilter("vpcId", args.VpcID). + WithQueryParamFilter("zoneName", args.ZoneName). + WithResult(result). + Do() + + return result, err +} + +// UpdateInstanceDomainName - update name of a specified instance domain +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to update domainName +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateInstanceDomainName(instanceId string, args *UpdateInstanceDomainNameArgs) error { + + if args == nil || args.Domain == "" { + return fmt.Errorf("unset Domain") + } + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/renameDomain"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// GetZoneList - list all zone +// +// PARAMS: +// - cli: the client agent which can perform sending request +// +// RETURNS: +// - *GetZoneListResult: result of the zone list +// - error: nil if success otherwise the specific error +func (c *Client) GetZoneList() (*GetZoneListResult, error) { + result := &GetZoneListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL("/v1/zone"). + WithResult(result). + Do() + + return result, err +} + +// FlushInstance - flush a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to flush instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) FlushInstance(instanceId string, args *FlushInstanceArgs) error { + + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.Password) + if err != nil { + return err + } + args.Password = cryptedPass + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/flush"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// BindingTags - bind tags to a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to bind tags to instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindingTag(instanceId string, args *BindingTagArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/bindTag"). + WithBody(args). + Do() +} + +// UnBindingTags - unbind tags to a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to unbind tags to instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindingTag(instanceId string, args *BindingTagArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/unBindTag"). + WithBody(args). + Do() +} + +// SetAsMaster - set instance as master +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SetAsMaster(instanceId string) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V2 + "/" + instanceId + "/setAsMaster"). + Do() +} + +// SetAsSlave - set instance as master +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to set instance as slave +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SetAsSlave(instanceId string, args *SetAsSlaveArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithBody(args). + WithURL(INSTANCE_URL_V2 + "/" + instanceId + "/setAsSlave"). + Do() +} + +// GetSecurityIp - list all securityIps +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *ListSecurityIp: result of the security IP list +// - error: nil if success otherwise the specific error +func (c *Client) GetSecurityIp(instanceId string) (*GetSecurityIpResult, error) { + + result := &GetSecurityIpResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/securityIp"). + WithResult(result). + Do() + + return result, err +} + +// AddSecurityIp - add securityIp to access a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to add securityIp +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AddSecurityIp(instanceId string, args *SecurityIpArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/securityIp"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// DeleteSecurityIp - delete securityIp to access a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to delete securityIp +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSecurityIp(instanceId string, args *SecurityIpArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/securityIp"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// ModifyPassword - modify the password of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to Modify Password +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyPassword(instanceId string, args *ModifyPasswordArgs) error { + + cryptedPass, err := Aes128EncryptUseSecreteKey(c.Config.Credentials.SecretAccessKey, args.Password) + if err != nil { + return err + } + args.Password = cryptedPass + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/modifyPassword"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// RenameDomain - rename domain +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to rename domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameDomain(instanceId string, args *RenameDomainArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V2+"/"+instanceId+"/renameDomain"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// SwapDomain - swap domain +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to swap domain +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SwapDomain(instanceId string, args *SwapDomainArgs) error { + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(INSTANCE_URL_V2+"/"+instanceId+"/swapDomain"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// GetParameters - query the configuration parameters and running parameters of redis instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetParameterResult: result of the parameters +// - error: nil if success otherwise the specific error +func (c *Client) GetParameters(instanceId string) (*GetParametersResult, error) { + + result := &GetParametersResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/parameter"). + WithResult(result). + Do() + + return result, err +} + +// ModifyParameters - modify the parameters of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to modify parameters +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyParameters(instanceId string, args *ModifyParametersArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/parameter"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// GetBackupList - get backup list of the instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - *GetBackupListResult: result of the backup list +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupList(instanceId string) (*GetBackupListResult, error) { + + result := &GetBackupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/backup"). + WithResult(result). + Do() + + return result, err +} + +// GetBackupDetail - get backup detail of the instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - backupRecordId: the backup record id +// +// RETURNS: +// - *GetBackupDetailResult: result of the backup detail +// - error: nil if success otherwise the specific error +func (c *Client) GetBackupDetail(instanceId, backupRecordId string) (*GetBackupDetailResult, error) { + + result := &GetBackupDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(INSTANCE_URL_V1 + "/" + instanceId + "/backup/" + backupRecordId). + WithResult(result). + Do() + + return result, err +} + +// ModifyBackupPolicy - modify the BackupPolicy of a specified instance +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// - args: the arguments to Modify BackupPolicy +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyBackupPolicy(instanceId string, args *ModifyBackupPolicyArgs) error { + + return bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(INSTANCE_URL_V1+"/"+instanceId+"/modifyBackupPolicy"). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// ListSecurityGroupByVpcId - list security groups by vpc id +// +// PARAMS: +// - vpcId: id of vpc +// +// RETURNS: +// - *[]SecurityGroup:security groups of vpc +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByVpcId(vpcId string) (*ListVpcSecurityGroupsResult, error) { + if len(vpcId) < 1 { + return nil, fmt.Errorf("unset vpcId") + } + result := &ListVpcSecurityGroupsResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithVpcIdUrl(vpcId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// ListSecurityGroupByInstanceId - list security groups by instance id +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *ListSecurityGroupResult: list secrity groups result of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListSecurityGroupByInstanceId(instanceId string) (*ListSecurityGroupResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + result := &ListSecurityGroupResult{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getSecurityGroupWithInstanceIdUrl(instanceId)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// BindSecurityGroups - bind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// UnBindSecurityGroups - unbind SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindSecurityGroups(args *UnbindSecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceId) < 1 { + return fmt.Errorf("unset instanceId") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getUnBindSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ReplaceSecurityGroups - replace SecurityGroup to instances +// +// PARAMS: +// - args: http request body +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ReplaceSecurityGroups(args *SecurityGroupArgs) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(args.InstanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(args.SecurityGroupIds) < 1 { + return fmt.Errorf("unset securityGroupIds") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getReplaceSecurityGroupWithUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ListRecycleInstances - list all instances in recycler with marker +// +// PARAMS: +// - marker: marker page +// +// RETURNS: +// - *RecyclerInstanceList: the result of instances in recycler +// - error: nil if success otherwise the specific error +func (c *Client) ListRecycleInstances(marker *Marker) (*RecyclerInstanceList, error) { + result := &RecyclerInstanceList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithQueryParams(getMarkerParams(marker)). + WithURL(getRecyclerUrl()). + WithResult(result). + Do() + + return result, err +} + +// RecoverRecyclerInstances - batch recover instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to recover +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RecoverRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + args := &BatchInstanceIds{ + InstanceIds: instanceIds, + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRecyclerRecoverUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// DeleteRecyclerInstances - batch delete instances that in recycler +// +// PARAMS: +// - instanceIds: instanceId list to delete +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRecyclerInstances(instanceIds []string) error { + if instanceIds == nil || len(instanceIds) < 1 { + return fmt.Errorf("unset instanceIds") + } + if len(instanceIds) > 10 { + return fmt.Errorf("the instanceIds length max value is 10") + } + + args := &BatchInstanceIds{ + InstanceIds: instanceIds, + } + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRecyclerDeleteUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// RenewInstances - batch renew instances +// +// PARAMS: +// - args: renew instanceIds and duration +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenewInstances(args *RenewInstanceArgs) (*OrderIdResult, error) { + if args == nil { + return nil, fmt.Errorf("unset args") + } + if args.InstanceIds == nil || len(args.InstanceIds) < 1 { + return nil, fmt.Errorf("unset instanceIds") + } + if len(args.InstanceIds) > 10 { + return nil, fmt.Errorf("the instanceIds length max value is 10") + } + result := &OrderIdResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getRenewUrl()). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// ListLogByInstanceId - list error or slow logs of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *[]Log:logs of instance +// - error: nil if success otherwise the specific error +func (c *Client) ListLogByInstanceId(instanceId string, args *ListLogArgs) (*ListLogResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if args == nil { + return nil, fmt.Errorf("unset list log args") + } + params, err2 := getQueryParams(args) + if err2 != nil { + return nil, err2 + } + result := &ListLogResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithInstanceId(instanceId)). + WithQueryParams(params). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetLogById - get log's detail of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *Log:log's detail of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetLogById(instanceId, logId string, args *GetLogArgs) (*LogItem, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + if len(logId) < 1 { + return nil, fmt.Errorf("unset logId") + } + if args == nil { + return nil, fmt.Errorf("unset get log args") + } + + result := &LogItem{} + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getLogsUrlWithLogId(instanceId, logId)). + WithQueryParam("validSeconds", strconv.Itoa(args.ValidSeconds)). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetMaintainTime - get maintainTime of instance +// +// PARAMS: +// - instanceId: id of instance +// +// RETURNS: +// - *GetMaintainTimeResult:maintainTime of instance +// - error: nil if success otherwise the specific error +func (c *Client) GetMaintainTime(instanceId string) (*GetMaintainTimeResult, error) { + if len(instanceId) < 1 { + return nil, fmt.Errorf("unset instanceId") + } + + result := &GetMaintainTimeResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getInstanceUrlWithId(instanceId)+"/maintainTime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// ModifyMaintainTime - modify MaintainTime of instance +// +// PARAMS: +// - args: new maintainTime +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ModifyMaintainTime(instanceId string, args *MaintainTime) error { + if args == nil { + return fmt.Errorf("unset args") + } + if len(instanceId) < 1 { + return fmt.Errorf("unset instanceIds") + } + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getInstanceUrlWithId(instanceId)+"/maintainTime"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + if err != nil { + return err + } + return nil +} + +// GroupPreCheck - group preCheck +// +// PARAMS: +// - args: the argumetns to group preCheck +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GroupPreCheckResult: the result of group preCheck +func (c *Client) GroupPreCheck(args *GroupPreCheckArgs) (*GroupPreCheckResult, error) { + result := &GroupPreCheckResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl()+"/check"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// CreateGroup - create group +// +// PARAMS: +// - args: the argumetns to create group +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *CreateGroupResult: the result of create group +func (c *Client) CreateGroup(args *CreateGroupArgs) (*CreateGroupResult, error) { + result := &CreateGroupResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl()+"/create"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// GetGroupList - get group list +// +// PARAMS: +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GroupListResult: the result of group list +func (c *Client) GetGroupList(args *GetGroupListArgs) (*GroupListResult, error) { + result := &GroupListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl()+"/list"). + WithBody(args). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GetGroupDetail - get group detail +// +// PARAMS: +// - groupId: the group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GroupDetailResult: the result of group detail +func (c *Client) GetGroupDetail(groupId string) (*GroupDetailResult, error) { + result := &GroupDetailResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGroupUrl()+"/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// DeleteGroup - delete group +// +// PARAMS: +// - groupId: the group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteGroup(groupId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getGroupUrl()+"/"+groupId+"/release"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// GroupAddFollower - add follower to a group +// +// PARAMS: +// - groupId: the group id +// - args: the arguments to add follower +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupAddFollower(groupId string, args *FollowerInfo) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl()+"/"+groupId+"/join"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupRemoveFollower - remove follower to a group +// +// PARAMS: +// - groupId: the group id +// - instanceId: the instance id which to remove +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupRemoveFollower(groupId, instanceId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl()+"/"+groupId+"/quit/"+instanceId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// SetAsLeader - set instance as leader +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - instanceId: id of the instance +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SetAsLeader(groupId, instanceId string) error { + return bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getGroupUrl() + "/" + groupId + "/setAsLeader/" + instanceId). + Do() +} + +// UpdateGroupName - update group name +// +// PARAMS: +// - groupId: the group id +// - args: the arguments to update group name +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateGroupName(groupId string, args *GroupNameArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupForbidWrite - forbid write permission +// +// PARAMS: +// - groupId: the group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupForbidWrite(groupId string, args *ForbidWriteArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId+"/forbidWrite"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupSetQps - set group qps +// PARAMS: +// - groupId: the group id +// - args: the arguments to set group qps +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupSetQps(groupId string, args *GroupSetQpsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId+"/qps"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupSyncStatus - get group sync status +// +// PARAMS: +// - groupId: the group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GroupSyncStatusResult: the result of group sync status +func (c *Client) GroupSyncStatus(groupId string) (*GroupSyncStatusResult, error) { + result := &GroupSyncStatusResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGroupUrl()+"/"+groupId+"/syncStatus"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GroupWhiteList - get group white list +// +// PARAMS: +// - groupId: the group id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GroupWhiteListResult: the result of group sync status +func (c *Client) GroupWhiteList(groupId string) (*GroupWhiteList, error) { + result := &GroupWhiteList{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getGroupUrl()+"/"+groupId+"/white_list"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// GroupWhiteListAdd - add group white list +// +// PARAMS: +// - groupId: the group id +// - args: the arguments to add group white list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupWhiteListAdd(groupId string, args *GroupWhiteList) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId+"/white_list/add"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupWhiteListDelete - delete group white list +// +// PARAMS: +// - groupId: the group id +// - args: the arguments to delete group white list +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupWhiteListDelete(groupId string, args *GroupWhiteList) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId+"/white_list/delete"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GroupStaleReadable - set group follower stale readable +// +// PARAMS: +// - groupId: the group id +// - args: the arguments to set group follower stale readable +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) GroupStaleReadable(groupId string, args *StaleReadableArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getGroupUrl()+"/"+groupId+"/stale_readable"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// CreateParamsTemplate - create params template +// +// PARAMS: +// - args: the arguments to create params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *CreateParamsTemplateResult: the result of create params template +func (c *Client) CreateParamsTemplate(args *CreateTemplateArgs) (*CreateParamsTemplateResult, error) { + result := &CreateParamsTemplateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTemplateUrl()+"/create"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// GetParamsTemplateList - get params template list +// +// PARAMS: +// - marker: pagination marker +// - maxkeys : max keys +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ParamsTemplateListResult: the result of get params template list +func (c *Client) GetParamsTemplateList(marker *Marker) (*ParamsTemplateListResult, error) { + result := &ParamsTemplateListResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTemplateUrl()+"/list"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParams(getMarkerParams(marker)). + WithResult(result). + Do() + return result, err +} + +// GetParamsTemplateDetail - get params template detail +// +// PARAMS: +// - templateId: the template id +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *ParamsTemplateDetailResult: the result of get params template detail +func (c *Client) GetParamsTemplateDetail(templateId string) (*ResultItem, error) { + result := &ResultItem{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTemplateUrl()+"/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithResult(result). + Do() + return result, err +} + +// DeleteParamsTemplate - delete params template +// +// PARAMS: +// - templateId: the template id +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteParamsTemplate(templateId string) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getTemplateUrl()+"/delete/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + Do() + return err +} + +// RenameParamsTemplate - rename params template +// +// PARAMS: +// - templateId: the template id +// - args : new template name args +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenameParamsTemplate(templateId string, args *RenameTemplateArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.PUT). + WithURL(getTemplateUrl()+"/rename/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// ApplyParamsTemplate - apply params template +// +// PARAMS: +// - templateId: the template id +// - args : the args to apply params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ApplyParamsTemplate(templateId string, args *ApplyTemplateArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTemplateUrl()+"/apply/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// TemplateAddParams - add params to template +// +// PARAMS: +// - templateId: the template id +// - args : the args to add params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) TemplateAddParams(templateId string, args *AddParamsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTemplateUrl()+"/addParams/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// TemplateModifyParams - modify params to template +// +// PARAMS: +// - templateId: the template id +// - args : the args to modify params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) TemplateModifyParams(templateId string, args *ModifyParamsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTemplateUrl()+"/modifyParams/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// TemplateDeleteParams - delete params to template +// +// PARAMS: +// - templateId: the template id +// - args : the args to delete params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) TemplateDeleteParams(templateId string, args *DeleteParamsArgs) error { + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getTemplateUrl()+"/deleteParams/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + Do() + return err +} + +// GetSystemTemplate - get system template +// +// PARAMS: +// - args : the args to get system params template +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *SystemTemptaleResult: the result of get system template +func (c *Client) GetSystemTemplate(args *GetSystemTemplateArgs) (*SystemTemplateResult, error) { + result := &SystemTemplateResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTemplateUrl()+"/system"). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithBody(args). + WithQueryParamFilter("engine", args.Engine). + WithQueryParamFilter("engineVersion", args.EngineVersion). + WithQueryParamFilter("clusterType", args.ClusterType). + WithResult(result). + Do() + return result, err +} + +// GetApplyRecords - get template apply records +// +// PARAMS: +// - args : the args to get template apply records +// +// RETURNS: +// - error: nil if success otherwise the specific error +// - *GetApplyRecordsResult: the result of get template apply records +func (c *Client) GetApplyRecords(templateId string, marker *Marker) (*GetApplyRecordsResult, error) { + result := &GetApplyRecordsResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getTemplateUrl()+"/record/"+templateId). + WithHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE). + WithQueryParams(getMarkerParams(marker)). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/scs/util.go b/bce-sdk-go/services/scs/util.go new file mode 100644 index 0000000..c47739d --- /dev/null +++ b/bce-sdk-go/services/scs/util.go @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of SCS service +package scs + +import ( + "encoding/hex" + "fmt" + + "github.com/baidubce/bce-sdk-go/util/crypto" +) + +func Aes128EncryptUseSecreteKey(sk string, data string) (string, error) { + if len(sk) < 16 { + return "", fmt.Errorf("error secrete key") + } + + crypted, err := crypto.EBCEncrypto([]byte(sk[:16]), []byte(data)) + if err != nil { + return "", err + } + + return hex.EncodeToString(crypted), nil +} diff --git a/bce-sdk-go/services/sms/api/mobile_black.go b/bce-sdk-go/services/sms/api/mobile_black.go new file mode 100644 index 0000000..0fee006 --- /dev/null +++ b/bce-sdk-go/services/sms/api/mobile_black.go @@ -0,0 +1,157 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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. + */ + +// mobile_black.go - the sms MobileBlack APIs definition supported by the SMS service + +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateMobileBlack - create an sms MobileBlack +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create an sms mobileBlack +// +// RETURNS: +// - error: the return error if any occurs +func CreateMobileBlack(cli bce.Client, args *CreateMobileBlackArgs) error { + if err := CheckError(args != nil, "CreateMobileBlackArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.Type) > 0, "type can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.Phone) > 0, "phone can not be blank"); err != nil { + return err + } + if args.Type == "SignatureBlack" { + if err := CheckError(len(args.SmsType) > 0, + "smsType can not be blank, when 'type' is 'SignatureBlack'."); err != nil { + return err + } + if err := CheckError(len(args.SignatureIdStr) > 0, + "signatureIdStr can not be blank, when 'type' is 'SignatureBlack'."); err != nil { + return err + } + } + + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_BLACK) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// DeleteMobileBlack - delete sms mobileBlack by phones +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to delete sms mobileBlack +// +// RETURNS: +// - error: the return error if any occurs +func DeleteMobileBlack(cli bce.Client, args *DeleteMobileBlackArgs) error { + if err := CheckError(args != nil, "DeleteMobileBlackArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.Phones) > 0, "Phones can not be blank"); err != nil { + return err + } + return bce.NewRequestBuilder(cli). + WithMethod(http.DELETE). + WithURL(REQUEST_URI_BLACK+"/delete"). + WithQueryParam("phones", args.Phones). + Do() +} + +// GetMobileBlack - get sms mobileBlackList +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get sms mobileBlackList +// +// RETURNS: +// - error: the return error if any occurs +// - *api.GetMobileBlackResult: the result of get sms MobileBlackList +func GetMobileBlack(cli bce.Client, args *GetMobileBlackArgs) (*GetMobileBlackResult, error) { + if err := CheckError(args != nil, "GetMobileBlackArgs can not be nil"); err != nil { + return nil, err + } + + paramsMap := make(map[string]string) + if len(args.Phone) > 0 { + paramsMap["phone"] = args.Phone + } + if len(args.SmsType) > 0 { + paramsMap["smsType"] = args.SmsType + } + if len(args.SignatureIdStr) > 0 { + paramsMap["signatureIdStr"] = args.SignatureIdStr + } + if len(args.StartTime) > 0 { + paramsMap["startTime"] = args.StartTime + } + if len(args.EndTime) > 0 { + paramsMap["endTime"] = args.EndTime + } + if len(args.PageNo) > 0 { + paramsMap["pageNo"] = args.PageNo + } + if len(args.PageSize) > 0 { + paramsMap["pageSize"] = args.PageSize + } + + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_BLACK) + req.SetMethod(http.GET) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetParams(paramsMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + result := &GetMobileBlackResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/sms/api/model.go b/bce-sdk-go/services/sms/api/model.go new file mode 100644 index 0000000..79ef37f --- /dev/null +++ b/bce-sdk-go/services/sms/api/model.go @@ -0,0 +1,253 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package api + +// SendSmsArgs defines the data structure for sending a SMS request +type SendSmsArgs struct { + Mobile string `json:"mobile"` + Template string `json:"template"` + SignatureId string `json:"signatureId"` + ContentVar map[string]interface{} `json:"contentVar"` + Custom string `json:"custom,omitempty"` + UserExtId string `json:"userExtId,omitempty"` + CallbackUrlId string `json:"merchantUrlId,omitempty"` + ClientToken string `json:"clientToken,omitempty"` +} + +// SendSmsResult defines the data structure of the result of sending a SMS request +type SendSmsResult struct { + Code string `json:"code"` + RequestId string `json:"requestId"` + Message string `json:"message"` + Data []SendMessageItem `json:"data"` +} + +type SendMessageItem struct { + Code string `json:"code"` + Mobile string `json:"mobile"` + MessageId string `json:"messageId"` + Message string `json:"message"` +} + +// CreateSignatureArgs defines the data structure for creating a signature +type CreateSignatureArgs struct { + Content string `json:"content"` + ContentType string `json:"contentType"` + Description string `json:"description,omitempty"` + CountryType string `json:"countryType"` + SignatureFileBase64 string `json:"signatureFileBase64,omitempty"` + SignatureFileFormat string `json:"signatureFileFormat,omitempty"` +} + +// CreateSignatureResult defines the data structure of the result of creating a signature +type CreateSignatureResult struct { + SignatureId string `json:"signatureId"` + Status string `json:"status"` +} + +// DeleteSignatureArgs defines the input data structure for deleting a signature +type DeleteSignatureArgs struct { + SignatureId string `json:"signatureId"` +} + +// ModifySignatureArgs defines the input data structure for modifying parameters of a signature +type ModifySignatureArgs struct { + SignatureId string `json:"signatureId"` + Content string `json:"content"` + ContentType string `json:"contentType"` + Description string `json:"description,omitempty"` + CountryType string `json:"countryType"` + SignatureFileBase64 string `json:"signatureFileBase64,omitempty"` + SignatureFileFormat string `json:"signatureFileFormat,omitempty"` +} + +// GetSignatureArgs defines the input data structure for Getting a signature +type GetSignatureArgs struct { + SignatureId string `json:"signatureId"` +} + +// GetSignatureResult defines the data structure of the result of getting a signature +type GetSignatureResult struct { + SignatureId string `json:"signatureId"` + UserId string `json:"userId"` + Content string `json:"content"` + ContentType string `json:"contentType"` + Status string `json:"status"` + CountryType string `json:"countryType"` + Review string `json:"review"` +} + +// CreateTemplateArgs defines the data structure for creating a template +type CreateTemplateArgs struct { + Name string `json:"name"` + Content string `json:"content"` + SmsType string `json:"smsType"` + CountryType string `json:"countryType"` + Description string `json:"description,omitempty"` +} + +// CreateTemplateResult defines the data structure of the result of creating a template +type CreateTemplateResult struct { + TemplateId string `json:"templateId"` + Status string `json:"status"` +} + +// DeleteTemplateArgs defines the data structure for deleting a template +type DeleteTemplateArgs struct { + TemplateId string `json:"templateId"` +} + +// ModifyTemplateArgs defines the data structure for modifying a template +type ModifyTemplateArgs struct { + TemplateId string `json:"templateId"` + Name string `json:"name"` + Content string `json:"content"` + SmsType string `json:"smsType"` + CountryType string `json:"countryType"` + Description string `json:"description,omitempty"` +} + +// GetTemplateArgs defines the data structure for getting a template +type GetTemplateArgs struct { + TemplateId string `json:"templateId"` +} + +// GetTemplateResult defines the data structure of the result of getting a template +type GetTemplateResult struct { + TemplateId string `json:"templateId"` + UserId string `json:"userId"` + Name string `json:"name"` + Content string `json:"content"` + CountryType string `json:"countryType"` + SmsType string `json:"smsType"` + Status string `json:"status"` + Description string `json:"description"` + Review string `json:"review"` +} + +// UpdateQuotaRateArgs defines the data structure for updating quota and rate limit +type UpdateQuotaRateArgs struct { + QuotaPerDay int `json:"quotaPerDay"` + QuotaPerMonth int `json:"quotaPerMonth"` + RateLimitPerDay int `json:"rateLimitPerMobilePerSignByDay"` + RateLimitPerHour int `json:"rateLimitPerMobilePerSignByHour"` + RateLimitPerMinute int `json:"rateLimitPerMobilePerSignByMinute"` +} + +// QueryQuotaRateResult defines the data structure of querying the user's quota and rate limit +type QueryQuotaRateResult struct { + QuotaPerDay int `json:"quotaPerDay"` + QuotaRemainToday int `json:"quotaRemainToday"` + QuotaPerMonth int `json:"quotaPerMonth"` + QuotaRemainThisMonth int `json:"quotaRemainThisMonth"` + ApplyQuotaPerDay int `json:"applyQuotaPerDay"` + ApplyQuotaPerMonth int `json:"applyQuotaPerMonth"` + ApplyCheckStatus string `json:"applyCheckStatus"` + ApplyCheckReply string `json:"checkReply"` + RateLimitPerDay int `json:"rateLimitPerMobilePerSignByDay"` + RateLimitPerHour int `json:"rateLimitPerMobilePerSignByHour"` + RateLimitPerMinute int `json:"rateLimitPerMobilePerSignByMinute"` + RateLimitWhitelist bool `json:"rateLimitWhitelist"` +} + +// CreateMobileBlackArgs defines the data structure for creating a mobileBlack +type CreateMobileBlackArgs struct { + Type string `json:"type"` + SmsType string `json:"smsType"` + SignatureIdStr string `json:"signatureIdStr"` + Phone string `json:"phone"` +} + +// DeleteMobileBlackArgs defines the data structure for deleting mobileBlack by phones +type DeleteMobileBlackArgs struct { + Phones string `json:"phones"` +} + +// GetMobileBlackArgs defines the data structure for get mobileBlackList +// startTime、endTime format is yyyy-MM-dd +type GetMobileBlackArgs struct { + Phone string + SmsType string + SignatureIdStr string + StartTime string + EndTime string + PageNo string + PageSize string +} + +// GetMobileBlackResult defines the data structure for get mobileBlackList +type GetMobileBlackResult struct { + TotalCount int `json:"totalCount"` + PageNo int `json:"pageNo"` + PageSize int `json:"pageSize"` + BlackLists []MobileBlackDetail `json:"blacklists"` +} + +// MobileBlackDetail defines the data structure for mobileBlackList detail +type MobileBlackDetail struct { + Phone string `json:"phone"` + Type string `json:"type"` + SmsType string `json:"smsType"` + SignatureIdStr string `json:"signatureIdStr"` + UpdateDate string `json:"updateDate"` +} + +// ListStatisticsArgs defines the request data structure of ListStatistics +type ListStatisticsArgs struct { + SmsType string `json:"smsType"` + SignatureId string `json:"signatureId"` + TemplateCode string `json:"TemplateCode"` + CountryType string `json:"countryType"` // available values: "domestic", "international" + StartTime string `json:"startTime"` // format: "yyyy-MM-dd" + EndTime string `json:"endTime"` // format: "yyyy-MM-dd" +} + +// ListStatisticsResponse defines the response data structure of ListStatistics +type ListStatisticsResponse struct { + StatisticsResults []StatisticsResult `json:"statisticsResults"` +} + +// StatisticsResult defines the detail of ListStatisticsResponse +type StatisticsResult struct { + Datetime string `json:"datetime"` + CountryAlpha2Code string `json:"countryAlpha2Code"` + SubmitCount string `json:"submitCount"` + SubmitLongCount string `json:"submitLongCount"` + ResponseSuccessCount string `json:"responseSuccessCount"` + ResponseSuccessProportion string `json:"responseSuccessProportion"` + DeliverSuccessCount string `json:"deliverSuccessCount"` + DeliverSuccessLongCount string `json:"deliverSuccessLongCount"` + DeliverSuccessProportion string `json:"deliverSuccessProportion"` + DeliverFailureCount string `json:"deliverFailureCount"` + DeliverFailureProportion string `json:"deliverFailureProportion"` + ReceiptProportion string `json:"receiptProportion"` + UnknownCount string `json:"unknownCount"` + UnknownProportion string `json:"unknownProportion"` + ResponseTimeoutCount string `json:"responseTimeoutCount"` + UnknownErrorCount string `json:"unknownErrorCount"` + NotExistCount string `json:"notExistCount"` + SignatureOrTemplateCount string `json:"signatureOrTemplateCount"` + AbnormalCount string `json:"abnormalCount"` + OverclockingCount string `json:"overclockingCount"` + OtherErrorCount string `json:"otherErrorCount"` + BlacklistCount string `json:"blacklistCount"` + RouteErrorCount string `json:"routeErrorCount"` + IssueFailureCount string `json:"issueFailureCount"` + ParameterErrorCount string `json:"parameterErrorCount"` + IllegalWordCount string `json:"illegalWordCount"` + AnomalyCount string `json:"anomalyCount"` +} diff --git a/bce-sdk-go/services/sms/api/quota_rate.go b/bce-sdk-go/services/sms/api/quota_rate.go new file mode 100644 index 0000000..c4deafb --- /dev/null +++ b/bce-sdk-go/services/sms/api/quota_rate.go @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// quota_rate.go - the quota and rate limit APIs definition supported by the SMS service + +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// QueryQuotaRate - query the quota and rate limit detail of an user +// +// RETURNS: +// - *QueryQuotaRateResult: the result of the query +// - error: the return error if any occurs +func QueryQuotaRate(cli bce.Client) (*QueryQuotaRateResult, error) { + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_QUOTA) + req.SetMethod(http.GET) + req.SetParam("userQuery", "") + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &QueryQuotaRateResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// UpdateQuotaRate - update the quota and rate limit detail of an user +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to update the quota and rate limit +// +// RETURNS: +// - *ListBucketsResult: the result bucket list structure +// - error: nil if ok otherwise the specific error +func UpdateQuotaRate(cli bce.Client, args *UpdateQuotaRateArgs) error { + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_QUOTA) + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} diff --git a/bce-sdk-go/services/sms/api/send_sms.go b/bce-sdk-go/services/sms/api/send_sms.go new file mode 100644 index 0000000..50b64dd --- /dev/null +++ b/bce-sdk-go/services/sms/api/send_sms.go @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// send_sms.go - the send sms APIs definition supported by the SMS service + +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// SendSms - send an sms message +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to send an sms message +// +// RETURNS: +// - *api.SendSmsResult: the result of sending an sms message +// - error: the return error if any occurs +func SendSms(cli bce.Client, args *SendSmsArgs) (*SendSmsResult, error) { + if err := CheckError(len(args.Mobile) > 0, "mobile can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.SignatureId) > 0, "signatureId can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.Template) > 0, "templateId can not be blank"); err != nil { + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_SEND_SMS) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + if len(args.ClientToken) > 0 { + req.SetParam(CLIENT_TOKEN, args.ClientToken) + } else { + req.SetParam(CLIENT_TOKEN, util.NewUUID()) + } + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &SendSmsResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + + } + return result, nil +} diff --git a/bce-sdk-go/services/sms/api/signature.go b/bce-sdk-go/services/sms/api/signature.go new file mode 100644 index 0000000..cd34397 --- /dev/null +++ b/bce-sdk-go/services/sms/api/signature.go @@ -0,0 +1,177 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// signature.go - the sms signature APIs definition supported by the SMS service + +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// CreateSignature - create an sms signature +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create an sms signature +// +// RETURNS: +// - *api.CreateSignatureResult: the result of creating an sms signature +// - error: the return error if any occurs +func CreateSignature(cli bce.Client, args *CreateSignatureArgs) (*CreateSignatureResult, error) { + if err := CheckError(args != nil, "CreateSignatureArgs can not be nil"); err != nil { + return nil, err + } + if err := CheckError(len(args.Content) > 0, "content can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.ContentType) > 0, "contentType can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.CountryType) > 0, "countryType can not be blank"); err != nil { + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_SIGNATURE) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetParam(CLIENT_TOKEN, util.NewUUID()) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CreateSignatureResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteSignature - delete an sms signature +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to delete an sms signature +// +// RETURNS: +// - error: the return error if any occurs +func DeleteSignature(cli bce.Client, args *DeleteSignatureArgs) error { + if err := CheckError(args != nil, "DeleteSignatureArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.SignatureId) > 0, "signatureId can not be blank"); err != nil { + return err + } + return bce.NewRequestBuilder(cli). + WithMethod(http.DELETE). + WithURL(REQUEST_URI_SIGNATURE + bce.URI_PREFIX + args.SignatureId). + Do() +} + +// ModifySignature - modify an sms signature +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to modify an sms signature +// +// RETURNS: +// - error: the return error if any occurs +func ModifySignature(cli bce.Client, args *ModifySignatureArgs) error { + if err := CheckError(args != nil, "ModifySignatureArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.SignatureId) > 0, "signatureId can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.Content) > 0, "content can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.ContentType) > 0, "contentType can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.CountryType) > 0, "countryType can not be blank"); err != nil { + return err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_SIGNATURE + bce.URI_PREFIX + args.SignatureId) + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetSignature - get the detail of an sms signature +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get the detail of an sms signature +// +// RETURNS: +// - *api.GetSignatureResult: the detail of an sms signature +// - error: the return error if any occurs +func GetSignature(cli bce.Client, args *GetSignatureArgs) (*GetSignatureResult, error) { + if err := CheckError(args != nil, "GetSignatureArgs can not be nil"); err != nil { + return nil, err + } + if err := CheckError(len(args.SignatureId) > 0, "signatureId can not be blank"); err != nil { + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_SIGNATURE + bce.URI_PREFIX + args.SignatureId) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetSignatureResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/sms/api/statistics.go b/bce-sdk-go/services/sms/api/statistics.go new file mode 100644 index 0000000..8e6afb6 --- /dev/null +++ b/bce-sdk-go/services/sms/api/statistics.go @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Baidu, Inc. + * + * 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. + */ + +// mobile_black.go - the sms MobileBlack APIs definition supported by the SMS service + +package api + +import ( + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListStatistics - get sms statistics data +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to get sms statistics data +// +// RETURNS: +// - error: the return error if any occurs +// - *api.ListStatisticsResponse: the result of get sms MobileBlackList +func ListStatistics(cli bce.Client, args *ListStatisticsArgs) (*ListStatisticsResponse, error) { + if err := CheckError(args != nil, "ListStatisticsArgs can not be nil"); err != nil { + return nil, err + } + + paramsMap := make(map[string]string) + + if err := CheckError(len(args.StartTime) > 0, + "ListStatistics query start time can not be nil"); err != nil { + return nil, err + } + + paramsMap["startTime"] = args.StartTime + " 00:00:00" + + if err := CheckError(len(args.EndTime) > 0, + "ListStatistics query end time can not be nil"); err != nil { + return nil, err + } + + paramsMap["endTime"] = args.EndTime + " 23:59:59" + + // default value + paramsMap["smsType"] = "all" + paramsMap["dimension"] = "day" + + if len(args.SmsType) > 0 { + paramsMap["smsType"] = args.SmsType + } + if len(args.CountryType) > 0 { + paramsMap["countryType"] = args.CountryType + } + if len(args.SignatureId) > 0 { + paramsMap["signatureId"] = args.SignatureId + } + if len(args.TemplateCode) > 0 { + paramsMap["templateCode"] = args.TemplateCode + } + + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_STATISTICS) + req.SetMethod(http.GET) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetParams(paramsMap) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + + result := &ListStatisticsResponse{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/sms/api/template.go b/bce-sdk-go/services/sms/api/template.go new file mode 100644 index 0000000..6441d21 --- /dev/null +++ b/bce-sdk-go/services/sms/api/template.go @@ -0,0 +1,182 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// template.go - the sms template APIs definition supported by the SMS service + +package api + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "github.com/baidubce/bce-sdk-go/util" +) + +// CreateTemplate - create an sms template +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to create an sms template +// +// RETURNS: +// - *api.CreateTemplateResult: the result of creating an sms template +// - error: the return error if any occurs +func CreateTemplate(cli bce.Client, args *CreateTemplateArgs) (*CreateTemplateResult, error) { + if err := CheckError(args != nil, "CreateTemplateArgs can not be nil"); err != nil { + return nil, err + } + if err := CheckError(len(args.Content) > 0, "content can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.CountryType) > 0, "countryType can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.Name) > 0, "name can not be blank"); err != nil { + return nil, err + } + if err := CheckError(len(args.SmsType) > 0, "smsType can not be blank"); err != nil { + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_TEMPLATE) + req.SetMethod(http.POST) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + req.SetParam(CLIENT_TOKEN, util.NewUUID()) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &CreateTemplateResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} + +// DeleteTemplate - delete an sms template +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to delete an sms template +// +// RETURNS: +// - error: the return error if any occurs +func DeleteTemplate(cli bce.Client, args *DeleteTemplateArgs) error { + if err := CheckError(args != nil, "DeleteTemplateArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.TemplateId) > 0, "templateId can not be blank"); err != nil { + return err + } + return bce.NewRequestBuilder(cli). + WithMethod(http.DELETE). + WithURL(REQUEST_URI_TEMPLATE + bce.URI_PREFIX + args.TemplateId). + Do() +} + +// ModifyTemplate - modify an sms template +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to modify an sms template +// +// RETURNS: +// - error: the return error if any occurs +func ModifyTemplate(cli bce.Client, args *ModifyTemplateArgs) error { + if err := CheckError(args != nil, "ModifyTemplateArgs can not be nil"); err != nil { + return err + } + if err := CheckError(len(args.TemplateId) > 0, "templateId can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.Content) > 0, "content can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.CountryType) > 0, "countryType can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.Name) > 0, "name can not be blank"); err != nil { + return err + } + if err := CheckError(len(args.SmsType) > 0, "smsType can not be blank"); err != nil { + return err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_TEMPLATE + bce.URI_PREFIX + args.TemplateId) + req.SetMethod(http.PUT) + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(body) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +// GetTemplate - modify an sms template +// +// PARAMS: +// - cli: the client agent which can perform sending request +// - args: the arguments to modify an sms template +// +// RETURNS: +// - error: the return error if any occurs +func GetTemplate(cli bce.Client, args *GetTemplateArgs) (*GetTemplateResult, error) { + if err := CheckError(args != nil, "GetTemplateResult can not be nil"); err != nil { + return nil, err + } + if err := CheckError(len(args.TemplateId) > 0, "templateId can not be blank"); err != nil { + return nil, err + } + req := &bce.BceRequest{} + req.SetUri(REQUEST_URI_TEMPLATE + bce.URI_PREFIX + args.TemplateId) + req.SetMethod(http.GET) + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + result := &GetTemplateResult{} + if err := resp.ParseJsonBody(result); err != nil { + return nil, err + } + return result, nil +} diff --git a/bce-sdk-go/services/sms/api/util.go b/bce-sdk-go/services/sms/api/util.go new file mode 100644 index 0000000..286b0f2 --- /dev/null +++ b/bce-sdk-go/services/sms/api/util.go @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// util.go - define the utilities for api package of SMS service + +package api + +import ( + "fmt" +) + +const ( + REQUEST_URI_SEND_SMS = "/api/v3/sendsms" + REQUEST_URI_SIGNATURE = "/sms/v3/signatureApply" + REQUEST_URI_TEMPLATE = "/sms/v3/template" + REQUEST_URI_QUOTA = "/sms/v3/quota" + REQUEST_URI_BLACK = "/sms/v3/blacklist" + REQUEST_URI_STATISTICS = "/sms/v3/summary" + CLIENT_TOKEN = "clientToken" +) + +func CheckError(condition bool, errMessage string) error { + if !condition { + return fmt.Errorf(errMessage) + } + return nil +} diff --git a/bce-sdk-go/services/sms/client.go b/bce-sdk-go/services/sms/client.go new file mode 100644 index 0000000..197465c --- /dev/null +++ b/bce-sdk-go/services/sms/client.go @@ -0,0 +1,235 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for SMS service + +package sms + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/sms/api" +) + +const ( + REGION_BJ = "bj" + REGION_SU = "su" + DEFAULT_SERVICE_DOMAIN = "smsv3." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of SMS service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the SMS service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + var credentials *auth.BceCredentials + var err error + if len(ak) == 0 && len(sk) == 0 { // to support public-read-write request + credentials, err = nil, nil + } else { + credentials, err = auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +// SendSms - send an sms message +// +// PARAMS: +// - args: the arguments to send an sms message +// +// RETURNS: +// - *api.SendSmsResult: the result of sending an sms message +// - error: the return error if any occurs +func (c *Client) SendSms(args *api.SendSmsArgs) (*api.SendSmsResult, error) { + return api.SendSms(c, args) +} + +// CreateSignature - create an sms signature +// +// PARAMS: +// - args: the arguments to create an sms signature +// +// RETURNS: +// - *api.CreateSignatureResult: the result of creating an sms signature +// - error: the return error if any occurs +func (c *Client) CreateSignature(args *api.CreateSignatureArgs) (*api.CreateSignatureResult, error) { + return api.CreateSignature(c, args) +} + +// DeleteSignature - delete an sms signature +// +// PARAMS: +// - args: the arguments to delete an sms signature +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteSignature(args *api.DeleteSignatureArgs) error { + return api.DeleteSignature(c, args) +} + +// ModifySignature - modify an sms signature +// +// PARAMS: +// - args: the arguments to modify an sms signature +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) ModifySignature(args *api.ModifySignatureArgs) error { + return api.ModifySignature(c, args) +} + +// GetSignature - get the detail of an sms signature +// +// PARAMS: +// - args: the arguments to get the detail of an sms signature +// +// RETURNS: +// - *api.GetSignatureResult: the detail of an sms signature +// - error: the return error if any occurs +func (c *Client) GetSignature(args *api.GetSignatureArgs) (*api.GetSignatureResult, error) { + return api.GetSignature(c, args) +} + +// CreateTemplate - create an sms template +// +// PARAMS: +// - args: the arguments to create an sms template +// +// RETURNS: +// - *api.CreateTemplateResult: the result of creating an sms template +// - error: the return error if any occurs +func (c *Client) CreateTemplate(args *api.CreateTemplateArgs) (*api.CreateTemplateResult, error) { + return api.CreateTemplate(c, args) +} + +// DeleteTemplate - delete an sms template +// +// PARAMS: +// - args: the arguments to delete an sms template +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteTemplate(args *api.DeleteTemplateArgs) error { + return api.DeleteTemplate(c, args) +} + +// ModifyTemplate - modify an sms template +// +// PARAMS: +// - args: the arguments to modify an sms template +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) ModifyTemplate(args *api.ModifyTemplateArgs) error { + return api.ModifyTemplate(c, args) +} + +// GetTemplate - modify an sms template +// +// PARAMS: +// - args: the arguments to modify an sms template +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) GetTemplate(args *api.GetTemplateArgs) (*api.GetTemplateResult, error) { + return api.GetTemplate(c, args) +} + +// QueryQuotaAndRateLimit - query the quota and rate limit +// +// RETURNS: +// - QueryQuotaRateResult: the result of querying the quota and rate limit +// - error: the return error if any occurs +func (c *Client) QueryQuotaAndRateLimit() (*api.QueryQuotaRateResult, error) { + return api.QueryQuotaRate(c) +} + +// UpdateQuotaAndRateLimit - update the quota or rate limit +// PARAMS: +// - args: the arguments to update the quota or rate limit +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) UpdateQuotaAndRateLimit(args *api.UpdateQuotaRateArgs) error { + return api.UpdateQuotaRate(c, args) +} + +// CreateMobileBlack - create an sms mobileBlack +// +// PARAMS: +// - args: the arguments to create an sms mobileBlack +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) CreateMobileBlack(args *api.CreateMobileBlackArgs) error { + return api.CreateMobileBlack(c, args) +} + +// DeleteMobileBlack - delete sms mobileBlack by phones +// +// PARAMS: +// - args: the arguments to delete an sms mobileBlack +// +// RETURNS: +// - error: the return error if any occurs +func (c *Client) DeleteMobileBlack(args *api.DeleteMobileBlackArgs) error { + return api.DeleteMobileBlack(c, args) +} + +// GetMobileBlack - get a sms mobileBlack +// +// PARAMS: +// - args: the arguments to get sms mobileBlack +// +// RETURNS: +// - error: the return error if any occurs +// - *api.GetMobileBlackResult: the result of creating an sms mobileBlack +func (c *Client) GetMobileBlack(args *api.GetMobileBlackArgs) (*api.GetMobileBlackResult, error) { + return api.GetMobileBlack(c, args) +} + +// ListStatistics - get a list of sms statistics data +// +// PARAMS: +// - args: the arguments to get sms statistics list data +// +// RETURNS: +// - error: the return error if any occurs +// - *api.ListStatisticsResponse: the result of getting statistics list data +func (c *Client) ListStatistics(args *api.ListStatisticsArgs) (*api.ListStatisticsResponse, error) { + return api.ListStatistics(c, args) +} diff --git a/bce-sdk-go/services/sms/client_test.go b/bce-sdk-go/services/sms/client_test.go new file mode 100644 index 0000000..ff2f681 --- /dev/null +++ b/bce-sdk-go/services/sms/client_test.go @@ -0,0 +1,236 @@ +package sms + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/services/sms/api" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + SMS_CLIENT *Client + TEST_SIGNATURE_ID = "" + TEST_TEMPLATE_ID = "" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "/config.json") + fmt.Println(conf) + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + SMS_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) + //log.SetLogLevel(log.DEBUG) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestSendSms(t *testing.T) { + contentMap := make(map[string]interface{}) + contentMap["code"] = "123" + contentMap["minute"] = "1" + sendSmsArgs := &api.SendSmsArgs{ + Mobile: "13800138000", + Template: "your template id", + SignatureId: "your signature id", + ContentVar: contentMap, + } + result, err := SMS_CLIENT.SendSms(sendSmsArgs) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", result) +} + +func TestCreateSignature(t *testing.T) { + result, err := SMS_CLIENT.CreateSignature(&api.CreateSignatureArgs{ + Content: "测试", + ContentType: "Enterprise", + Description: "This is a test", + CountryType: "DOMESTIC", + }) + ExpectEqual(t.Errorf, err, nil) + TEST_SIGNATURE_ID = result.SignatureId + t.Logf("%v", result) +} + +func TestGetSignature(t *testing.T) { + _, err := SMS_CLIENT.GetSignature(&api.GetSignatureArgs{SignatureId: TEST_SIGNATURE_ID}) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifySignature(t *testing.T) { + err := SMS_CLIENT.ModifySignature(&api.ModifySignatureArgs{ + SignatureId: TEST_SIGNATURE_ID, + Content: "测试变更", + ContentType: "Enterprise", + Description: "This is a test", + CountryType: "INTERNATIONAL", + SignatureFileBase64: "", + SignatureFileFormat: "", + }) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteSignature(t *testing.T) { + err := SMS_CLIENT.DeleteSignature(&api.DeleteSignatureArgs{SignatureId: TEST_SIGNATURE_ID}) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateTemplate(t *testing.T) { + result, err := SMS_CLIENT.CreateTemplate(&api.CreateTemplateArgs{ + Name: "测试", + Content: "${content}", + SmsType: "CommonNotice", + CountryType: "DOMESTIC", + Description: "gogogo", + }) + ExpectEqual(t.Errorf, err, nil) + TEST_TEMPLATE_ID = result.TemplateId +} + +func TestGetTemplate(t *testing.T) { + _, err := SMS_CLIENT.GetTemplate(&api.GetTemplateArgs{TemplateId: TEST_TEMPLATE_ID}) + ExpectEqual(t.Errorf, err, nil) +} + +func TestModifyTemplate(t *testing.T) { + err := SMS_CLIENT.ModifyTemplate(&api.ModifyTemplateArgs{ + TemplateId: TEST_TEMPLATE_ID, + Name: "测试变更模板", + Content: "${code}", + SmsType: "CommonVcode", + CountryType: "GLOBAL", + Description: "this is a test", + }) + ExpectEqual(t.Errorf, err, nil) +} + +func TestDeleteTemplate(t *testing.T) { + err := SMS_CLIENT.DeleteTemplate(&api.DeleteTemplateArgs{ + TemplateId: TEST_TEMPLATE_ID, + }) + ExpectEqual(t.Errorf, err, nil) +} + +func TestCreateMobileBlack(t *testing.T) { + err := SMS_CLIENT.CreateMobileBlack(&api.CreateMobileBlackArgs{ + Type: "SignatureBlack", + Phone: "17600000000", + SmsType: "CommonNotice", + SignatureIdStr: "1234", + }) + ExpectEqual(t.Errorf, err, nil) +} + +func TestGetMobileBlack(t *testing.T) { + res, err := SMS_CLIENT.GetMobileBlack(&api.GetMobileBlackArgs{ + Phone: "17600000000", + SmsType: "CommonNotice", + SignatureIdStr: "1234", + StartTime: "2023-07-10", + EndTime: "2023-07-20", + PageSize: "10", + PageNo: "1", + }) + ExpectEqual(t.Errorf, err, nil) + t.Logf("%v", res) +} + +func TestDeleteMobileBlack(t *testing.T) { + err := SMS_CLIENT.DeleteMobileBlack(&api.DeleteMobileBlackArgs{ + Phones: "17600000000,17600000001", + }) + ExpectEqual(t.Errorf, err, nil) +} + +func TestListStatistics(t *testing.T) { + // normal case: necessary parameters, domestic + res, err := SMS_CLIENT.ListStatistics(&api.ListStatisticsArgs{ + StartTime: "2023-09-30", + EndTime: "2023-09-30", + CountryType: "domestic", + }) + ExpectEqual(t.Errorf, 2, len(res.StatisticsResults)) + t.Logf("test1: %#v", res) + + // normal case: necessary parameters, international + res, err = SMS_CLIENT.ListStatistics(&api.ListStatisticsArgs{ + StartTime: "2023-09-30", + EndTime: "2023-09-30", + CountryType: "international", + }) + ExpectEqual(t.Errorf, 6, len(res.StatisticsResults)) + t.Logf("test2: %#v", res) + + // normal case: select signature + res, err = SMS_CLIENT.ListStatistics(&api.ListStatisticsArgs{ + StartTime: "2023-09-30", + EndTime: "2023-09-30", + SignatureId: "114514", + }) + ExpectEqual(t.Errorf, 2, len(res.StatisticsResults)) + t.Logf("test2: %#v", res) + + // error case1: nil query end time + res, err = SMS_CLIENT.ListStatistics(&api.ListStatisticsArgs{ + StartTime: "2023-10-08", + }) + ExpectEqual(t.Errorf, nil, res) + t.Logf("test3: %v", err) + + // error case2: wrong format of time + res, err = SMS_CLIENT.ListStatistics(&api.ListStatisticsArgs{ + StartTime: "2023-10-08 00", + EndTime: "2023-10-09", + }) + ExpectEqual(t.Errorf, nil, res) + t.Logf("test4: %v", err) +} diff --git a/bce-sdk-go/services/sts/api/mode.go b/bce-sdk-go/services/sts/api/mode.go new file mode 100644 index 0000000..ef43330 --- /dev/null +++ b/bce-sdk-go/services/sts/api/mode.go @@ -0,0 +1,30 @@ +package api + +import "time" + +type GetSessionTokenResult struct { + AccessKeyId string + SecretAccessKey string + SessionToken string + CreateTime string + Expiration string + UserId string +} + +type AssumeRoleArgs struct { + DurationSeconds int + AccountId string + UserId string + RoleName string + Acl string +} + +type Credential struct { + AccessKeyId string + SecretAccessKey string + SessionToken string + CreateTime time.Time + Expiration time.Time + UserId string + RoleId string +} diff --git a/bce-sdk-go/services/sts/api/sts.go b/bce-sdk-go/services/sts/api/sts.go new file mode 100644 index 0000000..d849b7e --- /dev/null +++ b/bce-sdk-go/services/sts/api/sts.go @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// sts.go - define the response for STS service + +// Package api defines all APIs supported by the STS service of BCE. +package api + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +const ( + DEFAULT_DURATION_SECONDS = 43200 // default duration is 12 hours + URI_PREFIX = bce.URI_PREFIX + "v1" // sts service not support uri without prefix "v1" + REQUEST_URI = "/sessionToken" + + DEFAULT_ASSUMEROLE_DURATION_SECONDS = 7200 // default duration is 2 hours + REQUEST_ASSUMEROLE_URI = "/credential" +) + +// GetSessionToken - get the session token from the STS service +// +// PARAMS: +// - cli: the client object which can perform sending request +// - durationSec: the duration seconds of the token +// - acl: the acl string +// +// RETURNS: +// - *GetSessionTokenResult: result of this api +// - error: nil if ok otherwise the specific error +func GetSessionToken(cli bce.Client, durationSec int, acl string) (*GetSessionTokenResult, error) { + // If the duration seconds is not a positive, use the default value + if durationSec <= 0 { + durationSec = DEFAULT_DURATION_SECONDS + } + + // Build the request + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + REQUEST_URI) + req.SetMethod(http.POST) + req.SetParam("durationSeconds", strconv.Itoa(durationSec)) + if len(acl) > 0 { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + body, err := bce.NewBodyFromString(acl) + if err != nil { + return nil, err + } + req.SetBody(body) + } + + // Send requet and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetSessionTokenResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +// AssumeRole - get the credential for the assume role from the STS service +// +// PARAMS: +// - cli: the client object which can perform sending request +// - args: the args for assumeRole +// +// RETURNS: +// - *Credential: result of this api +// - error: nil if ok otherwise the specific error +func AssumeRole(cli bce.Client, args *AssumeRoleArgs) (*Credential, error) { + // If the duration seconds is not a positive, use the default value + if args.DurationSeconds <= 0 { + args.DurationSeconds = DEFAULT_ASSUMEROLE_DURATION_SECONDS + } + + if args.AccountId == "" { + return nil, fmt.Errorf("please set accountId") + } + + if args.RoleName == "" { + return nil, fmt.Errorf("please set roleName") + } + + // Build the request + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + REQUEST_ASSUMEROLE_URI) + req.SetMethod(http.POST) + req.SetParam("assumeRole", "") + req.SetParam("durationSeconds", strconv.Itoa(args.DurationSeconds)) + req.SetParam("accountId", args.AccountId) + req.SetParam("roleName", args.RoleName) + + if args.UserId != "" { + req.SetParam("userId", args.UserId) + } + + if len(args.Acl) > 0 { + req.SetHeader(http.CONTENT_TYPE, bce.DEFAULT_CONTENT_TYPE) + body, err := bce.NewBodyFromString(args.Acl) + if err != nil { + return nil, err + } + req.SetBody(body) + } + + // Send requet and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &Credential{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/sts/client.go b/bce-sdk-go/services/sts/client.go new file mode 100644 index 0000000..de40c42 --- /dev/null +++ b/bce-sdk-go/services/sts/client.go @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for STS service which is derived from BceClient + +// Package sts defines the STS service of BCE. +// It contains the model sub package to implement the concrete request and response of the +// GetSessionToken API. +package sts + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/sts/api" + "github.com/baidubce/bce-sdk-go/util" +) + +const DEFAULT_SERVICE_DOMAIN = "sts." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN + +// Client of STS service is a kind of BceClient, so it derived from the BceClient and it only +// supports the GetSessionToken API. There is no other fields needed. +type Client struct { + *bce.BceClient +} + +func (c *Client) GetSessionToken(duration int, acl string) (*api.GetSessionTokenResult, error) { + return api.GetSessionToken(c, duration, acl) +} + +func (c *Client) AssumeRole(args *api.AssumeRoleArgs) (*api.Credential, error) { + return api.AssumeRole(c, args) +} + +// NewClient make the STS service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk string) (*Client, error) { + return NewStsClient(ak, sk, DEFAULT_SERVICE_DOMAIN) +} + +func NewStsClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + Timestamp: util.NowUTCSeconds(), + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} diff --git a/bce-sdk-go/services/sts/client_test.go b/bce-sdk-go/services/sts/client_test.go new file mode 100644 index 0000000..0f721e5 --- /dev/null +++ b/bce-sdk-go/services/sts/client_test.go @@ -0,0 +1,96 @@ +package sts + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util/log" +) + +var CLIENT *Client + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string +} + +func init() { + _, f, _, _ := runtime.Caller(0) + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + CLIENT, _ = NewClient(confObj.AK, confObj.SK) + //log.SetLogHandler(log.STDERR) + //log.SetLogLevel(log.INFO) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestGetSessionToken(t *testing.T) { + res, err := CLIENT.GetSessionToken(-1, "") + ExpectEqual(t.Errorf, err, nil) + t.Logf("%+v", res) + + acl := ` +{ + "id":"10eb6f5ff6ff4605bf044313e8f3ffa5", + "accessControlList": [ + { + "eid": "10eb6f5ff6ff4605bf044313e8f3ffa5-1", + "effect": "Deny", + "resource": ["bos-rd-ssy/*"], + "region": "bj", + "service": "bce:bos", + "permission": ["WRITE"] + } + ] +}` + res, err = CLIENT.GetSessionToken(10, acl) + ExpectEqual(t.Fatalf, err, nil) + t.Logf("ak: %v", res.AccessKeyId) + t.Logf("sk: %v", res.SecretAccessKey) + t.Logf("sessionToken: %v", res.SessionToken) + t.Logf("createTime: %v", res.CreateTime) + t.Logf("expiration: %v", res.Expiration) + t.Logf("userId: %v", res.UserId) +} diff --git a/bce-sdk-go/services/userservice/client.go b/bce-sdk-go/services/userservice/client.go new file mode 100644 index 0000000..43ed3a9 --- /dev/null +++ b/bce-sdk-go/services/userservice/client.go @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for User Service service + +// Package userservice defines the User Service services of BCE. The supported APIs are all defined in sub-package +package userservice + +import ( + "encoding/json" + + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "blb." + bce.DEFAULT_REGION + ".baidubce.com" + URI_PREFIX = bce.URI_PREFIX + "v1" + REQUEST_SERVICE_URL = "/service" +) + +type Client struct { + *bce.BceClient +} + +// NewClient 是一个函数,用于创建一个新的客户端对象 +func NewClient(ak, sk, endPoint string) (*Client, error) { + if endPoint == "" { + endPoint = DEFAULT_SERVICE_DOMAIN + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + + } + return &Client{client}, nil +} + +// userServiceRequest 发起用户服务请求 +func (c *Client) userServiceRequest(action, method, uri, clientToken string, args interface{}) error { + req := &bce.BceRequest{} + req.SetMethod(method) + req.SetUri(uri) + req.SetParam(action, "") + req.SetParam("clientToken", clientToken) + + jsonBytes, err := json.Marshal(args) + if err != nil { + return err + } + jsonBody, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + req.SetBody(jsonBody) + resp := &bce.BceResponse{} + if err := c.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + return nil +} + +// getUserServiceUri 函数返回用户服务的URI地址 +func getUserServiceUri() string { + return URI_PREFIX + REQUEST_SERVICE_URL +} + +// getUserServiceIdUri 返回指定服务的用户服务ID的URI +func getUserServiceIdUri(service string) string { + return URI_PREFIX + REQUEST_SERVICE_URL + "/" + service +} diff --git a/bce-sdk-go/services/userservice/client_test.go b/bce-sdk-go/services/userservice/client_test.go new file mode 100644 index 0000000..c206370 --- /dev/null +++ b/bce-sdk-go/services/userservice/client_test.go @@ -0,0 +1,206 @@ +package userservice + +import ( + "encoding/json" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + USER_SERVICE_CLIENT *Client + + // set these values before start test + VPC_TEST_ID = "" + SUBNET_TEST_ID = "" + INSTANCE_ID = "" + CERT_ID = "" + CLUSTER_ID = "" + CLUSTER_PROPERTY_TEST = "" + TEST_BLB_ID = "lb-xxxxxxxx" + SERVICE = "xxxxxxxxxx.beijing.baidubce.com" +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string + SK string + Endpoint string +} + +// init 函数用于初始化全局变量 +func init() { + _, f, _, _ := runtime.Caller(0) + conf := filepath.Join(filepath.Dir(f), "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + err = decoder.Decode(confObj) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + USER_SERVICE_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.Endpoint) + log.SetLogLevel(log.WARN) +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +// getClientToken 函数返回一个随机的UUID字符串 +func getClientToken() string { + return util.NewUUID() +} + +// TestClient_CreateUserService 用于测试创建用户服务的函数 +func TestClient_CreateUserService(t *testing.T) { + args := &CreateUserServiceArgs{ + ClientToken: getClientToken(), + InstanceId: TEST_BLB_ID, + Name: "test_name", + Description: "test_user_service_description", + ServiceName: "testUserServiceName", + } + + createResult, err := USER_SERVICE_CLIENT.CreateUserService(args) + ExpectEqual(t.Errorf, nil, err) + SERVICE = createResult.Service +} + +// TestClient_UpdateUserService 是一个用于测试客户端函数UpdateUserService的测试函数 +func TestClient_UpdateUserService(t *testing.T) { + + args := &UpdateServiceArgs{ + ClientToken: getClientToken(), + Name: "update_test_name", + Description: "update_test_user_service_description", + } + + err := USER_SERVICE_CLIENT.UpdateUserService(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_UnBindInstance 测试客户端解绑实例函数 +func TestClient_UnBindInstance(t *testing.T) { + + args := &UserServiceUnBindArgs{ + ClientToken: getClientToken(), + } + + err := USER_SERVICE_CLIENT.UserServiceUnBindInstance(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_BindInstance 测试绑定实例函数 +func TestClient_BindInstance(t *testing.T) { + + args := &UserServiceBindArgs{ + ClientToken: getClientToken(), + InstanceId: TEST_BLB_ID, + } + + err := USER_SERVICE_CLIENT.UserServiceBindInstance(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_UserServiceRemoveAuth 测试用户服务移除授权 +func TestClient_UserServiceRemoveAuth(t *testing.T) { + + args := &UserServiceRemoveAuthArgs{ + ClientToken: getClientToken(), + UidList: []string{"7cc5aff841ff4b648028d80000000000"}, + } + + err := USER_SERVICE_CLIENT.UserServiceRemoveAuth(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_UserServiceAddAuth 用于测试用户服务添加权限的方法 +func TestClient_UserServiceAddAuth(t *testing.T) { + + args := &UserServiceAuthArgs{ + ClientToken: getClientToken(), + AuthList: []UserServiceAuthModel{ + { + Uid: "7cc5aff841ff4b648028d80000000000", + Auth: ServiceAuthDeny, + }}, + } + + err := USER_SERVICE_CLIENT.UserServiceAddAuth(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_UserServiceEditAuth 测试UserServiceEditAuth函数 +func TestClient_UserServiceEditAuth(t *testing.T) { + + args := &UserServiceAuthArgs{ + ClientToken: getClientToken(), + AuthList: []UserServiceAuthModel{ + { + Uid: "7cc5aff841ff4b648028d80000000000", + Auth: ServiceAuthAllow, + }}, + } + + err := USER_SERVICE_CLIENT.UserServiceEditAuth(SERVICE, args) + ExpectEqual(t.Errorf, nil, err) +} + +// TestClient_DescribeUserServices 是一个测试函数,用于测试客户端的DescribeUserServices方法 +func TestClient_DescribeUserServices(t *testing.T) { + + args := &DescribeUserServicesArgs{} + + result, err := USER_SERVICE_CLIENT.DescribeUserServices(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 1, len(result.Services)) +} + +// TestClient_DescribeUserServiceDetail 测试函数,用于测试客户端的DescribeUserServiceDetail方法 +func TestClient_DescribeUserServiceDetail(t *testing.T) { + + result, err := USER_SERVICE_CLIENT.DescribeUserServiceDetail(SERVICE) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "available", len(result.Status)) +} + +// TestClient_DeleteUserService 是一个测试函数,用于测试 DeleteUserService 方法是否能够成功删除用户服务。 +func TestClient_DeleteUserService(t *testing.T) { + + err := USER_SERVICE_CLIENT.DeleteUserService(SERVICE) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/userservice/model.go b/bce-sdk-go/services/userservice/model.go new file mode 100644 index 0000000..cd651b5 --- /dev/null +++ b/bce-sdk-go/services/userservice/model.go @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package userservice + +type AuthStatus string + +const ( + ServiceAuthAllow AuthStatus = "allow" + ServiceAuthDeny AuthStatus = "deny" +) + +type CreateUserServiceArgs struct { + ClientToken string `json:"-"` + InstanceId string `json:"instanceId"` + Name string `json:"name"` + Description string `json:"description"` + ServiceName string `json:"serviceName"` + AuthList []UserServiceAuthModel `json:"authList"` +} + +type CreateUserServiceResult struct { + Service string `json:"service"` +} + +type UserServiceAuthModel struct { + Uid string `json:"uid"` + Auth AuthStatus `json:"auth"` +} + +type UpdateServiceArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` +} + +type UserServiceBindArgs struct { + ClientToken string `json:"-"` + InstanceId string `json:"instanceId"` +} + +type UserServiceUnBindArgs struct { + ClientToken string `json:"-"` +} + +type UserServiceAuthArgs struct { + ClientToken string `json:"-"` + AuthList []UserServiceAuthModel `json:"authList"` +} + +type UserServiceRemoveAuthArgs struct { + ClientToken string `json:"-"` + UidList []string `json:"uidList"` +} + +type DescribeUserServicesArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type DescribeUserServicesResult struct { + Services []UserServiceModel `json:"services"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type UserServiceModel struct { + ServiceId string `json:"serviceId"` + Name string `json:"name"` + Description string `json:"description"` + ServiceName string `json:"serviceName"` + BindType string `json:"bindType"` + InstanceId string `json:"instanceId"` + Status string `json:"status"` + Service string `json:"service"` + CreateTime string `json:"createTime"` + EndpointCount int `json:"endpointCount"` + EndpointList []RelatedEndpointModel `json:"endpointList"` + AuthList []UserServiceAuthModel `json:"authList"` +} + +type RelatedEndpointModel struct { + EndpointId string `json:"endpointId"` + Uid string `json:"uid"` + AttachTime string `json:"attachTime"` +} + +type DescribeUserServiceDetailResult struct { + UserServiceModel +} diff --git a/bce-sdk-go/services/userservice/userservice.go b/bce-sdk-go/services/userservice/userservice.go new file mode 100644 index 0000000..9cb87d9 --- /dev/null +++ b/bce-sdk-go/services/userservice/userservice.go @@ -0,0 +1,306 @@ +/* + * Copyright 2024 Baidu, Inc. + * + * 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. + */ + +// userservice.go - the User Service APIs definition supported by the User Service service + +package userservice + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateUserService - create a User Service +// +// PARAMS: +// - args: parameters to create User Service +// +// RETURNS: +// - *CreateUserServiceResult: the result of create User Service, contains new Service Domain +// - error: nil if ok otherwise the specific error +func (c *Client) CreateUserService(args *CreateUserServiceArgs) (*CreateUserServiceResult, error) { + if args == nil || len(args.Name) == 0 { + return nil, fmt.Errorf("unset name") + } + + if len(args.ServiceName) == 0 { + return nil, fmt.Errorf("unset service name") + } + + if len(args.InstanceId) == 0 { + return nil, fmt.Errorf("unset instance id") + } + + if len(args.AuthList) > 0 { + for i := range args.AuthList { + authModel := args.AuthList[i] + if authModel.Uid == "" { + return nil, fmt.Errorf("unset uid") + } + if authModel.Auth != ServiceAuthAllow && authModel.Auth != ServiceAuthDeny { + return nil, fmt.Errorf("invalid auth") + } + } + } else { + args.AuthList = []UserServiceAuthModel{ + { + Uid: "*", + Auth: ServiceAuthDeny, + }} + } + + result := &CreateUserServiceResult{} + err := bce.NewRequestBuilder(c). + WithMethod(http.POST). + WithURL(getUserServiceUri()). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + + return result, err +} + +// UpdateUserService - update a User Service +// +// PARAMS: +// - service: Service Domain +// - args: parameters to update User Service +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UpdateUserService(service string, args *UpdateServiceArgs) error { + if args == nil { + args = &UpdateServiceArgs{} + } + + if len(service) == 0 { + return fmt.Errorf("unset service") + } + + return c.userServiceRequest("modifyAttribute", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) +} + +// UserServiceBindInstance - User Service bind BLB instance +// +// PARAMS: +// - service: Service Domain +// - args: parameters to bind blb instance id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UserServiceBindInstance(service string, args *UserServiceBindArgs) error { + if args == nil { + args = &UserServiceBindArgs{} + } + + if len(service) == 0 { + return fmt.Errorf("unset service") + } + + if len(args.InstanceId) == 0 { + return fmt.Errorf("unset instance id") + } + + return c.userServiceRequest("bind", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) +} + +// UserServiceUnBindInstance - User Service unbind BLB instance +// +// PARAMS: +// - service: Service Domain +// - args: parameters to unbind blb instance id +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UserServiceUnBindInstance(service string, args *UserServiceUnBindArgs) error { + if args == nil { + args = &UserServiceUnBindArgs{} + } + + if len(service) == 0 { + return fmt.Errorf("unset service") + } + + return c.userServiceRequest("unbind", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) +} + +// UserServiceAddAuth - add auth to User Service +// +// PARAMS: +// - service: Service Domain +// - args: parameters to add auth +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UserServiceAddAuth(service string, args *UserServiceAuthArgs) error { + if args == nil { + args = &UserServiceAuthArgs{} + } + + if len(args.AuthList) == 0 { + return fmt.Errorf("unset auth list") + } + + for i := range args.AuthList { + authModel := args.AuthList[i] + if authModel.Uid == "" { + return fmt.Errorf("unset uid") + } + if authModel.Auth != ServiceAuthAllow && authModel.Auth != ServiceAuthDeny { + return fmt.Errorf("invalid auth") + } + } + + return c.userServiceRequest("addAuth", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) +} + +// UserServiceEditAuth - edit auth to User Service +// +// PARAMS: +// - service: Service Domain +// - args: parameters to edit auth +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UserServiceEditAuth(service string, args *UserServiceAuthArgs) error { + if args == nil { + args = &UserServiceAuthArgs{} + } + + if len(service) == 0 { + return fmt.Errorf("unset service") + } + + if len(args.AuthList) == 0 { + return fmt.Errorf("unset auth list") + } + + for i := range args.AuthList { + authModel := args.AuthList[i] + if authModel.Uid == "" { + return fmt.Errorf("unset uid") + } + if authModel.Auth != ServiceAuthAllow && authModel.Auth != ServiceAuthDeny { + return fmt.Errorf("invalid auth") + } + } + + return c.userServiceRequest("editAuth", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) + +} + +// UserServiceRemoveAuth - Remove Auth to User Service +// +// PARAMS: +// - service: Service Domain +// - args: parameters to remove auth +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) UserServiceRemoveAuth(service string, args *UserServiceRemoveAuthArgs) error { + if args == nil { + args = &UserServiceRemoveAuthArgs{} + } + + if len(service) == 0 { + return fmt.Errorf("unset service") + } + + if len(args.UidList) == 0 { + return fmt.Errorf("unset auth list") + } + + for i := range args.UidList { + uid := args.UidList[i] + if uid == "" { + return fmt.Errorf("unset uid") + } + } + + return c.userServiceRequest("removeAuth", http.PUT, getUserServiceIdUri(service), args.ClientToken, args) +} + +// DescribeUserServices - describe all User Services +// +// PARAMS: +// - args: parameters to describe all User Services +// +// RETURNS: +// - *DescribeUserServicesResult: the result all User Services's detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeUserServices(args *DescribeUserServicesArgs) (*DescribeUserServicesResult, error) { + if args == nil { + args = &DescribeUserServicesArgs{} + } + + if args.MaxKeys > 1000 || args.MaxKeys <= 0 { + args.MaxKeys = 1000 + } + + result := &DescribeUserServicesResult{} + request := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getUserServiceUri()). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result) + + err := request.Do() + return result, err +} + +// DescribeUserServiceDetail - describe a User Service +// +// PARAMS: +// - service: describe Service Domain +// +// RETURNS: +// - *DescribeServiceDetailResult: the result Service detail +// - error: nil if ok otherwise the specific error +func (c *Client) DescribeUserServiceDetail(service string) (*DescribeUserServiceDetailResult, error) { + result := &DescribeUserServiceDetailResult{} + + if len(service) == 0 { + return nil, fmt.Errorf("unset service") + } + + err := bce.NewRequestBuilder(c). + WithMethod(http.GET). + WithURL(getUserServiceIdUri(service)). + WithResult(result). + Do() + + return result, err +} + +// DeleteUserService - delete a User Service +// +// PARAMS: +// - blbId: parameters to delete Service Domain +// +// RETURNS: +// - error: nil if ok otherwise the specific error +func (c *Client) DeleteUserService(service string) error { + if len(service) == 0 { + return fmt.Errorf("unset service") + } + return bce.NewRequestBuilder(c). + WithMethod(http.DELETE). + WithURL(getUserServiceIdUri(service)). + Do() +} diff --git a/bce-sdk-go/services/vca/api/media.go b/bce-sdk-go/services/vca/api/media.go new file mode 100644 index 0000000..36f496c --- /dev/null +++ b/bce-sdk-go/services/vca/api/media.go @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// media.go - the media APIs definition supported by the VCA service + +// Package api defines all APIs supported by the VCA service of BCE. +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func PutMedia(cli bce.Client, args *PutMediaArgs) (*GetMediaResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + MEDIA_URI) + req.SetMethod(http.PUT) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetMediaResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} + +func GetMedia(cli bce.Client, source string) (*GetMediaResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + MEDIA_URI) + req.SetMethod(http.GET) + req.SetParam("source", source) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetMediaResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/vca/api/model.go b/bce-sdk-go/services/vca/api/model.go new file mode 100644 index 0000000..0140859 --- /dev/null +++ b/bce-sdk-go/services/vca/api/model.go @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model for VCA + +package api + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + MEDIA_URI = "/media" +) + +type PutMediaArgs struct { + Source string `json:"source"` + Auth string `json:"auth"` + Title string `json:"title"` + Description string `json:"description"` + Preset string `json:"preset"` + Notification string `json:"notification"` +} + +type GetMediaResult struct { + Source string `json:"source"` + MediaId string `json:"mediaId"` + Title string `json:"title"` + Description string `json:"description"` + Preset string `json:"preset"` + Status string `json:"status"` + Percent int `json:"percent"` + Notification string `json:"notification"` + CreateTime string `json:"createTime"` + StartTime string `json:"startTime"` + PublishTime string `json:"publishTime"` + Results []AnalyzeResult `json:"results"` + Error AnalyzeError `json:"error"` +} + +type AnalyzeResult struct { + Type string `json:"type"` + Attributes []AnalyzeAttribute `json:"result"` +} + +type AnalyzeAttribute struct { + Attribute string `json:"attribute"` + Confidence float32 `json:"confidence"` + Source string `json:"source"` + AttributeTime []TimePeriod `json:"time"` +} + +type TimePeriod struct { + Start int `json:"start"` + End int `json:"end"` +} + +type AnalyzeError struct { + Code string `json:"code"` + Message string `json:"message"` +} diff --git a/bce-sdk-go/services/vca/client.go b/bce-sdk-go/services/vca/client.go new file mode 100644 index 0000000..89d97b1 --- /dev/null +++ b/bce-sdk-go/services/vca/client.go @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for VCA service + +// Package vca defines the VCA services of BCE. The supported APIs are all defined in sub-package +package vca + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/vca/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "vca." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of VCR service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the VCR service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func (c *Client) PutMedia(args *api.PutMediaArgs) (*api.GetMediaResult, error) { + return api.PutMedia(c, args) +} + +func (c *Client) GetMedia(source string) (*api.GetMediaResult, error) { + return api.GetMedia(c, source) +} diff --git a/bce-sdk-go/services/vca/client_test.go b/bce-sdk-go/services/vca/client_test.go new file mode 100644 index 0000000..e7c8820 --- /dev/null +++ b/bce-sdk-go/services/vca/client_test.go @@ -0,0 +1,43 @@ +package vca + +import ( + "github.com/baidubce/bce-sdk-go/services/vca/api" + "github.com/baidubce/bce-sdk-go/util/log" + "testing" +) + +var CLIENT *Client + +const ( + AK = "YourAK" + SK = "YourSK" + MEDIA_SOURCE = "YourTestMedia" // e.g. "bos://YourBucket/dir/video.mp4 +) + +func init() { + CLIENT, _ = NewClient(AK, SK, "") + //log.SetLogHandler(log.STDERR | log.FILE) + //log.SetRotateType(log.ROTATE_SIZE) + //log.SetLogLevel(log.WARN) + + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) +} + +func TestPutMedia(t *testing.T) { + preset := "default" + args := &api.PutMediaArgs{Source: MEDIA_SOURCE, Preset: preset} + if res, err := CLIENT.PutMedia(args); err != nil { + t.Log("put media error") + } else { + t.Logf("%+v", res) + } +} + +func TestGetMedia(t *testing.T) { + if res, err := CLIENT.GetMedia(MEDIA_SOURCE); err != nil { + t.Log("get media error") + } else { + t.Logf("%+v", res) + } +} diff --git a/bce-sdk-go/services/vcr/api/image.go b/bce-sdk-go/services/vcr/api/image.go new file mode 100644 index 0000000..8617c5f --- /dev/null +++ b/bce-sdk-go/services/vcr/api/image.go @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Baidu, Inc. + * + * 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. + */ + +// image.go - the image APIs definition supported by the VCR service + +// Package api defines all APIs supported by the VCR service of BCE. +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func PutImageSync(cli bce.Client, args *PutImageSyncArgs) (*PutImageSyncResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + IMAGE_URI) + req.SetMethod(http.PUT) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &PutImageSyncResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/vcr/api/media.go b/bce-sdk-go/services/vcr/api/media.go new file mode 100644 index 0000000..2e6e095 --- /dev/null +++ b/bce-sdk-go/services/vcr/api/media.go @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// media.go - the media APIs definition supported by the VCR service + +// Package api defines all APIs supported by the VCR service of BCE. +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func PutMedia(cli bce.Client, args *PutMediaArgs) error { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return err + } + + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + MEDIA_URI) + req.SetMethod(http.PUT) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return err + } + if resp.IsFail() { + return resp.ServiceError() + } + defer func() { resp.Body().Close() }() + return nil +} + +func GetMedia(cli bce.Client, source string) (*GetMediaResult, error) { + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + MEDIA_URI) + req.SetMethod(http.GET) + req.SetParam("source", source) + + // Send request and get response + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &GetMediaResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/vcr/api/model.go b/bce-sdk-go/services/vcr/api/model.go new file mode 100644 index 0000000..5ca6abe --- /dev/null +++ b/bce-sdk-go/services/vcr/api/model.go @@ -0,0 +1,106 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model for VCR + +package api + +import "github.com/baidubce/bce-sdk-go/bce" + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + MEDIA_URI = "/media" + TEXT_URI = "/text" + IMAGE_URI = "/image" +) + +type PutMediaArgs struct { + Source string `json:"source"` + Auth string `json:"auth"` + Description string `json:"description"` + Preset string `json:"preset"` + Notification string `json:"notification"` +} + +type GetMediaResult struct { + Source string `json:"source"` + MediaId string `json:"mediaId"` + Description string `json:"description"` + Preset string `json:"preset"` + Status string `json:"status"` + Percent int `json:"percent"` + Notification string `json:"notification"` + CreateTime string `json:"createTime"` + FinishTime string `json:"finishTime"` + Label string `json:"label"` + Results []CheckResult `json:"results"` + Error CheckError `json:"error"` +} + +type CheckResult struct { + Type string `json:"type"` + Items []ResultItem `json:"items"` +} + +type ResultItem struct { + SubType string `json:"subType"` + Target string `json:"target"` + TimeInSeconds int `json:"timeInSeconds"` + Confidence float32 `json:"confidence"` + Label string `json:"label"` + Extra string `json:"extra"` + Evidence Evidence `json:"evidence"` +} + +type Evidence struct { + Thumbnail string `json:"thumbnail"` + Location Location `json:"location"` + Text string `json:"text"` +} + +type Location struct { + LeftOffsetInPixel int `json:"leftOffsetInPixel"` + TopOffsetInPixel int `json:"topOffsetInPixel"` + WidthInPixel int `json:"widthInPixel"` + HeightInPixel int `json:"heightInPixel"` +} + +type CheckError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type PutTextArgs struct { + Text string `json:"text"` + Preset string `json:"preset"` +} + +type PutTextResult struct { + Text string `json:"text"` + Preset string `json:"preset"` + Results []CheckResult `json:"results"` +} + +type PutImageSyncArgs struct { + Source string `json:"source"` + Preset string `json:"preset"` +} + +type PutImageSyncResult struct { + Source string `json:"source"` + Label string `json:"label"` + Preset string `json:"preset"` + Status string `json:"status"` + Results []CheckResult `json:"results"` +} diff --git a/bce-sdk-go/services/vcr/api/text.go b/bce-sdk-go/services/vcr/api/text.go new file mode 100644 index 0000000..c00c16a --- /dev/null +++ b/bce-sdk-go/services/vcr/api/text.go @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// media.go - the media APIs definition supported by the VCR service + +// Package api defines all APIs supported by the VCR service of BCE. +package api + +import ( + "encoding/json" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +func PutText(cli bce.Client, args *PutTextArgs) (*PutTextResult, error) { + jsonBytes, jsonErr := json.Marshal(args) + if jsonErr != nil { + return nil, jsonErr + } + body, err := bce.NewBodyFromBytes(jsonBytes) + if err != nil { + return nil, err + } + + req := &bce.BceRequest{} + req.SetUri(URI_PREFIX + TEXT_URI) + req.SetMethod(http.PUT) + req.SetBody(body) + + resp := &bce.BceResponse{} + if err := cli.SendRequest(req, resp); err != nil { + return nil, err + } + if resp.IsFail() { + return nil, resp.ServiceError() + } + jsonBody := &PutTextResult{} + if err := resp.ParseJsonBody(jsonBody); err != nil { + return nil, err + } + return jsonBody, nil +} diff --git a/bce-sdk-go/services/vcr/client.go b/bce-sdk-go/services/vcr/client.go new file mode 100644 index 0000000..01df208 --- /dev/null +++ b/bce-sdk-go/services/vcr/client.go @@ -0,0 +1,91 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for VCR service + +// Package vcr defines the VCR services of BCE. The supported APIs are all defined in sub-package +package vcr + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/services/vcr/api" +) + +const ( + DEFAULT_SERVICE_DOMAIN = "vcr." + bce.DEFAULT_REGION + "." + bce.DEFAULT_DOMAIN +) + +// Client of VCR service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +// NewClient make the VCR service client with default configuration. +// Use `cli.Config.xxx` to access the config or change it to non-default value. +func NewClient(ak, sk, endpoint string) (*Client, error) { + credentials, err := auth.NewBceCredentials(ak, sk) + if err != nil { + return nil, err + } + if len(endpoint) == 0 { + endpoint = DEFAULT_SERVICE_DOMAIN + } + defaultSignOptions := &auth.SignOptions{ + HeadersToSign: auth.DEFAULT_HEADERS_TO_SIGN, + ExpireSeconds: auth.DEFAULT_EXPIRE_SECONDS} + defaultConf := &bce.BceClientConfiguration{ + Endpoint: endpoint, + Region: bce.DEFAULT_REGION, + UserAgent: bce.DEFAULT_USER_AGENT, + Credentials: credentials, + SignOption: defaultSignOptions, + Retry: bce.DEFAULT_RETRY_POLICY, + ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS} + v1Signer := &auth.BceV1Signer{} + + client := &Client{bce.NewBceClient(defaultConf, v1Signer)} + return client, nil +} + +func (c *Client) PutMedia(args *api.PutMediaArgs) error { + return api.PutMedia(c, args) +} + +func (c *Client) SimplePutMedia(source string, description string, preset string, notification string) error { + args := &api.PutMediaArgs{Source: source, Description: description, Preset: preset, Notification: notification} + return api.PutMedia(c, args) +} + +func (c *Client) GetMedia(source string) (*api.GetMediaResult, error) { + return api.GetMedia(c, source) +} + +func (c *Client) PutText(args *api.PutTextArgs) (*api.PutTextResult, error) { + return api.PutText(c, args) +} + +func (c *Client) SimplePutText(text string) (*api.PutTextResult, error) { + args := &api.PutTextArgs{Text: text} + return api.PutText(c, args) +} + +func (c *Client) PutImageSync(args *api.PutImageSyncArgs) (*api.PutImageSyncResult, error) { + return api.PutImageSync(c, args) +} + +func (c *Client) SimplePutImageSync(source string) (*api.PutImageSyncResult, error) { + args := &api.PutImageSyncArgs{Source: source} + return api.PutImageSync(c, args) +} diff --git a/bce-sdk-go/services/vcr/client_test.go b/bce-sdk-go/services/vcr/client_test.go new file mode 100644 index 0000000..87136b4 --- /dev/null +++ b/bce-sdk-go/services/vcr/client_test.go @@ -0,0 +1,88 @@ +package vcr + +import ( + "github.com/baidubce/bce-sdk-go/services/vcr/api" + "github.com/baidubce/bce-sdk-go/util/log" + "testing" +) + +var CLIENT *Client + +const ( + AK = "YourAK" + SK = "YourSK" + MEDIA_SOURCE = "YourTestMedia" // e.g. "bos://YourBucket/dir/video.mp4 + TEXT = "YourTestText" + IMAGE_SOURCE = "YourTestImage" // e.g. "bos://YourBucket/dir/image.jpg" +) + +func init() { + CLIENT, _ = NewClient(AK, SK, "") + //log.SetLogHandler(log.STDERR | log.FILE) + //log.SetRotateType(log.ROTATE_SIZE) + //log.SetLogLevel(log.WARN) + + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) +} + +func TestPutMedia(t *testing.T) { + preset := "default" + desc := "vcr_test" + args := &api.PutMediaArgs{Source: MEDIA_SOURCE, Preset: preset, Description: desc} + if err := CLIENT.PutMedia(args); err != nil { + t.Log("put media error") + } else { + t.Log("put media success") + } +} + +func TestSimplePutMedia(t *testing.T) { + if err := CLIENT.SimplePutMedia(MEDIA_SOURCE, "", "", ""); err != nil { + t.Log("simple put media error") + } else { + t.Log("simple put media success") + } +} + +func TestGetMedia(t *testing.T) { + if res, err := CLIENT.GetMedia(MEDIA_SOURCE); err != nil { + t.Log("get media error") + } else { + t.Logf("%+v", res) + } +} + +func TestPutText(t *testing.T) { + args := &api.PutTextArgs{Text: TEXT} + if res, err := CLIENT.PutText(args); err != nil { + t.Log("simple put text error") + } else { + t.Logf("%+v", res) + } +} + +func TestSimplePutText(t *testing.T) { + if res, err := CLIENT.SimplePutText(TEXT); err != nil { + t.Log("simple put text error") + } else { + t.Logf("%+v", res) + } +} + +func TestPutImageSync(t *testing.T) { + args := &api.PutImageSyncArgs{Source: IMAGE_SOURCE} + if res, err := CLIENT.PutImageSync(args); err != nil { + t.Log("simple put image sync error", err) + } else { + t.Logf("%+v", res) + } +} + +func TestSimplePutImageSync(t *testing.T) { + if res, err := CLIENT.SimplePutImageSync(IMAGE_SOURCE); err != nil { + t.Log("simple put image sync error", err) + } else { + t.Logf("%+v", res) + } +} diff --git a/bce-sdk-go/services/vpc/acl.go b/bce-sdk-go/services/vpc/acl.go new file mode 100644 index 0000000..e542340 --- /dev/null +++ b/bce-sdk-go/services/vpc/acl.go @@ -0,0 +1,127 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// acl.go - the acl APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// ListAclEntrys - list all acl entrys of the given VPC +// +// PARAMS: +// - vpcId: the id of the specific VPC +// +// RETURNS: +// - *ListAclEntrysResult: the result of all acl entrys +// - error: nil if success otherwise the specific error +func (c *Client) ListAclEntrys(vpcId string) (*ListAclEntrysResult, error) { + result := &ListAclEntrysResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForAclEntry()). + WithMethod(http.GET). + WithQueryParam("vpcId", vpcId). + WithResult(result). + Do() + + return result, err +} + +// CreateAclRule - create a new acl rule with the specific parameters +// +// PARAMS: +// - args: the arguments to create acl rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateAclRule(args *CreateAclRuleArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForAclRule()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// ListAclRules - list all acl rules with the specific parameters +// +// PARAMS: +// - args: the arguments to list all acl rules +// +// RETURNS: +// - *ListAclRulesResult: the result of all acl rules +// - error: nil if success otherwise the specific error +func (c *Client) ListAclRules(args *ListAclRulesArgs) (*ListAclRulesResult, error) { + if args == nil { + return nil, fmt.Errorf("The listAclRulesArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListAclRulesResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForAclRule()). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithQueryParam("subnetId", args.SubnetId). + WithResult(result). + Do() + + return result, err +} + +// UpdateAclRule - udpate acl rule with the specific parameters +// +// PARAMS: +// - aclRuleId: the id of the specific acl rule +// - args: the arguments to update acl rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateAclRule(aclRuleId string, args *UpdateAclRuleArgs) error { + if args == nil { + args = &UpdateAclRuleArgs{} + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForAclRuleId(aclRuleId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// DeleteAclRule - delete the specific acl rule +// +// PARAMS: +// - aclRuleId: the id of the specific acl rule +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteAclRule(aclRuleId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForAclRuleId(aclRuleId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} diff --git a/bce-sdk-go/services/vpc/client.go b/bce-sdk-go/services/vpc/client.go new file mode 100644 index 0000000..5322d0e --- /dev/null +++ b/bce-sdk-go/services/vpc/client.go @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for VPC service + +// Package vpc defines the VPC services of BCE. +// The supported APIs are all defined in different files. +package vpc + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_VPC_URL = "/vpc" + REQUEST_SUBNET_URL = "/subnet" + REQUEST_ROUTE_URL = "/route" + REQUEST_RULE_URL = "/rule" + REQUEST_ACL_URL = "/acl" + REQUEST_NAT_URL = "/nat" + REQUEST_PEERCONN_URL = "/peerconn" + REQUEST_NETWORK_TOPOLOGY_URL = "/topology" + REQUEST_PROBE_URL = "/probe" + REQUEST_NETWORK_IPV6GATEWAY_URL = "/IPv6Gateway" +) + +// Client of VPC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{BceClient: client}, nil +} + +func getURLForVPC() string { + return URI_PREFIX + REQUEST_VPC_URL +} + +func getURLForVPCId(vpcId string) string { + return getURLForVPC() + "/" + vpcId +} + +func getURLForSubnet() string { + return URI_PREFIX + REQUEST_SUBNET_URL +} + +func getURLForSubnetId(subnetId string) string { + return getURLForSubnet() + "/" + subnetId +} + +// getURLForIpreserve 返回一个包含 /ipreserve 的请求 URL +func getURLForIpreserve() string { + // URI_PREFIX + REQUEST_SUBNET_URL + "/ipreserve" + return getURLForSubnet() + "/ipreserve" +} + +// getURLForDeleteIpreserve returns the url for deleting an IP preserve. +func getURLForDeleteIpreserve(ipReserveId string) string { + // URI_PREFIX + REQUEST_SUBNET_URL + "/ipreserve/" + ipReserveId + return getURLForSubnet() + "/ipreserve/" + ipReserveId +} + +func getURLForRouteTable() string { + return URI_PREFIX + REQUEST_ROUTE_URL +} + +func getURLForRouteRule() string { + return getURLForRouteTable() + REQUEST_RULE_URL +} + +func getURLForRouteRuleId(routeRuleId string) string { + return getURLForRouteRule() + "/" + routeRuleId +} + +func getURLForAclEntry() string { + return URI_PREFIX + REQUEST_ACL_URL +} + +func getURLForAclRule() string { + return URI_PREFIX + REQUEST_ACL_URL + REQUEST_RULE_URL +} + +func getURLForAclRuleId(aclRuleId string) string { + return URI_PREFIX + REQUEST_ACL_URL + REQUEST_RULE_URL + "/" + aclRuleId +} + +func getURLForNat() string { + return URI_PREFIX + REQUEST_NAT_URL +} + +func getURLForNatId(natId string) string { + return getURLForNat() + "/" + natId +} + +func getURLForPeerConn() string { + return URI_PREFIX + REQUEST_PEERCONN_URL +} + +func getURLForPeerConnId(peerConnId string) string { + return getURLForPeerConn() + "/" + peerConnId +} + +// getURLForNetworkTopology 获取网络拓扑信息的URL +func getURLForNetworkTopology() string { + return getURLForVPC() + REQUEST_NETWORK_TOPOLOGY_URL +} + +func getURLForProbe() string { + return URI_PREFIX + REQUEST_PROBE_URL +} + +func getURLForProbeId(probeId string) string { + return getURLForProbe() + "/" + probeId +} + +func getURLForIpv6Gateway() string { + return URI_PREFIX + REQUEST_NETWORK_IPV6GATEWAY_URL +} + +func getURLForIpv6GatewayId(ipv6GatewayId string) string { + return getURLForIpv6Gateway() + "/" + ipv6GatewayId +} diff --git a/bce-sdk-go/services/vpc/client_test.go b/bce-sdk-go/services/vpc/client_test.go new file mode 100644 index 0000000..85ab1fb --- /dev/null +++ b/bce-sdk-go/services/vpc/client_test.go @@ -0,0 +1,1125 @@ +package vpc + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + "time" + + "github.com/baidubce/bce-sdk-go/model" + "github.com/baidubce/bce-sdk-go/services/eip" + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + VPC_CLIENT *Client + EIP_CLIENT *eip.Client + + region string + + VPCID string + SubnetID string + RouteTableID string + RouteRuleID string + AclRuleID string + NatID string + PeerConnID string + LocalIfID string + PeerVPCID string + EIPAddress string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + VPCEndpoint string `json:"VPC"` + EIPEndpoint string `json:"EIP"` +} + +func init() { + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + //for i := 0; i < 7; i++ { + // f = filepath.Dir(f) + //} + f = filepath.Dir(f) + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + VPC_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.VPCEndpoint) + EIP_CLIENT, _ = eip.NewClient(confObj.AK, confObj.SK, confObj.EIPEndpoint) + log.SetLogLevel(log.WARN) + + region = confObj.VPCEndpoint[4:6] +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestCreateVPCDhcp(t *testing.T) { + VPCID = "" + args := &CreateVpcDhcpArgs{ + DomainNameServers: "3.3.3.5", + ClientToken: getClientToken(), + } + err := VPC_CLIENT.CreateVPCDhcp(VPCID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestUpdateVPCDhcp(t *testing.T) { + VPCID = "" + args := &UpdateVpcDhcpArgs{ + DomainNameServers: "", + ClientToken: getClientToken(), + } + err := VPC_CLIENT.UpdateVPCDhcp(VPCID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetVPCDhcp(t *testing.T) { + VPCID = "" + result, err := VPC_CLIENT.GetVPCDhcpInfo(VPCID) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestCreateVPC(t *testing.T) { + args := &CreateVPCArgs{ + Name: "TestSDK-VPC", + Description: "vpc test", + Cidr: "192.168.0.0/16", + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + ClientToken: getClientToken(), + } + result, err := VPC_CLIENT.CreateVPC(args) + ExpectEqual(t.Errorf, nil, err) + + VPCID = result.VPCID +} + +func TestListVPC(t *testing.T) { + args := &ListVPCArgs{ + MaxKeys: 1000, + IsDefault: "false", + } + res, err := VPC_CLIENT.ListVPC(args) + fmt.Println(res.VPCs[0].CreatedTime) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) +} + +func TestGetVPCDetail(t *testing.T) { + result, err := VPC_CLIENT.GetVPCDetail(VPCID) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) + ExpectEqual(t.Errorf, "TestSDK-VPC", result.VPC.Name) + ExpectEqual(t.Errorf, "vpc test", result.VPC.Description) + ExpectEqual(t.Errorf, "192.168.0.0/16", result.VPC.Cidr) + ExpectEqual(t.Errorf, 1, len(result.VPC.Tags)) + ExpectEqual(t.Errorf, "tagK", result.VPC.Tags[0].TagKey) + ExpectEqual(t.Errorf, "tagV", result.VPC.Tags[0].TagValue) +} + +func TestUpdateVPC(t *testing.T) { + args := &UpdateVPCArgs{ + Name: "TestSDK-VPC-update", + Description: "vpc update", + ClientToken: getClientToken(), + } + err := VPC_CLIENT.UpdateVPC(VPCID, args) + ExpectEqual(t.Errorf, nil, err) + + result, err := VPC_CLIENT.GetVPCDetail(VPCID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "TestSDK-VPC-update", result.VPC.Name) + ExpectEqual(t.Errorf, "vpc update", result.VPC.Description) +} + +func TestGetPrivateIpAddressInfo(t *testing.T) { + args := &GetVpcPrivateIpArgs{ + VpcId: "vpc-2pa2x0bjt26i", + PrivateIpAddresses: []string{"192.168.0.1,192.168.0.2"}, + PrivateIpRange: "192.168.0.0-192.168.0.45", + } + result, err := VPC_CLIENT.GetPrivateIpAddressesInfo(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} +func TestCreateSubnet(t *testing.T) { + args := &CreateSubnetArgs{ + Name: "TestSDK-Subnet", + ZoneName: fmt.Sprintf("cn-%s-a", region), + Cidr: "192.168.1.0/24", + VpcId: VPCID, + SubnetType: SUBNET_TYPE_BCC, + Description: "test subnet", + EnableIpv6: true, + Tags: []model.TagModel{ + { + TagKey: "tagK", + TagValue: "tagV", + }, + }, + ClientToken: getClientToken(), + } + result, err := VPC_CLIENT.CreateSubnet(args) + ExpectEqual(t.Errorf, nil, err) + + SubnetID = result.SubnetId +} + +func TestListSubnets(t *testing.T) { + args := &ListSubnetArgs{ + VpcId: VPCID, + SubnetType: SUBNET_TYPE_BCC, + } + _, err := VPC_CLIENT.ListSubnets(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetSubnetDetail(t *testing.T) { + result, err := VPC_CLIENT.GetSubnetDetail(SubnetID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "TestSDK-Subnet", result.Subnet.Name) + ExpectEqual(t.Errorf, fmt.Sprintf("cn-%s-a", region), result.Subnet.ZoneName) + ExpectEqual(t.Errorf, "192.168.1.0/24", result.Subnet.Cidr) + ExpectEqual(t.Errorf, VPCID, result.Subnet.VPCId) + ExpectEqual(t.Errorf, SUBNET_TYPE_BCC, result.Subnet.SubnetType) + ExpectEqual(t.Errorf, "test subnet", result.Subnet.Description) + ExpectEqual(t.Errorf, 1, len(result.Subnet.Tags)) + ExpectEqual(t.Errorf, "tagK", result.Subnet.Tags[0].TagKey) + ExpectEqual(t.Errorf, "tagV", result.Subnet.Tags[0].TagValue) +} + +func TestUpdateSubnet(t *testing.T) { + args := &UpdateSubnetArgs{ + ClientToken: getClientToken(), + Name: "TestSDK-Subnet-update", + Description: "subnet update", + EnableIpv6: true, + } + err := VPC_CLIENT.UpdateSubnet(SubnetID, args) + ExpectEqual(t.Errorf, nil, err) + + result, err := VPC_CLIENT.GetSubnetDetail(SubnetID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "TestSDK-Subnet-update", result.Subnet.Name) + ExpectEqual(t.Errorf, "subnet update", result.Subnet.Description) +} + +func TestListAclEntrys(t *testing.T) { + _, err := VPC_CLIENT.ListAclEntrys(VPCID) + ExpectEqual(t.Errorf, nil, err) +} + +func TestCreateAclRule(t *testing.T) { + args := &CreateAclRuleArgs{ + ClientToken: getClientToken(), + AclRules: []AclRuleRequest{ + { + SubnetId: SubnetID, + Description: "test acl rule", + Protocol: ACL_RULE_PROTOCOL_TCP, + SourceIpAddress: "192.168.1.1", + DestinationIpAddress: "172.17.1.1", + SourcePort: "5555", + DestinationPort: "6666", + Position: 10, + Direction: ACL_RULE_DIRECTION_EGRESS, + Action: ACL_RULE_ACTION_ALLOW, + }, + }, + } + err := VPC_CLIENT.CreateAclRule(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestListAclRules(t *testing.T) { + args := &ListAclRulesArgs{ + SubnetId: SubnetID, + } + result, err := VPC_CLIENT.ListAclRules(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 3, len(result.AclRules)) + for _, acl := range result.AclRules { + if acl.Position == 10 { + ExpectEqual(t.Errorf, SubnetID, acl.SubnetId) + ExpectEqual(t.Errorf, "test acl rule", acl.Description) + ExpectEqual(t.Errorf, ACL_RULE_PROTOCOL_TCP, acl.Protocol) + ExpectEqual(t.Errorf, "192.168.1.1/32", acl.SourceIpAddress) + ExpectEqual(t.Errorf, "172.17.1.1/32", acl.DestinationIpAddress) + ExpectEqual(t.Errorf, "5555", acl.SourcePort) + ExpectEqual(t.Errorf, "6666", acl.DestinationPort) + ExpectEqual(t.Errorf, ACL_RULE_DIRECTION_EGRESS, acl.Direction) + ExpectEqual(t.Errorf, ACL_RULE_ACTION_ALLOW, acl.Action) + AclRuleID = acl.Id + break + } + } + if AclRuleID == "" { + t.Errorf("Test acl rule failed.") + } +} + +func TestUpdateAclRule(t *testing.T) { + args := &UpdateAclRuleArgs{ + ClientToken: getClientToken(), + Description: "acl rule update", + Protocol: ACL_RULE_PROTOCOL_UDP, + SourceIpAddress: "192.168.1.2", + DestinationIpAddress: "172.17.1.2", + SourcePort: "7777", + DestinationPort: "8888", + Position: 10, + Action: ACL_RULE_ACTION_DENY, + } + err := VPC_CLIENT.UpdateAclRule(AclRuleID, args) + ExpectEqual(t.Errorf, nil, err) + + listAclArgs := &ListAclRulesArgs{SubnetId: SubnetID} + result, err := VPC_CLIENT.ListAclRules(listAclArgs) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 3, len(result.AclRules)) + isExist := false + for _, acl := range result.AclRules { + if acl.Position == 10 { + isExist = true + ExpectEqual(t.Errorf, SubnetID, acl.SubnetId) + ExpectEqual(t.Errorf, "acl rule update", acl.Description) + ExpectEqual(t.Errorf, ACL_RULE_PROTOCOL_UDP, acl.Protocol) + ExpectEqual(t.Errorf, "192.168.1.2/32", acl.SourceIpAddress) + ExpectEqual(t.Errorf, "172.17.1.2/32", acl.DestinationIpAddress) + ExpectEqual(t.Errorf, "7777", acl.SourcePort) + ExpectEqual(t.Errorf, "8888", acl.DestinationPort) + ExpectEqual(t.Errorf, ACL_RULE_ACTION_DENY, acl.Action) + break + } + } + if !isExist { + t.Errorf("Test acl rule failed.") + } +} + +func TestCreateDefaultNatGateway(t *testing.T) { + args := &CreateNatGatewayArgs{ + ClientToken: getClientToken(), + Name: "Test-SDK-NatGateway", + VpcId: VPCID, + Spec: NAT_GATEWAY_SPEC_SMALL, + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, + } + result, err := VPC_CLIENT.CreateNatGateway(args) + ExpectEqual(t.Errorf, nil, err) + NatID = result.NatId + + err = waitStateForNatGateway(NatID, NAT_STATUS_UNCONFIGURED) + ExpectEqual(t.Errorf, nil, err) +} + +func TestCreateEnhanceNatGateway(t *testing.T) { + args := &CreateNatGatewayArgs{ + ClientToken: getClientToken(), + Name: "Test-SDK-NatGateway-CU", + VpcId: VPCID, + CuNum: "3", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + } + result, err := VPC_CLIENT.CreateNatGateway(args) + ExpectEqual(t.Errorf, nil, err) + NatID = result.NatId + + err = waitStateForNatGateway(NatID, NAT_STATUS_UNCONFIGURED) + ExpectEqual(t.Errorf, nil, err) +} + +func TestGetRouteTableDetail(t *testing.T) { + RouteTableID = "" + result, err := VPC_CLIENT.GetRouteTableDetail(RouteTableID, VPCID) + r, err := json.Marshal(result) + fmt.Println(string(r)) + ExpectEqual(t.Errorf, nil, err) + RouteTableID = result.RouteTableId +} + +func TestCreateRouteRule(t *testing.T) { + args := &CreateRouteRuleArgs{ + ClientToken: getClientToken(), + RouteTableId: RouteTableID, + SourceAddress: "192.168.1.0/24", + DestinationAddress: "172.17.0.0/16", + NexthopType: NEXTHOP_TYPE_NAT, + NexthopId: NatID, + Description: "test route rule", + } + result, err := VPC_CLIENT.CreateRouteRule(args) + ExpectEqual(t.Errorf, nil, err) + + RouteRuleID = result.RouteRuleId + + routeTable, err := VPC_CLIENT.GetRouteTableDetail("", VPCID) + ExpectEqual(t.Errorf, nil, err) + isExist := false + for _, rule := range routeTable.RouteRules { + if rule.RouteRuleId == result.RouteRuleId { + isExist = true + ExpectEqual(t.Errorf, RouteTableID, rule.RouteTableId) + ExpectEqual(t.Errorf, "192.168.1.0/24", rule.SourceAddress) + ExpectEqual(t.Errorf, "172.17.0.0/16", rule.DestinationAddress) + ExpectEqual(t.Errorf, NEXTHOP_TYPE_NAT, rule.NexthopType) + ExpectEqual(t.Errorf, NatID, rule.NexthopId) + ExpectEqual(t.Errorf, "test route rule", rule.Description) + } + } + if !isExist { + t.Errorf("Test route rule failed.") + } +} + +func TestCreateEtGatewayRouteRule(t *testing.T) { + RouteTableID = "" + var SourceAddress = "12.0.0.0/25" + var DestinationAddress = "2.2.2.6/32" + var Description = "sdk test etGateway route rule" + var NexthopType = NEXTHOP_TYPE_ETGATEWAY + + mulargs := &CreateRouteRuleArgs{ + ClientToken: getClientToken(), + RouteTableId: RouteTableID, + SourceAddress: SourceAddress, + DestinationAddress: DestinationAddress, + NextHopList: []NextHop{ + { + NexthopId: "", + NexthopType: NexthopType, + PathType: "ha:active", + }, { + NexthopId: "", + NexthopType: NexthopType, + PathType: "ha:standby", + }, + }, + Description: Description, + } + result, err := VPC_CLIENT.CreateRouteRule(mulargs) + + r, err := json.Marshal(result) + fmt.Println(string(r)) + fmt.Println(err) + + ExpectEqual(t.Errorf, nil, err) + + var RouteRuleIds = result.RouteRuleIds + routeTable, err := VPC_CLIENT.GetRouteTableDetail(RouteTableID, VPCID) + ExpectEqual(t.Errorf, nil, err) + isExist := false + for _, rule := range routeTable.RouteRules { + for _, RouteRuleId := range RouteRuleIds { + if rule.RouteRuleId == RouteRuleId { + isExist = true + ExpectEqual(t.Errorf, RouteTableID, rule.RouteTableId) + ExpectEqual(t.Errorf, SourceAddress, rule.SourceAddress) + ExpectEqual(t.Errorf, DestinationAddress, rule.DestinationAddress) + ExpectEqual(t.Errorf, NexthopType, rule.NexthopType) + ExpectEqual(t.Errorf, Description, rule.Description) + } + } + } + if !isExist { + t.Errorf("Test route rule failed.") + } +} + +func TestListNatGateway(t *testing.T) { + args := &ListNatGatewayArgs{ + VpcId: VPCID, + NatId: NatID, + } + result, err := VPC_CLIENT.ListNatGateway(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 1, len(result.Nats)) +} + +func TestGetNatGatewayDetail(t *testing.T) { + result, err := VPC_CLIENT.GetNatGatewayDetail("nat-bzrav7t2ppb5") + tags := []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + } + r, _ := json.Marshal(result) + fmt.Println(string(r)) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Test-SDK-NatGateway", result.Name) + ExpectEqual(t.Errorf, VPCID, result.VpcId) + ExpectEqual(t.Errorf, NAT_GATEWAY_SPEC_SMALL, result.Spec) + ExpectEqual(t.Errorf, PAYMENT_TIMING_POSTPAID, result.PaymentTiming) + ExpectEqual(t.Errorf, tags, result.Tags) +} + +func TestUpdateNatGateway(t *testing.T) { + args := &UpdateNatGatewayArgs{ + ClientToken: getClientToken(), + Name: "Test-SDK-NatGateway-update", + } + err := VPC_CLIENT.UpdateNatGateway(NatID, args) + ExpectEqual(t.Errorf, nil, err) + + result, err := VPC_CLIENT.GetNatGatewayDetail(NatID) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "Test-SDK-NatGateway-update", result.Name) +} + +func TestResizeNatGateway(t *testing.T) { + args := &ResizeNatGatewayArgs{ + ClientToken: getClientToken(), + CuNum: 8, + } + err := VPC_CLIENT.ResizeNatGateway(NatID, args) + ExpectEqual(t.Errorf, nil, err) + err = waitCuNumForNatGateway(NatID, args.CuNum) + ExpectEqual(t.Errorf, nil, err) +} + +func TestBindEips(t *testing.T) { + // create eip first + args := &eip.CreateEipArgs{ + Name: "sdk-eip", + BandWidthInMbps: 10, + Billing: &eip.Billing{ + PaymentTiming: "Postpaid", + BillingMethod: "ByTraffic", + }, + ClientToken: getClientToken(), + } + result, err := EIP_CLIENT.CreateEip(args) + ExpectEqual(t.Errorf, nil, err) + EIPAddress = result.Eip + + // wait until the eip available + err = waitStateForEIP(EIPAddress, "available") + ExpectEqual(t.Errorf, nil, err) + + // bind eip + bindEipArgs := &BindEipsArgs{ + ClientToken: getClientToken(), + Eips: []string{EIPAddress}, + } + err = VPC_CLIENT.BindEips(NatID, bindEipArgs) + ExpectEqual(t.Errorf, nil, err) + + // wait until the eip bind completed + err = waitStateForNatGateway(NatID, NAT_STATUS_ACTIVE) + ExpectEqual(t.Errorf, nil, err) +} + +func TestUnBindEips(t *testing.T) { + // unbind eip + args := &UnBindEipsArgs{ + ClientToken: getClientToken(), + Eips: []string{EIPAddress}, + } + err := VPC_CLIENT.UnBindEips(NatID, args) + ExpectEqual(t.Errorf, nil, err) + + // wait until the eip unbind completed + err = waitStateForNatGateway(NatID, NAT_STATUS_UNCONFIGURED) + ExpectEqual(t.Errorf, nil, err) + + // delete eip + err = EIP_CLIENT.DeleteEip(EIPAddress, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestRenewNatGateway(t *testing.T) { + args := &RenewNatGatewayArgs{ + ClientToken: getClientToken(), + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + } + err := VPC_CLIENT.RenewNatGateway(NatID, args) + if err == nil { + t.Errorf("Test RenewNatGateway failed.") + } +} + +func TestCreateNatGatewaySnatRule(t *testing.T) { + args := &CreateNatGatewaySnatRuleArgs{ + RuleName: "sdk-test", + PublicIpAddresses: []string{"100.88.10.84"}, + SourceCIDR: "192.168.3.33", + } + result, err := VPC_CLIENT.CreateNatGatewaySnatRule("nat-b1jb3b5e34tc", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestBatchCreateNatGatewaySnatRule(t *testing.T) { + snatargs1 := SnatRuleArgs{ + RuleName: "sdk-test-b1", + PublicIpAddresses: []string{"100.88.9.91", "100.88.2.154"}, + SourceCIDR: "192.168.3.6", + } + snatargs2 := SnatRuleArgs{ + RuleName: "sdk-test-b2", + PublicIpAddresses: []string{"100.88.10.84", "100.88.2.154"}, + SourceCIDR: "192.168.3.7", + } + + args := &BatchCreateNatGatewaySnatRuleArgs{ + NatId: "nat-b1jb3b5e34tc", + } + + args.SnatRules = append(args.SnatRules, snatargs1) + args.SnatRules = append(args.SnatRules, snatargs2) + result, err := VPC_CLIENT.BatchCreateNatGatewaySnatRule(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestUpdateNatGatewaySnatRule(t *testing.T) { + args := &UpdateNatGatewaySnatRuleArgs{ + RuleName: "sdk-test-1", + SourceCIDR: "192.168.3.66", + } + result := VPC_CLIENT.UpdateNatGatewaySnatRule("nat-b1jb3b5e34tc", "rule-7zr5941yngks", args) + r, _ := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestDeleteNatGatewaySnatRule(t *testing.T) { + result := VPC_CLIENT.DeleteNatGatewaySnatRule("nat-b1jb3b5e34tc", "rule-7zr5941yngks", getClientToken()) + r, _ := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestListNatGatewaySnatRules(t *testing.T) { + args := &ListNatGatewaySnatRuleArgs{ + NatId: "nat-b1jb3b5e34tc", + } + result, err := VPC_CLIENT.ListNatGatewaySnatRules(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestCreateNatGatewayDnatRule(t *testing.T) { + args := &CreateNatGatewayDnatRuleArgs{ + RuleName: "sdk-test", + PublicIpAddress: "100.88.0.217", + PrivateIpAddress: "192.168.1.4", + Protocol: "TCP", + PublicPort: "121", + PrivatePort: "122", + } + result, err := VPC_CLIENT.CreateNatGatewayDnatRule("nat-b1jb3b5e34tc", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestBatchCreateNatGatewayDnatRule(t *testing.T) { + snatargs1 := DnatRuleArgs{ + RuleName: "sdk-test-db1", + PublicIpAddress: "100.88.0.217", + PrivateIpAddress: "192.168.1.21", + Protocol: "TCP", + PublicPort: "1211", + PrivatePort: "1221", + } + snatargs2 := DnatRuleArgs{ + RuleName: "sdk-test-db2", + PublicIpAddress: "100.88.0.217", + PrivateIpAddress: "192.168.1.22", + Protocol: "TCP", + PublicPort: "1212", + PrivatePort: "1222", + } + + args := &BatchCreateNatGatewayDnatRuleArgs{ + Rules: []DnatRuleArgs{snatargs1, snatargs2}, + } + + result, err := VPC_CLIENT.BatchCreateNatGatewayDnatRule("nat-b1jb3b5e34tc", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestUpdateNatGatewayDnatRule(t *testing.T) { + args := &UpdateNatGatewayDnatRuleArgs{ + RuleName: "sdk-test-3", + PrivateIpAddress: "192.168.1.5", + } + result := VPC_CLIENT.UpdateNatGatewayDnatRule("nat-b1jb3b5e34tc", "rule-8gee5abqins0", args) + r, _ := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestDeleteNatGatewayDnatRule(t *testing.T) { + result := VPC_CLIENT.DeleteNatGatewayDnatRule("nat-b1jb3b5e34tc", "rule-8gee5abqins0", getClientToken()) + r, _ := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestListNatGatewayDnatRules(t *testing.T) { + args := &ListNatGatewaDnatRuleArgs{ + MaxKeys: 2, + Marker: "rule-29n3en0z8tku", + } + result, err := VPC_CLIENT.ListNatGatewayDnatRules("nat-b1jb3b5e34tc", args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestCreatePeerConn(t *testing.T) { + // create another VPC + createVPCArgs := &CreateVPCArgs{ + Name: "TestSDK-VPC-Peer", + Description: "vpc test", + Cidr: "172.17.0.0/16", + ClientToken: getClientToken(), + } + vpcResult, err := VPC_CLIENT.CreateVPC(createVPCArgs) + ExpectEqual(t.Errorf, nil, err) + PeerVPCID = vpcResult.VPCID + + args := &CreatePeerConnArgs{ + ClientToken: getClientToken(), + BandwidthInMbps: 10, + Description: "test peer conn", + LocalIfName: "local-interface", + LocalVpcId: VPCID, + PeerVpcId: PeerVPCID, + PeerRegion: region, + PeerIfName: "peer-interface", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + Tags: []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + }, + } + result, err := VPC_CLIENT.CreatePeerConn(args) + ExpectEqual(t.Errorf, nil, err) + + PeerConnID = result.PeerConnId + + // wait until peerconn active + err = waitStateForPeerConn(PeerConnID, PEERCONN_STATUS_ACTIVE) + ExpectEqual(t.Errorf, nil, err) +} + +func TestListPeerConn(t *testing.T) { + args := &ListPeerConnsArgs{ + VpcId: VPCID, + } + result, err := VPC_CLIENT.ListPeerConn(args) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 1, len(result.PeerConns)) +} + +func TestGetPeerConnDetail(t *testing.T) { + result, err := VPC_CLIENT.GetPeerConnDetail(PeerConnID, PEERCONN_ROLE_INITIATOR) + r, _ := json.Marshal(result) + fmt.Println(string(r)) + tags := []model.TagModel{ + { + TagKey: "tagKey", + TagValue: "tagValue", + }, + } + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, 10, result.BandwidthInMbps) + ExpectEqual(t.Errorf, "test peer conn", result.Description) + ExpectEqual(t.Errorf, "local-interface", result.LocalIfName) + ExpectEqual(t.Errorf, VPCID, result.LocalVpcId) + ExpectEqual(t.Errorf, PAYMENT_TIMING_POSTPAID, result.PaymentTiming) + ExpectEqual(t.Errorf, tags, result.Tags) + + LocalIfID = result.LocalIfId +} + +func TestUpdatePeerConn(t *testing.T) { + args := &UpdatePeerConnArgs{ + LocalIfId: LocalIfID, + LocalIfName: "local-interface-update", + Description: "peer conn update", + } + err := VPC_CLIENT.UpdatePeerConn(PeerConnID, args) + ExpectEqual(t.Errorf, nil, err) + + result, err := VPC_CLIENT.GetPeerConnDetail(PeerConnID, PEERCONN_ROLE_INITIATOR) + ExpectEqual(t.Errorf, nil, err) + ExpectEqual(t.Errorf, "peer conn update", result.Description) + ExpectEqual(t.Errorf, "local-interface-update", result.LocalIfName) + ExpectEqual(t.Errorf, VPCID, result.LocalVpcId) + ExpectEqual(t.Errorf, PAYMENT_TIMING_POSTPAID, result.PaymentTiming) +} + +func TestAcceptPeerConnApply(t *testing.T) { + err := VPC_CLIENT.AcceptPeerConnApply(PeerConnID, getClientToken()) + if err == nil { + t.Errorf("Test AcceptPeerConnApply failed.") + } +} + +func TestRejectPeerConnReject(t *testing.T) { + err := VPC_CLIENT.RejectPeerConnApply(PeerConnID, getClientToken()) + if err == nil { + t.Errorf("Test RejectPeerConnApply failed.") + } +} + +func TestResizePeerConn(t *testing.T) { + args := &ResizePeerConnArgs{ + NewBandwidthInMbps: 20, + ClientToken: getClientToken(), + } + err := VPC_CLIENT.ResizePeerConn(PeerConnID, args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestRenewPeerConn(t *testing.T) { + args := &RenewPeerConnArgs{ + ClientToken: getClientToken(), + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_POSTPAID, + }, + } + err := VPC_CLIENT.RenewPeerConn(PeerConnID, args) + if err == nil { + t.Errorf("Test RenewPeerConn failed.") + } +} + +func TestOpenPeerConnSyncDNS(t *testing.T) { + args := &PeerConnSyncDNSArgs{ + Role: PEERCONN_ROLE_INITIATOR, + ClientToken: getClientToken(), + } + err := VPC_CLIENT.OpenPeerConnSyncDNS(PeerConnID, args) + ExpectEqual(t.Errorf, nil, err) + + // wait until dns sync completed + err = waitStateForPeerConn(PeerConnID, DNS_STATUS_OPEN) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClosePeerConnSyncDNS(t *testing.T) { + args := &PeerConnSyncDNSArgs{ + Role: PEERCONN_ROLE_INITIATOR, + ClientToken: getClientToken(), + } + err := VPC_CLIENT.ClosePeerConnSyncDNS(PeerConnID, args) + ExpectEqual(t.Errorf, nil, err) + + // wait until dns sync completed + err = waitStateForPeerConn(PeerConnID, DNS_STATUS_CLOSE) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeletePeerConn(t *testing.T) { + err := VPC_CLIENT.DeletePeerConn(PeerConnID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) + + err = waitStateForPeerConn(PeerConnID, PEERCONN_STATUS_DELETED) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteNatGateway(t *testing.T) { + err := VPC_CLIENT.DeleteNatGateway(NatID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) + + err = waitStateForNatGateway(NatID, NAT_STATUS_DELETED) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteAclRule(t *testing.T) { + err := VPC_CLIENT.DeleteAclRule(AclRuleID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteRouteRule(t *testing.T) { + err := VPC_CLIENT.DeleteRouteRule(RouteRuleID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteSubnet(t *testing.T) { + err := VPC_CLIENT.DeleteSubnet(SubnetID, getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestDeleteVPC(t *testing.T) { + // The vpc will be SUBNET_INUSE status after the subnet deleted in a period of time. + // So delete vpc by waitStateForVPC currently. + err := waitStateForVPC(VPCID) + ExpectEqual(t.Errorf, nil, err) + + err = waitStateForVPC(PeerVPCID) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} + +func waitStateForNatGateway(natID string, status NatStatusType) error { + ticker := time.NewTicker(2 * time.Second) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-ticker.C: + nat, err := VPC_CLIENT.GetNatGatewayDetail(natID) + if err != nil { + if !strings.Contains(err.Error(), "NoSuchNat") { + errChan <- err + return + } + nat = &NAT{Status: NAT_STATUS_DELETED} + } + if nat.Status == status { + errChan <- nil + return + } + } + } + }() + + select { + case <-time.After(10 * time.Minute): + return fmt.Errorf("Test nat gateway %s timeout.", natID) + case err := <-errChan: + return err + } +} + +func waitCuNumForNatGateway(natID string, cuNum int) error { + ticker := time.NewTicker(2 * time.Second) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-ticker.C: + nat, err := VPC_CLIENT.GetNatGatewayDetail(natID) + r, _ := json.Marshal(nat) + fmt.Println(string(r)) + fmt.Println(cuNum) + if err != nil { + if !strings.Contains(err.Error(), "NoSuchNat") { + errChan <- err + return + } + } + if nat.CuNum == cuNum { + errChan <- nil + return + } + } + } + }() + + select { + case <-time.After(10 * time.Minute): + return fmt.Errorf("Test nat gateway %s timeout.", natID) + case err := <-errChan: + return err + } +} + +func waitStateForEIP(eipAddress, status string) error { + ticker := time.NewTicker(2 * time.Second) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-ticker.C: + args := &eip.ListEipArgs{Eip: EIPAddress} + eips, err := EIP_CLIENT.ListEip(args) + if err != nil { + errChan <- err + return + } + if len(eips.EipList) == 1 && eips.EipList[0].Status == status { + errChan <- nil + return + } + } + } + }() + + select { + case <-time.After(10 * time.Minute): + return fmt.Errorf("Test eip %s timeout.", eipAddress) + case err := <-errChan: + return err + } +} + +func waitStateForVPC(vpcID string) error { + ticker := time.NewTicker(2 * time.Second) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-ticker.C: + err := VPC_CLIENT.DeleteVPC(vpcID, getClientToken()) + if err != nil && strings.Contains(err.Error(), "SUBNET_INUSE") { + continue + } + errChan <- err + return + } + } + }() + + select { + case <-time.After(10 * time.Minute): + return fmt.Errorf("Test VPC %s timeout.", vpcID) + case err := <-errChan: + return err + } +} + +func waitStateForPeerConn(peerConnID string, status interface{}) error { + ticker := time.NewTicker(2 * time.Second) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-ticker.C: + result, err := VPC_CLIENT.GetPeerConnDetail(peerConnID, PEERCONN_ROLE_INITIATOR) + if err != nil { + // not found error + if !strings.Contains(err.Error(), "EOF") { + errChan <- err + return + } + result = &PeerConn{Status: PEERCONN_STATUS_DELETED} + } + + switch status.(type) { + case PeerConnStatusType: + if result.Status == status { + errChan <- nil + return + } + case DnsStatusType: + if result.DnsStatus == status { + errChan <- nil + return + } + default: + errChan <- fmt.Errorf("The status %s type is not supported.", status) + return + } + } + } + }() + + select { + case <-time.After(10 * time.Minute): + return fmt.Errorf("Test peer conn %s timeout.", peerConnID) + case err := <-errChan: + return err + } +} + +func TestGetNetworkTopologyInfo(t *testing.T) { + args := &GetNetworkTopologyArgs{ + HostId: "", + HostIp: "", + } + result, err := VPC_CLIENT.GetNetworkTopologyInfo(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) +} + +func TestClient_BindDnatEips(t *testing.T) { + args := &BindDnatEipsArgs{ + ClientToken: getClientToken(), + DnatEips: []string{"100.88.14.243"}, + } + err := VPC_CLIENT.BindDnatEips("nat-bc39ugw5ry9z", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnBindDnatEips(t *testing.T) { + args := &UnBindDnatEipsArgs{ + ClientToken: getClientToken(), + DnatEips: []string{"100.88.14.243"}, + } + err := VPC_CLIENT.UnBindDnatEips("nat-bc39ugw5ry9z", args) + ExpectEqual(t.Errorf, nil, err) +} diff --git a/bce-sdk-go/services/vpc/ipv6gateway.go b/bce-sdk-go/services/vpc/ipv6gateway.go new file mode 100644 index 0000000..280a8a7 --- /dev/null +++ b/bce-sdk-go/services/vpc/ipv6gateway.go @@ -0,0 +1,295 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// ipv6gateway.go - the ipv6 gateway APIs definition supported by the VPC service + +package vpc + +import ( + "errors" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateIPv6Gateway - create a new ipv6 gateway +// +// PARAMS: +// - args: the arguments to create ipv6 gateway +// +// RETURNS: +// - *CreateIPv6GatewayResult: the id of the ipv6 gateway newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateIPv6Gateway(args *CreateIPv6GatewayArgs) (*CreateIPv6GatewayResult, error) { + if args == nil { + return nil, errors.New("The CreateIPv6GatewayArgs cannot be nil.") + } + + result := &CreateIPv6GatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6Gateway()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListIPv6Gateway - list all ipv6 gateways with the specific parameters +// +// PARAMS: +// - args: the arguments to list ipv6 gateways +// +// RETURNS: +// - *ListIPv6GatewayResult: the result of ipv6 gateway list +// - error: nil if success otherwise the specific error +func (c *Client) ListIPv6Gateway(args *ListIPv6GatewayArgs) (*ListIPv6GatewayResult, error) { + if args == nil { + return nil, errors.New("The ListIPv6GatewayArgs cannot be nil.") + } + + result := &ListIPv6GatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6Gateway()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithResult(result). + Do() + + return result, err +} + +// DeleteIPv6Gateway - delete the specified ipv6 gateway +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to delete ipv6 gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteIPv6Gateway(gatewayId string, args *DeleteIPv6GatewayArgs) error { + if args == nil { + return errors.New("The DeleteIPv6GatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)). + WithMethod(http.DELETE). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// ResizeIPv6Gateway - resize the specified ipv6 gateway +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to resize ipv6 gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizeIPv6Gateway(gatewayId string, args *ResizeIPv6GatewayArgs) error { + if args == nil { + return errors.New("The ResizeIPv6GatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParam("resize", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// CreateIPv6GatewayEgressOnlyRule - create a new ipv6 gateway egress only rule +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to create ipv6 gateway egress only rule +// +// RETURNS: +// - *CreateIPv6GatewayEgressOnlyRuleResult: the id of the ipv6 gateway egress only rule newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateIPv6GatewayEgressOnlyRule(gatewayId string, args *CreateIPv6GatewayEgressOnlyRuleArgs) ( + *CreateIPv6GatewayEgressOnlyRuleResult, error) { + if args == nil { + return nil, errors.New("The CreateIPv6GatewayEgressOnlyRuleArgs cannot be nil.") + } + + result := &CreateIPv6GatewayEgressOnlyRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/egressOnlyRule"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListIPv6GatewayEgressOnlyRule - list all ipv6 gateway egress only rules with the specific parameters +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to list ipv6 gateway egress only rules +// +// RETURNS: +// - *ListIPv6GatewayEgressOnlyRuleResult: the result of ipv6 gateway egress only rule list +// - error: nil if success otherwise the specific error +func (c *Client) ListIPv6GatewayEgressOnlyRule(gatewayId string, args *ListIPv6GatewayEgressOnlyRuleArgs) (*ListIPv6GatewayEgressOnlyRuleResult, error) { + if args == nil { + return nil, errors.New("The ListIPv6GatewayEgressOnlyRuleArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListIPv6GatewayEgressOnlyRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/egressOnlyRule"). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// DeleteIPv6GatewayEgressOnlyRule - delete the specified ipv6 gateway egress only rule +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - egressOnlyRuleId: the id of the specific ipv6 gateway egress only rule +// - args: the arguments to delete ipv6 gateway egress only rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteIPv6GatewayEgressOnlyRule(gatewayId, egressOnlyRuleId string, args *DeleteIPv6GatewayEgressOnlyRuleArgs) error { + if args == nil { + return errors.New("The DeleteIPv6GatewayEgressOnlyRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/egressOnlyRule/"+egressOnlyRuleId). + WithMethod(http.DELETE). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// CreateIPv6GatewayRateLimitRule - create a new ipv6 gateway rate limit rule +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to create ipv6 gateway rate limit rule +// +// RETURNS: +// - *CreateIPv6GatewayRateLimitRuleResult: the id of the ipv6 gateway rate limit rule newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateIPv6GatewayRateLimitRule(gatewayId string, args *CreateIPv6GatewayRateLimitRuleArgs) ( + *CreateIPv6GatewayRateLimitRuleResult, error) { + if args == nil { + return nil, errors.New("The CreateIPv6GatewayRateLimitRuleArgs cannot be nil.") + } + + result := &CreateIPv6GatewayRateLimitRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/rateLimitRule"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListIPv6GatewayRateLimitRule - list all ipv6 gateway rate limit rules with the specific parameters +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - args: the arguments to list ipv6 gateway rate limit rules +// +// RETURNS: +// - *ListIPv6GatewayRateLimitRuleResult: the result of ipv6 gateway rate limit rule list +// - error: nil if success otherwise the specific error +func (c *Client) ListIPv6GatewayRateLimitRule(gatewayId string, args *ListIPv6GatewayRateLimitRuleArgs) (*ListIPv6GatewayRateLimitRuleResult, error) { + if args == nil { + return nil, errors.New("The ListIPv6GatewayRateLimitRuleArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListIPv6GatewayRateLimitRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/rateLimitRule"). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// DeleteIPv6GatewayRateLimitRule - delete the specified ipv6 gateway rate limit rule +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - rateLimitRuleId: the id of the specific ipv6 gateway rate limit rule +// - args: the arguments to delete ipv6 gateway rate limit rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteIPv6GatewayRateLimitRule(gatewayId, rateLimitRuleId string, args *DeleteIPv6GatewayRateLimitRuleArgs) error { + if args == nil { + return errors.New("The DeleteIPv6GatewayRateLimitRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/rateLimitRule/"+rateLimitRuleId). + WithMethod(http.DELETE). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// UpdateIPv6GatewayRateLimitRule - update the specified ipv6 gateway rate limit rule +// +// PARAMS: +// - gatewayId: the id of the specific ipv6 gateway +// - rateLimitRuleId: the id of the specific ipv6 gateway rate limit rule +// - args: the arguments to update ipv6 gateway rate limit rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateIPv6GatewayRateLimitRule(gatewayId, rateLimitRuleId string, args *UpdateIPv6GatewayRateLimitRuleArgs) error { + if args == nil { + return errors.New("The UpdateIPv6GatewayRateLimitRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForIpv6GatewayId(gatewayId)+"/rateLimitRule/"+rateLimitRuleId). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} diff --git a/bce-sdk-go/services/vpc/model.go b/bce-sdk-go/services/vpc/model.go new file mode 100644 index 0000000..0547ae1 --- /dev/null +++ b/bce-sdk-go/services/vpc/model.go @@ -0,0 +1,995 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package vpc + +import ( + "github.com/baidubce/bce-sdk-go/model" +) + +type ( + SubnetType string + + NexthopType string + + AclRuleProtocolType string + + AclRuleDirectionType string + + AclRuleActionType string + + AclRulePortType string + + NatGatewaySpecType string + + PaymentTimingType string + + PeerConnRoleType string + + NatStatusType string + + PeerConnStatusType string + + DnsStatusType string +) + +const ( + SUBNET_TYPE_BCC SubnetType = "BCC" + SUBNET_TYPE_BCCNAT SubnetType = "BCC_NAT" + SUBNET_TYPE_BBC SubnetType = "BBC" + + NEXTHOP_TYPE_CUSTOM NexthopType = "custom" + NEXTHOP_TYPE_VPN NexthopType = "vpn" + NEXTHOP_TYPE_NAT NexthopType = "nat" + NEXTHOP_TYPE_ETGATEWAY NexthopType = "dcGateway" + NEXTHOP_TYPE_PEERCONN NexthopType = "peerConn" + NEXTHOP_TYPE_IPV6GATEWAY NexthopType = "ipv6gateway" + NEXTHOP_TYPE_ENIC NexthopType = "enic" + NEXTHOP_TYPE_HAVIP NexthopType = "havip" + + ACL_RULE_PROTOCOL_TCP AclRuleProtocolType = "tcp" + ACL_RULE_PROTOCOL_UDP AclRuleProtocolType = "udp" + ACL_RULE_PROTOCOL_ICMP AclRuleProtocolType = "icmp" + + ACL_RULE_DIRECTION_INGRESS AclRuleDirectionType = "ingress" + ACL_RULE_DIRECTION_EGRESS AclRuleDirectionType = "egress" + + ACL_RULE_ACTION_ALLOW AclRuleActionType = "allow" + ACL_RULE_ACTION_DENY AclRuleActionType = "deny" + + ACL_RULE_PORT_ALL AclRulePortType = "all" + + NAT_GATEWAY_SPEC_SMALL NatGatewaySpecType = "small" + NAT_GATEWAY_SPEC_MEDIUM NatGatewaySpecType = "medium" + NAT_GATEWAY_SPEC_LARGE NatGatewaySpecType = "large" + + PAYMENT_TIMING_PREPAID PaymentTimingType = "Prepaid" + PAYMENT_TIMING_POSTPAID PaymentTimingType = "Postpaid" + + PEERCONN_ROLE_INITIATOR PeerConnRoleType = "initiator" + PEERCONN_ROLE_ACCEPTOR PeerConnRoleType = "acceptor" + + NAT_STATUS_BUILDING NatStatusType = "building" + NAT_STATUS_UNCONFIGURED NatStatusType = "unconfigured" + NAT_STATUS_CONFIGURING NatStatusType = "configuring" + NAT_STATUS_ACTIVE NatStatusType = "active" + NAT_STATUS_STOPPING NatStatusType = "stopping" + NAT_STATUS_DOWN NatStatusType = "down" + NAT_STATUS_STARTING NatStatusType = "starting" + NAT_STATUS_DELETING NatStatusType = "deleting" + NAT_STATUS_DELETED NatStatusType = "deleted" + + PEERCONN_STATUS_CREATING PeerConnStatusType = "creating" + PEERCONN_STATUS_CONSULTING PeerConnStatusType = "consulting" + PEERCONN_STATUS_CONSULT_FAILED PeerConnStatusType = "consult_failed" + PEERCONN_STATUS_ACTIVE PeerConnStatusType = "active" + PEERCONN_STATUS_DOWN PeerConnStatusType = "down" + PEERCONN_STATUS_STARTING PeerConnStatusType = "starting" + PEERCONN_STATUS_STOPPING PeerConnStatusType = "stopping" + PEERCONN_STATUS_DELETING PeerConnStatusType = "deleting" + PEERCONN_STATUS_DELETED PeerConnStatusType = "deleted" + PEERCONN_STATUS_EXPIRED PeerConnStatusType = "expired" + PEERCONN_STATUS_ERROR PeerConnStatusType = "error" + PEERCONN_STATUS_UPDATING PeerConnStatusType = "updating" + + DNS_STATUS_CLOSE DnsStatusType = "close" + DNS_STATUS_WAIT DnsStatusType = "wait" + DNS_STATUS_SYNCING DnsStatusType = "syncing" + DNS_STATUS_OPEN DnsStatusType = "open" + DNS_STATUS_CLOSING DnsStatusType = "closing" +) + +// CreateVPCArgs defines the structure of the input parameters for the CreateVPC api +type CreateVPCArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Cidr string `json:"cidr"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +// CreateVPCResult defines the structure of the output parameters for the CreateVPC api +type CreateVPCResult struct { + VPCID string `json:"vpcId"` +} + +// ListVPCArgs defines the structure of the input parameters for the ListVPC api +type ListVPCArgs struct { + Marker string + MaxKeys int + + // IsDefault is a string type, + // so we can identify if it has been setted when the value is false. + // NOTE: it can be only true or false. + IsDefault string +} + +// ListVPCResult defines the structure of the output parameters for the ListVPC api +type ListVPCResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + VPCs []VPC `json:"vpcs"` +} + +type VPC struct { + VPCID string `json:"vpcId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + CreatedTime string `json:"createdTime"` + Description string `json:"description"` + IsDefault bool `json:"isDefault"` + SecondaryCidr []string `json:"secondaryCidr"` + Tags []model.TagModel `json:"tags"` +} + +// GetVPCDetailResult defines the structure of the output parameters for the GetVPCDetail api +type GetVPCDetailResult struct { + VPC ShowVPCModel `json:"vpc"` +} + +type ShowVPCModel struct { + VPCId string `json:"vpcId"` + Name string `json:"name"` + Cidr string `json:"cidr"` + CreatedTime string `json:"createdTime"` + Description string `json:"description"` + IsDefault bool `json:"isDefault"` + Subnets []Subnet `json:"subnets"` + SecondaryCidr []string `json:"secondaryCidr"` + Tags []model.TagModel `json:"tags"` +} + +type Subnet struct { + SubnetId string `json:"subnetId"` + Name string `json:"name"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + Ipv6Cidr string `json:"ipv6Cidr"` + VPCId string `json:"vpcId"` + SubnetType SubnetType `json:"subnetType"` + Description string `json:"description"` + CreatedTime string `json:"createdTime"` + AvailableIp int `json:"availableIp"` + Tags []model.TagModel `json:"tags"` +} + +// UpdateVPCArgs defines the structure of the input parameters for the UpdateVPC api +type UpdateVPCArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +// CreateSubnetArgs defines the structure of the input parameters for the CreateSubnet api +type CreateSubnetArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + ZoneName string `json:"zoneName"` + Cidr string `json:"cidr"` + VpcId string `json:"vpcId"` + VpcSecondaryCidr string `json:"vpcSecondaryCidr,omitempty"` + SubnetType SubnetType `json:"subnetType,omitempty"` + Description string `json:"description,omitempty"` + EnableIpv6 bool `json:"enableIpv6,omitempty"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +// CreateSubnetResult defines the structure of the output parameters for the CreateSubnet api +type CreateSubnetResult struct { + SubnetId string `json:"subnetId"` +} + +// ListSubnetArgs defines the structure of the input parameters for the ListSubnet api +type ListSubnetArgs struct { + Marker string + MaxKeys int + VpcId string + ZoneName string + SubnetType SubnetType +} + +// ListSubnetResult defines the structure of the output parameters for the ListSubnet api +type ListSubnetResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + Subnets []Subnet `json:"subnets"` +} + +// GetSubnetDetailResult defines the structure of the output parameters for the GetSubnetDetail api +type GetSubnetDetailResult struct { + Subnet Subnet `json:"subnet"` +} + +// UpdateSubnetArgs defines the structure of the input parameters for the UpdateSubnet api +type UpdateSubnetArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + EnableIpv6 bool `json:"enableIpv6"` +} + +// CreateIpreserveArgs - arguments for creating a reserved CIDR +type CreateIpreserveArgs struct { + SubnetId string `json:"subnetId"` + IpCidr string `json:"ipCidr"` + IpVersion int32 `json:"ipVersion"` + Description string `json:"description"` + ClientToken string `json:"-"` +} + +// CreateIpreserveResult - result of creating a reserved CIDR +type CreateIpreserveResult struct { + IpReserveId string `json:"ipReserveId"` +} + +// ListIpeserveArgs - arguments for listing reserved CIDRs +type ListIpeserveArgs struct { + SubnetId string `json:"subnetId,omitempty"` + Marker string `json:"marker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type ipReserve struct { + IpReserveId string `json:"ipReserveId"` + SubnetId string `json:"subnetId"` + IpCidr string `json:"ipCidr"` + IpVersion int32 `json:"ipVersion"` + Description string `json:"description"` + CreatedTime string `json:"createdTime"` + UpdatedTime string `json:"updatedTime"` +} + +// ListIpeserveResult - result of listing reserved CIDRs +type ListIpeserveResult struct { + IpReserves []ipReserve `json:"ipReserves"` + Marker string `json:"marker,omitempty"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker,omitempty"` + MaxKeys int `json:"maxKeys,omitempty"` +} + +type RouteRule struct { + RouteRuleId string `json:"routeRuleId"` + RouteTableId string `json:"routeTableId"` + SourceAddress string `json:"sourceAddress"` + DestinationAddress string `json:"destinationAddress"` + NexthopId string `json:"nexthopId"` + NexthopType NexthopType `json:"nexthopType"` + Description string `json:"description"` + PathType string `json:"pathType"` +} + +// GetRouteTableResult defines the structure of the output parameters for the GetRouteTableDetail api +type GetRouteTableResult struct { + RouteTableId string `json:"routeTableId"` + VpcId string `json:"vpcId"` + RouteRules []RouteRule `json:"routeRules"` +} + +type GetRouteRuleArgs struct { + RouteTableId string `json:"routeTableId"` + VpcId string `json:"vpcId"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type GetRouteRuleResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RouteRules []RouteRule `json:"routeRules"` +} + +type UpdateRouteRuleArgs struct { + RouteRuleId string `json:"routeRuleId"` + ClientToken string `json:"-"` + SourceAddress string `json:"sourceAddress"` + DestinationAddress string `json:"destinationAddress"` + NexthopId string `json:"nexthopId,omitempty"` + Description string `json:"description,omitempty"` +} + +// CreateRouteRuleArgs defines the structure of the input parameters for the CreateRouteRule api +type CreateRouteRuleArgs struct { + ClientToken string `json:"-"` + RouteTableId string `json:"routeTableId"` + SourceAddress string `json:"sourceAddress"` + DestinationAddress string `json:"destinationAddress"` + NexthopId string `json:"nexthopId,omitempty"` + IpVersion string `json:"ipVersion,omitempty"` + NexthopType NexthopType `json:"nexthopType"` + NextHopList []NextHop `json:"nextHopList,omitempty"` + Description string `json:"description,omitempty"` +} + +type NextHop struct { + NexthopId string `json:"nexthopId"` + NexthopType NexthopType `json:"nexthopType"` + PathType string `json:"pathType"` +} + +// CreateRouteRuleResult defines the structure of the output parameters for the CreateRouteRule api +type CreateRouteRuleResult struct { + RouteRuleId string `json:"routeRuleId"` + RouteRuleIds []string `json:"routeRuleIds,omitempty"` +} + +// ListAclEntrysResult defines the structure of the output parameters for the ListAclEntrys api +type ListAclEntrysResult struct { + VpcId string `json:"vpcId"` + VpcName string `json:"vpcName"` + VpcCidr string `json:"vpcCidr"` + AclEntrys []AclEntry `json:"aclEntrys"` +} + +type AclEntry struct { + SubnetId string `json:"subnetId"` + SubnetName string `json:"subnetName"` + SubnetCidr string `json:"subnetCidr"` + AclRules []AclRule `json:"aclRules"` +} + +type AclRule struct { + Id string `json:"id"` + SubnetId string `json:"subnetId"` + Description string `json:"description"` + Protocol AclRuleProtocolType `json:"protocol"` + SourceIpAddress string `json:"sourceIpAddress"` + DestinationIpAddress string `json:"destinationIpAddress"` + SourcePort string `json:"sourcePort"` + DestinationPort string `json:"destinationPort"` + Position int `json:"position"` + Direction AclRuleDirectionType `json:"direction"` + Action AclRuleActionType `json:"action"` +} + +// CreateAclRuleArgs defines the structure of the input parameters for the CreateAclRule api +type CreateAclRuleArgs struct { + ClientToken string `json:"-"` + AclRules []AclRuleRequest `json:"aclRules"` +} + +type AclRuleRequest struct { + SubnetId string `json:"subnetId"` + Description string `json:"description,omitempty"` + Protocol AclRuleProtocolType `json:"protocol"` + SourceIpAddress string `json:"sourceIpAddress"` + DestinationIpAddress string `json:"destinationIpAddress"` + SourcePort string `json:"sourcePort"` + DestinationPort string `json:"destinationPort"` + Position int `json:"position"` + Direction AclRuleDirectionType `json:"direction"` + Action AclRuleActionType `json:"action"` +} + +// ListAclRulesArgs defines the structure of the input parameters for the ListAclRules api +type ListAclRulesArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + SubnetId string `json:"subnetId"` +} + +// ListAclRulesResult defines the structure of the output parameters for the ListAclRules api +type ListAclRulesResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + AclRules []AclRule `json:"aclRules"` +} + +// UpdateAclRuleArgs defines the structure of the input parameters for the UpdateAclRule api +type UpdateAclRuleArgs struct { + ClientToken string `json:"-"` + Description string `json:"description,omitempty"` + Protocol AclRuleProtocolType `json:"protocol,omitempty"` + SourceIpAddress string `json:"sourceIpAddress,omitempty"` + DestinationIpAddress string `json:"destinationIpAddress,omitempty"` + SourcePort string `json:"sourcePort,omitempty"` + DestinationPort string `json:"destinationPort,omitempty"` + Position int `json:"position,omitempty"` + Action AclRuleActionType `json:"action,omitempty"` +} + +// CreateNatGatewayArgs defines the structure of the input parameters for the CreateNatGateway api +type CreateNatGatewayArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + VpcId string `json:"vpcId"` + Spec NatGatewaySpecType `json:"spec"` + CuNum string `json:"cuNum,omitempty"` + Eips []string `json:"eips,omitempty"` + DnatEips []string `json:"dnatEips,omitempty"` + Billing *Billing `json:"billing"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +type ResizeNatGatewayArgs struct { + ClientToken string `json:"-"` + CuNum int `json:"cuNum"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +// CreateNatGatewayResult defines the structure of the output parameters for the CreateNatGateway api +type CreateNatGatewayResult struct { + NatId string `json:"natId"` +} + +// ListNatGatewayArgs defines the structure of the input parameters for the ListNatGateway api +type ListNatGatewayArgs struct { + VpcId string + NatId string + Name string + Ip string + Marker string + MaxKeys int +} + +// ListNatGatewayResult defines the structure of the output parameters for the ListNatGateway api +type ListNatGatewayResult struct { + Nats []NAT `json:"nats"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +// NAT is the result for getNatGatewayDetail api. +type NAT struct { + Id string `json:"id"` + Name string `json:"name"` + VpcId string `json:"vpcId"` + Spec string `json:"spec,omitempty"` + CuNum int `json:"cuNum,omitempty"` + Status NatStatusType `json:"status"` + Eips []string `json:"eips"` + DnatEips []string `json:"dnatEips"` + PaymentTiming string `json:"paymentTiming"` + ExpiredTime string `json:"expiredTime"` + Tags []model.TagModel `json:"tags"` +} + +type SnatRule struct { + RuleId string `json:"ruleId"` + RuleName string `json:"ruleName"` + PublicIpAddresses []string `json:"publicIpsAddress"` + SourceCIDR string `json:"sourceCIDR"` + Status string `json:"status"` +} + +type SnatRuleArgs struct { + RuleName string `json:"ruleName,omitempty"` + SourceCIDR string `json:"sourceCIDR,omitempty"` + PublicIpAddresses []string `json:"publicIpsAddress,omitempty"` +} + +type DnatRuleArgs struct { + RuleName string `json:"ruleName,omitempty"` + PublicIpAddress string `json:"publicIpAddress,omitempty"` + PrivateIpAddress string `json:"privateIpAddress,omitempty"` + Protocol string `json:"protocol,omitempty"` + PublicPort string `json:"publicPort,omitempty"` + PrivatePort string `json:"privatePort,omitempty"` +} + +type DnatRule struct { + RuleId string `json:"ruleId"` + RuleName string `json:"ruleName"` + PublicIpAddress string `json:"publicIpAddress"` + PrivateIpAddress string `json:"privateIpAddress"` + Protocol string `json:"protocol"` + PublicPort int `json:"publicPort"` + PrivatePort int `json:"privatePort"` + Status string `json:"status"` +} + +// UpdateNatGatewayArgs defines the structure of the input parameters for the UpdateNatGateway api +type UpdateNatGatewayArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` +} + +// BindEipsArgs defines the structure of the input parameters for the BindEips api +type BindEipsArgs struct { + ClientToken string `json:"-"` + Eips []string `json:"eips"` +} + +// BindDnatEipsArgs defines the structure of the input parameters for the BindDnatEips api +type BindDnatEipsArgs struct { + ClientToken string `json:"-"` + DnatEips []string `json:"dnatEips"` +} + +// UnBindEipsArgs defines the structure of the input parameters for the UnBindEips api +type UnBindEipsArgs struct { + ClientToken string `json:"-"` + Eips []string `json:"eips"` +} + +// UnBindDnatEipsArgs defines the structure of the input parameters for the UnBindDnatEips api +type UnBindDnatEipsArgs struct { + ClientToken string `json:"-"` + DnatEips []string `json:"dnatEips"` +} + +// RenewNatGatewayArgs defines the structure of the input parameters for the RenewNatGateway api +type RenewNatGatewayArgs struct { + ClientToken string `json:"-"` + Billing *Billing `json:"billing"` +} + +type CreateNatGatewaySnatRuleArgs struct { + ClientToken string `json:"-"` + RuleName string `json:"ruleName,omitempty"` + SourceCIDR string `json:"sourceCIDR,omitempty"` + PublicIpAddresses []string `json:"publicIpsAddress,omitempty"` +} + +type BatchCreateNatGatewaySnatRuleArgs struct { + ClientToken string `json:"-"` + NatId string `json:"natId"` + SnatRules []SnatRuleArgs `json:"snatRules"` +} + +type UpdateNatGatewaySnatRuleArgs struct { + ClientToken string `json:"-"` + RuleName string `json:"ruleName,omitempty"` + SourceCIDR string `json:"sourceCIDR,omitempty"` + PublicIpAddresses []string `json:"publicIpsAddress,omitempty"` +} + +type ListNatGatewaySnatRuleArgs struct { + NatId string `json:"natId"` + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListNatGatewaySnatRulesResult struct { + Rules []SnatRule `json:"rules"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type CreateNatGatewaySnatRuleResult struct { + RuleId string `json:"ruleId"` +} + +type BatchCreateNatGatewaySnatRuleResult struct { + SnatRuleIds []string `json:"snatRuleIds"` +} + +type CreateNatGatewayDnatRuleArgs struct { + ClientToken string `json:"-"` + RuleName string `json:"ruleName,omitempty"` + PublicIpAddress string `json:"publicIpAddress,omitempty"` + PrivateIpAddress string `json:"privateIpAddress,omitempty"` + Protocol string `json:"protocol,omitempty"` + PublicPort string `json:"publicPort,omitempty"` + PrivatePort string `json:"privatePort,omitempty"` +} + +type BatchCreateNatGatewayDnatRuleArgs struct { + ClientToken string `json:"-"` + Rules []DnatRuleArgs `json:"rules"` +} + +type UpdateNatGatewayDnatRuleArgs struct { + ClientToken string `json:"-"` + RuleName string `json:"ruleName,omitempty"` + PublicIpAddress string `json:"publicIpAddress,omitempty"` + PrivateIpAddress string `json:"privateIpAddress,omitempty"` + Protocol string `json:"protocol,omitempty"` + PublicPort string `json:"publicPort,omitempty"` + PrivatePort string `json:"privatePort,omitempty"` +} + +type ListNatGatewaDnatRuleArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` +} + +type ListNatGatewayDnatRulesResult struct { + Rules []DnatRule `json:"rules"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type CreateNatGatewayDnatRuleResult struct { + RuleId string `json:"ruleId"` +} + +type BatchCreateNatGatewayDnatRuleResult struct { + RuleIds []string `json:"ruleIds"` +} + +// CreatePeerConnArgs defines the structure of the input parameters for the CreatePeerConn api +type CreatePeerConnArgs struct { + ClientToken string `json:"-"` + BandwidthInMbps int `json:"bandwidthInMbps"` + Description string `json:"description,omitempty"` + LocalIfName string `json:"localIfName,omitempty"` + LocalVpcId string `json:"localVpcId"` + PeerAccountId string `json:"peerAccountId,omitempty"` + PeerVpcId string `json:"peerVpcId"` + PeerRegion string `json:"peerRegion"` + PeerIfName string `json:"peerIfName,omitempty"` + Billing *Billing `json:"billing"` + Tags []model.TagModel `json:"tags,omitempty"` +} + +// CreatePeerConnResult defines the structure of the output parameters for the CreatePeerConn api +type CreatePeerConnResult struct { + PeerConnId string `json:"peerConnId"` +} + +// ListPeerConnsArgs defines the structure of the input parameters for the ListPeerConns api +type ListPeerConnsArgs struct { + VpcId string + Marker string + MaxKeys int +} + +// ListPeerConnsResult defines the structure of the output parameters for the ListPeerConns api +type ListPeerConnsResult struct { + PeerConns []PeerConn `json:"peerConns"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type PeerConn struct { + PeerConnId string `json:"peerConnId"` + Role PeerConnRoleType `json:"role"` + Status PeerConnStatusType `json:"status"` + BandwidthInMbps int `json:"bandwidthInMbps"` + Description string `json:"description"` + LocalIfId string `json:"localIfId"` + LocalIfName string `json:"localIfName"` + LocalVpcId string `json:"localVpcId"` + LocalRegion string `json:"localRegion"` + PeerVpcId string `json:"peerVpcId"` + PeerRegion string `json:"peerRegion"` + PeerAccountId string `json:"peerAccountId"` + PaymentTiming string `json:"paymentTiming"` + DnsStatus DnsStatusType `json:"dnsStatus"` + CreatedTime string `json:"createdTime"` + ExpiredTime string `json:"expiredTime"` + Tags []model.TagModel `json:"tags"` +} + +// UpdatePeerConnArgs defines the structure of the input parameters for the UpdatePeerConn api +type UpdatePeerConnArgs struct { + LocalIfId string `json:"localIfId"` + Description string `json:"description,omitempty"` + LocalIfName string `json:"localIfName,omitempty"` +} + +// ResizePeerConnArgs defines the structure of the input parameters for the ResizePeerConn api +type ResizePeerConnArgs struct { + NewBandwidthInMbps int `json:"newBandwidthInMbps"` + ClientToken string `json:"-"` +} + +// RenewPeerConnArgs defines the structure of the input parameters for the RenewPeerConn api +type RenewPeerConnArgs struct { + Billing *Billing `json:"billing"` + ClientToken string `json:"-"` +} + +// PeerConnSyncDNSArgs defines the structure of the input parameters for the PeerConnSyncDNS api +type PeerConnSyncDNSArgs struct { + Role PeerConnRoleType `json:"role"` + ClientToken string `json:"-"` +} + +/* +Get VpcPrivateIpAddressedInfo args + + VpcId:the vpc you want to query ips + PrivateIpAddresses:the privateIp list you want to query + PrivateIpRange:the range of privateIp .ex:"192.168.0.1-192.168.0.5" + pay attention that the size of PrivateIpAddresses and PrivateIpRange must less than 100 + if both PrivateIpAddresses and PrivateIpRange ,the PrivateIpRange will effect +*/ +type GetVpcPrivateIpArgs struct { + VpcId string `json:"vpcId"` + PrivateIpAddresses []string `json:"privateIpAddresses",omitempty` + PrivateIpRange string `json:"privateIpRange,omitempty"` +} + +type VpcPrivateIpAddress struct { + PrivateIpAddress string `json:"privateIpAddress"` + Cidr string `json:"cidr"` + PrivateIpAddressType string `json:"privateIpAddressType` + CreatedTime string `json:"createdTime"` +} + +// VpcPrivateIpAddressesResult defines the structure of the output parameters for the GetPrivateIpAddressInfo api +type VpcPrivateIpAddressesResult struct { + VpcPrivateIpAddresses []VpcPrivateIpAddress `json:"vpcPrivateIpAddresses"` +} + +/* +Get NetworkTopologyInfo args + + HostIp:the host ip of the network topology to be queried + HostId:the host id of the network topology to be queried + If both HostIp and HostId are passed in, the HostIp will effect (only need to pass in one of the two) +*/ +type GetNetworkTopologyArgs struct { + HostIp string `json:"hostIp,omitempty"` + HostId string `json:"hostId,omitempty"` +} + +type NetworkTopology struct { + ClusterName string `json:"clusterName"` + HostId string `json:"hostId"` + SwitchId string `json:"switchId"` + HostIp string `json:"hostIp"` + PodName string `json:"podName"` +} + +// NetworkTopologyResult defines the structure of the output parameters for the GetNetworkTopologyInfo api +type NetworkTopologyResult struct { + NetworkTopologies []NetworkTopology `json:"networkTopologies"` +} + +type CreateProbeArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Protocol string `json:"protocol"` + Frequency int `json:"frequency"` + DestIp string `json:"destIp"` + DestPort string `json:"destPort"` + SourceIps []string `json:"sourceIps"` + SourceIpNum int `json:"sourceIpNum,omitempty"` + Description string `json:"description,omitempty"` + Payload string `json:"payload,omitempty"` +} + +type CreateProbeResult struct { + ProbeId string `json:"probeId"` +} + +type UpdateProbeArgs struct { + ClientToken string `json:"-"` + Name string `json:"name,omitempty"` + Frequency int `json:"frequency,omitempty"` + DestIp string `json:"destIp,omitempty"` + DestPort string `json:"destPort,omitempty"` + Description string `json:"description,omitempty"` + Payload string `json:"payload,omitempty"` +} + +type UpdateProbeResult struct { + ProbeId string `json:"probeId"` +} + +type ListProbesArgs struct { + Marker string + MaxKeys int +} + +type Probe struct { + ProbeId string `json:"probeId"` + Name string `json:"name"` + VpcId string `json:"vpcId"` + SubnetId string `json:"subnetId"` + Protocol string `json:"protocol"` + Frequency int `json:"frequency"` + DestIp string `json:"destIp"` + DestPort string `json:"destPort"` + SourceIps []string `json:"sourceIps"` + SourceIpNum int `json:"sourceIpNum,omitempty"` + Description string `json:"description,omitempty"` + Payload string `json:"payload,omitempty"` +} + +type ListProbesResult struct { + Probes []Probe `json:"probes"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +type GetProbeDetailResult struct { + Probe Probe `json:"probes"` +} + +// CreateIPv6GatewayArgs defines the structure of the input parameters for the CreateIPv6Gateway api +type CreateIPv6GatewayArgs struct { + ClientToken string `json:"-"` + Name string `json:"name"` + VpcId string `json:"vpcId"` + BandwidthInMbps int `json:"bandwidthInMbps"` + Billing *Billing `json:"billing"` +} + +// CreateIPv6GatewayResult defines the structure of the output parameters for the CreateIPv6Gateway api +type CreateIPv6GatewayResult struct { + GatewayId string `json:"gatewayId"` +} + +// ListIPv6GatewayArgs defines the structure of the input parameters for the ListIPv6Gateway api +type ListIPv6GatewayArgs struct { + VpcId string `json:"vpcId"` +} + +// DeleteIPv6GatewayArgs defines the structure of the input parameters for the DeleteIPv6Gateway api +type DeleteIPv6GatewayArgs struct { + ClientToken string `json:"-"` +} + +// ListIPv6GatewayResult defines the structure of the output parameters for the ListIPv6Gateway api +type ListIPv6GatewayResult struct { + GatewayId string `json:"gatewayId"` + Name string `json:"name"` + BandwidthInMbps int `json:"bandwidthInMbps"` + VpcId string `json:"vpcId"` + EgressOnlyRules []EgressOnlyRule `json:"egressOnlyRules"` + RateLimitRules []RateLimitRule `json:"rateLimitRules"` +} + +type EgressOnlyRule struct { + EgressOnlyRuleId string `json:"egressOnlyRuleId"` + Cidr string `json:"cidr"` +} + +type RateLimitRule struct { + RateLimitRuleId string `json:"rateLimitRuleId"` + IPv6Address string `json:"ipv6Address"` + IngressBandwidthInMbps int `json:"ingressBandwidthInMbps"` + EgressBandwidthInMbps int `json:"egressBandwidthInMbps"` +} + +// ResizeIPv6GatewayArgs defines the structure of the input parameters for the ResizeIPv6Gateway api +type ResizeIPv6GatewayArgs struct { + ClientToken string `json:"-"` + BandwidthInMbps int `json:"bandwidthInMbps"` +} + +// CreateIPv6GatewayEgressOnlyRuleArgs defines the structure of the input parameters for the CreateIPv6GatewayEgressOnlyRule api +type CreateIPv6GatewayEgressOnlyRuleArgs struct { + ClientToken string `json:"-"` + Cidr string `json:"cidr"` +} + +// CreateIPv6GatewayEgressOnlyRuleResult defines the structure of the output parameters for the CreateIPv6GatewayEgressOnlyRule api +type CreateIPv6GatewayEgressOnlyRuleResult struct { + EgressOnlyRuleId string `json:"egressOnlyRuleId"` +} + +// ListIPv6GatewayEgressOnlyRuleArgs defines the structure of the input parameters for the ListIPv6GatewayEgressOnlyRule api +type ListIPv6GatewayEgressOnlyRuleArgs struct { + Marker string + MaxKeys int +} + +// DeleteIPv6GatewayEgressOnlyRuleArgs defines the structure of the input parameters for the DeleteIPv6GatewayEgressOnlyRule api +type DeleteIPv6GatewayEgressOnlyRuleArgs struct { + ClientToken string `json:"-"` +} + +// ListIPv6GatewayEgressOnlyRuleResult defines the structure of the output parameters for the ListIPv6GatewayEgressOnlyRule api +type ListIPv6GatewayEgressOnlyRuleResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + EgressOnlyRules []EgressOnlyRule `json:"egressOnlyRules"` +} + +// CreateIPv6GatewayRateLimitRuleArgs defines the structure of the input parameters for the CreateIPv6GatewayRateLimitRule api +type CreateIPv6GatewayRateLimitRuleArgs struct { + ClientToken string `json:"-"` + IPv6Address string `json:"ipv6Address"` + IngressBandwidthInMbps int `json:"ingressBandwidthInMbps"` + EgressBandwidthInMbps int `json:"egressBandwidthInMbps"` +} + +// CreateIPv6GatewayRateLimitRuleResult defines the structure of the output parameters for the CreateIPv6GatewayRateLimitRule api +type CreateIPv6GatewayRateLimitRuleResult struct { + RateLimitRuleId string `json:"rateLimitRuleId"` +} + +// ListIPv6GatewayRateLimitRuleArgs defines the structure of the input parameters for the ListIPv6GatewayRateLimitRule api +type ListIPv6GatewayRateLimitRuleArgs struct { + Marker string + MaxKeys int +} + +// DeleteIPv6GatewayRateLimitRuleArgs defines the structure of the input parameters for the DeleteIPv6GatewayRateLimitRule api +type DeleteIPv6GatewayRateLimitRuleArgs struct { + ClientToken string `json:"-"` +} + +// ListIPv6GatewayRateLimitRuleResult defines the structure of the output parameters for the ListIPv6GatewayRateLimitRule api +type ListIPv6GatewayRateLimitRuleResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + RateLimitRules []RateLimitRule `json:"rateLimitRules"` +} + +// UpdateIPv6GatewayRateLimitRuleArgs defines the structure of the input parameters for the UpdateIPv6GatewayRateLimitRule api +type UpdateIPv6GatewayRateLimitRuleArgs struct { + ClientToken string `json:"-"` + IngressBandwidthInMbps int `json:"ingressBandwidthInMbps"` + EgressBandwidthInMbps int `json:"egressBandwidthInMbps"` +} + +type CreateVpcDhcpArgs struct { + ClientToken string `json:"-"` + DomainNameServers string `json:"domainNameServers"` +} + +type UpdateVpcDhcpArgs struct { + ClientToken string `json:"-"` + DomainNameServers string `json:"domainNameServers"` +} + +type VpcDhcpInfo struct { + VPCID string `json:"vpcId"` + DhcpOptions DhcpOptions `json:"dhcpOptions"` +} + +type DhcpOptions struct { + DomainNameServers string `json:"domainNameServers"` +} diff --git a/bce-sdk-go/services/vpc/nat.go b/bce-sdk-go/services/vpc/nat.go new file mode 100644 index 0000000..cb1f030 --- /dev/null +++ b/bce-sdk-go/services/vpc/nat.go @@ -0,0 +1,412 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// nat.go - the nat gateway APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateNatGateway - create a new nat gateway +// +// PARAMS: +// - args: the arguments to create nat gateway +// +// RETURNS: +// - *CreateNatGatewayResult: the id of the nat gateway newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateNatGateway(args *CreateNatGatewayArgs) (*CreateNatGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The createNatGatewayArgs cannot be nil.") + } + + result := &CreateNatGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNat()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListNatGateway - list all nat gateways with the specific parameters +// +// PARAMS: +// - args: the arguments to list nat gateways +// +// RETURNS: +// - *ListNatGatewayResult: the result of nat gateway list +// - error: nil if success otherwise the specific error +func (c *Client) ListNatGateway(args *ListNatGatewayArgs) (*ListNatGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The listNatGatewayArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListNatGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNat()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithQueryParamFilter("natId", args.NatId). + WithQueryParamFilter("name", args.Name). + WithQueryParamFilter("ip", args.Ip). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetNatGatewayDetail - get details of the specific nat gateway +// +// PARAMS: +// - natId: the id of the specified nat +// +// RETURNS: +// - *NAT: the result of the specific nat gateway details +// - error: nil if success otherwise the specific error +func (c *Client) GetNatGatewayDetail(natId string) (*NAT, error) { + result := &NAT{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateNatGateway - update the specified nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to update nat gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateNatGateway(natId string, args *UpdateNatGatewayArgs) error { + if args == nil { + return fmt.Errorf("The updateNatGatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// BindEips - bind eips for the specific nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to bind eips +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindEips(natId string, args *BindEipsArgs) error { + if args == nil { + return fmt.Errorf("The bindEipArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("bind", ""). + Do() +} + +// UnBindEips - unbind eips for the specific nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to unbind eips +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindEips(natId string, args *UnBindEipsArgs) error { + if args == nil { + return fmt.Errorf("The unBindEipArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("unbind", ""). + Do() +} + +// BindDnatEips - bind dnatEips for the specific nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to bind dnatEips +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindDnatEips(natId string, args *BindDnatEipsArgs) error { + if args == nil { + return fmt.Errorf("The bindDnatEipsArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("bind", ""). + Do() +} + +// UnBindDnatEips - unbind dnatEips for the specific nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to unbind dnatEips +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindDnatEips(natId string, args *UnBindDnatEipsArgs) error { + if args == nil { + return fmt.Errorf("the unBindDnatEipArgs cannot be nil") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("unbind", ""). + Do() +} + +// DeleteNatGateway - delete the specific nat gateway +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteNatGateway(natId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// RenewNatGateway - renew nat gateway with the specific parameters +// +// PARAMS: +// - natId: the id of the specific nat gateway +// - args: the arguments to renew nat gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenewNatGateway(natId string, args *RenewNatGatewayArgs) error { + if args == nil { + return fmt.Errorf("The renewNatGatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("purchaseReserved", ""). + Do() +} + +func (c *Client) CreateNatGatewaySnatRule(natId string, args *CreateNatGatewaySnatRuleArgs) (*CreateNatGatewaySnatRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateNatGatewaySnatRuleArgs cannot be nil.") + } + + result := &CreateNatGatewaySnatRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/snatRule"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) BatchCreateNatGatewaySnatRule(args *BatchCreateNatGatewaySnatRuleArgs) (*BatchCreateNatGatewaySnatRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("The BatchCreateNatGatewaySnatRuleArgs cannot be nil.") + } + + result := &BatchCreateNatGatewaySnatRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNat()+"/snatRule/batchCreate"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) DeleteNatGatewaySnatRule(natId string, snatRuleId string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/snatRule/"+snatRuleId). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +func (c *Client) UpdateNatGatewaySnatRule(natId string, snatRuleId string, args *UpdateNatGatewaySnatRuleArgs) error { + if args == nil { + return fmt.Errorf("The UpdateNatGatewaySnatRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/snatRule/"+snatRuleId). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +func (c *Client) ListNatGatewaySnatRules(args *ListNatGatewaySnatRuleArgs) (*ListNatGatewaySnatRulesResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListNatGatewaySnatRuleArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListNatGatewaySnatRulesResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(args.NatId)+"/snatRule"). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) CreateNatGatewayDnatRule(natId string, args *CreateNatGatewayDnatRuleArgs) (*CreateNatGatewayDnatRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateNatGatewayDnatRuleArgs cannot be nil.") + } + + result := &CreateNatGatewayDnatRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/dnatRule"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) BatchCreateNatGatewayDnatRule(natId string, args *BatchCreateNatGatewayDnatRuleArgs) (*BatchCreateNatGatewayDnatRuleResult, error) { + if args == nil { + return nil, fmt.Errorf("The BatchCreateNatGatewayDnatRuleArgs cannot be nil.") + } + + result := &BatchCreateNatGatewayDnatRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/dnatRule/batchCreate"). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) DeleteNatGatewayDnatRule(natId string, dnatRuleId string, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/dnatRule/"+dnatRuleId). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +func (c *Client) UpdateNatGatewayDnatRule(natId string, dnatRuleId string, args *UpdateNatGatewayDnatRuleArgs) error { + if args == nil { + return fmt.Errorf("The UpdateNatGatewayDnatRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/dnatRule/"+dnatRuleId). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +func (c *Client) ListNatGatewayDnatRules(natId string, args *ListNatGatewaDnatRuleArgs) (*ListNatGatewayDnatRulesResult, error) { + if args == nil { + return nil, fmt.Errorf("The ListNatGatewaDnatRuleArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListNatGatewayDnatRulesResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)+"/dnatRule"). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +func (c *Client) ResizeNatGateway(natId string, args *ResizeNatGatewayArgs) error { + if args == nil { + return fmt.Errorf("The ResizeNatGatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForNatId(natId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("resize", " "). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} diff --git a/bce-sdk-go/services/vpc/peerconn.go b/bce-sdk-go/services/vpc/peerconn.go new file mode 100644 index 0000000..d6ff381 --- /dev/null +++ b/bce-sdk-go/services/vpc/peerconn.go @@ -0,0 +1,259 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// peerconn.go - the peer connection APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreatePeerConn - create a new peer connection with the specific parameters +// +// PARAMS: +// - args: the arguments to create peer connection +// +// RETURNS: +// - *CreatePeerConnResult: the id of peer connection newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreatePeerConn(args *CreatePeerConnArgs) (*CreatePeerConnResult, error) { + if args == nil { + return nil, fmt.Errorf("The createPeerConnArgs cannot be nil.") + } + + result := &CreatePeerConnResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForPeerConn()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListPeerConn - list all peer connections with the specific parameters +// +// PARAMS: +// - args: the arguments to list peer connections +// +// RETURNS: +// - *ListPeerConnsResult: the result of the peer connection list +// - error: nil if success otherwise the specific error +func (c *Client) ListPeerConn(args *ListPeerConnsArgs) (*ListPeerConnsResult, error) { + if args == nil { + args = &ListPeerConnsArgs{} + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListPeerConnsResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForPeerConn()). + WithMethod(http.GET). + WithQueryParamFilter("vpcId", args.VpcId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetPeerConnDetail - get details for the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - role: the role of the specific peer connection, which can be initiator or acceptor +// +// RETURNS: +// - *PeerConn: the result of the specfic peer connection details +// - error: nil if success otherwise the specific error +func (c *Client) GetPeerConnDetail(peerConnId string, role PeerConnRoleType) (*PeerConn, error) { + result := &PeerConn{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.GET). + WithQueryParamFilter("role", string(role)). + WithResult(result). + Do() + + return result, err +} + +// UpdatePeerConn - update the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - args: the arguments to update the specific peer connection +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdatePeerConn(peerConnId string, args *UpdatePeerConnArgs) error { + if args == nil { + return fmt.Errorf("The updatePeerConnArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithBody(args). + Do() +} + +// AcceptPeerConnApply - accept the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) AcceptPeerConnApply(peerConnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithQueryParam("accept", ""). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// RejectPeerConnApply - reject the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RejectPeerConnApply(peerConnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithQueryParam("reject", ""). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// DeletePeerConn - delete the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeletePeerConn(peerConnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ResizePeerConn - resize the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - args: the arguments to resize the peer connection +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ResizePeerConn(peerConnId string, args *ResizePeerConnArgs) error { + if args == nil { + return fmt.Errorf("The resizePeerConnArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParam("resize", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// RenewPeerConn - renew the specific peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - args: the arguments to renew the peer connection +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenewPeerConn(peerConnId string, args *RenewPeerConnArgs) error { + if args == nil { + return fmt.Errorf("The renewPeerConnArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParam("purchaseReserved", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// OpenPeerConnSyncDNS - open the dns synchronization for the given peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - args: the arguments to open dns synchronization +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) OpenPeerConnSyncDNS(peerConnId string, args *PeerConnSyncDNSArgs) error { + if args == nil { + return fmt.Errorf("The peerConnSyncDNS cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithQueryParam("open", ""). + WithQueryParamFilter("role", string(args.Role)). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} + +// ClosePeerConnSyncDNS - close the dns synchronization for the given peer connection +// +// PARAMS: +// - peerConnId: the id of the specific peer connection +// - args: the arguments to close dns synchronization +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) ClosePeerConnSyncDNS(peerConnId string, args *PeerConnSyncDNSArgs) error { + if args == nil { + return fmt.Errorf("The peerConnSyncDNS cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForPeerConnId(peerConnId)). + WithMethod(http.PUT). + WithQueryParam("close", ""). + WithQueryParamFilter("role", string(args.Role)). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} diff --git a/bce-sdk-go/services/vpc/probe.go b/bce-sdk-go/services/vpc/probe.go new file mode 100644 index 0000000..beafa5f --- /dev/null +++ b/bce-sdk-go/services/vpc/probe.go @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// probe.go - the probe APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateProbe - create a new probe with the specified parameters +// +// PARAMS: +// - args: the arguments to create a probe +// +// RETURNS: +// - *CreateProbeResult: the ID of the probe newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateProbe(args *CreateProbeArgs) (*CreateProbeResult, error) { + if args == nil { + return nil, fmt.Errorf("CreateProbeResult cannot be nil.") + } + result := &CreateProbeResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForProbe()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// UpdateProbe - update a probe with the specified parameters +// +// PARAMS: +// - probeId: the ID of the probe to be updated +// - args: the arguments to update the probe +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateProbe(probeId string, args *UpdateProbeArgs) error { + if probeId == "" { + return fmt.Errorf("The probeId cannot be blank.") + } + if args == nil { + return fmt.Errorf("UpdateProbeArgs cannot be nil.") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForProbeId(probeId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("modifyAttribute", ""). + Do() +} + +// DeleteProbe - delete a probe +// PARAMS: +// - probeId: the ID of the probe to be deleted +// - clientToken: the client token of the request +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteProbe(probeId string, clientToken string) error { + if probeId == "" { + return fmt.Errorf("The probeId cannot be blank.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForProbeId(probeId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ListProbes - list all probes of the user +// +// PARAMS: +// - args: the arguments to list probes +// +// RETURNS: +// - *ListProbesResult: the infromation of all probes +// - error: nil if success otherwise the specific error +func (c *Client) ListProbes(args *ListProbesArgs) (*ListProbesResult, error) { + if args == nil { + args = &ListProbesArgs{} + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("The field maxKeys is out of range [0, 1000]") + } else if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListProbesResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForProbe()). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetProbeDetail - get details of a probe +// +// PARAMS: +// - probeId: the ID of the probe to get +// +// RETURNS: +// - *Probe: the infromation of the probe +// - error: nil if success otherwise the specific error +func (c *Client) GetProbeDetail(probeId string) (*Probe, error) { + if probeId == "" { + return nil, fmt.Errorf("The probeId cannot be blank.") + } + + result := &Probe{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForProbeId(probeId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/vpc/route.go b/bce-sdk-go/services/vpc/route.go new file mode 100644 index 0000000..15251d1 --- /dev/null +++ b/bce-sdk-go/services/vpc/route.go @@ -0,0 +1,166 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// route.go - the route APIs definition supported by the VPC service + +package vpc + +import ( + "errors" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// GetRouteTableDetail - get details of the given routeTableId or vpcId +// +// PARAMS: +// - routeTableId: the id of the specific route table +// - vpcId: the id of the specific VPC +// +// RETURNS: +// - *GetRouteTableResult: the result of route table details +// - error: nil if success otherwise the specific error +func (c *Client) GetRouteTableDetail(routeTableId, vpcId string) (*GetRouteTableResult, error) { + if routeTableId == "" && vpcId == "" { + return nil, errors.New("The routeTableId and vpcId cannot be blank at the same time.") + } + + result := &GetRouteTableResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForRouteTable()). + WithMethod(http.GET). + WithQueryParamFilter("routeTableId", routeTableId). + WithQueryParamFilter("vpcId", vpcId). + WithResult(result). + Do() + + return result, err +} + +// CreateRouteRule - create a new route rule with the given parameters +// +// PARAMS: +// - args: the arguments to create route rule +// +// RETURNS: +// - *CreateRouteRuleResult: the id of the route rule newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateRouteRule(args *CreateRouteRuleArgs) (*CreateRouteRuleResult, error) { + if args == nil { + return nil, errors.New("CreateRouteRuleArgs cannot be nil.") + } + + result := &CreateRouteRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForRouteRule()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// DeleteRouteRule - delete the given routing rule +// +// PARAMS: +// - routeRuleId: the id of the specific routing rule +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteRouteRule(routeRuleId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForRouteRuleId(routeRuleId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// switchRoute +// +// PARAMS: +// - routeRuleId: 主路由规则ID +// - clientToken: the idempotent token, 可选 +// - action: 切换路由规则的动作,可选值:switchRouteHA +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) SwitchRoute(routeRuleId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForRouteRuleId(routeRuleId)). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParamFilter("action", "switchRouteHA"). + Do() +} + +// GetRouteTableDetail - get details of the given routeTableId or vpcId +// +// PARAMS: +// - routeTableId: the id of the specific route table +// - vpcId: the id of the specific VPC +// +// RETURNS: +// - *GetRouteTableResult: the result of route table details +// - error: nil if success otherwise the specific error +func (c *Client) GetRouteRuleDetail(args *GetRouteRuleArgs) (*GetRouteRuleResult, error) { + if args.RouteTableId == "" && args.VpcId == "" { + return nil, errors.New("The RouteTableId and VpcId cannot be blank at the same time.") + } + + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &GetRouteRuleResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForRouteRule()). + WithMethod(http.GET). + WithQueryParamFilter("routeTableId", args.RouteTableId). + WithQueryParamFilter("vpcId", args.VpcId). + WithQueryParamFilter("marker", args.Marker). + WithQueryParam("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// UpdateRouteRule - update the given route rule +// +// PARAMS: +// - routeRuleId: the id of the specific route rule +// - args: the arguments to update route rule +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateRouteRule(args *UpdateRouteRuleArgs) error { + if args.RouteRuleId == "" { + return errors.New("The RouteRuleId cannot be blank.") + } + if args == nil { + return errors.New("The UpdateRouteRuleArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForRouteRuleId(args.RouteRuleId)). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + Do() +} diff --git a/bce-sdk-go/services/vpc/subnet.go b/bce-sdk-go/services/vpc/subnet.go new file mode 100644 index 0000000..d5f2c58 --- /dev/null +++ b/bce-sdk-go/services/vpc/subnet.go @@ -0,0 +1,236 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// subnet.go - the subnet APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateSubnet - create a new subnet with the specified parameters +// +// PARAMS: +// - args: the arguments to create subnet +// +// RETURNS: +// - *CreateSubnetResult: the ID of the subnet newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateSubnet(args *CreateSubnetArgs) (*CreateSubnetResult, error) { + if args == nil { + return nil, fmt.Errorf("CreateSubnetArgs cannot be nil.") + } + + result := &CreateSubnetResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSubnet()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListSubnets - list all subnets with the specified parameters +// +// PARAMS: +// - args: the arguments to list subnets +// - : +// +// RETURNS: +// - *ListSubnetResult: the result of all subnets +// - error: nil if success otherwise the specific error +func (c *Client) ListSubnets(args *ListSubnetArgs) (*ListSubnetResult, error) { + if args == nil { + args = &ListSubnetArgs{} + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("The field maxKeys is out of range [0, 1000]") + } else if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListSubnetResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSubnet()). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("vpcId", args.VpcId). + WithQueryParamFilter("zoneName", args.ZoneName). + WithQueryParamFilter("subnetType", string(args.SubnetType)). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// GetSubnetDetail - get details of the given subnet +// +// PARAMS: +// - subnetId: the id of the specified subnet +// +// RETURNS: +// - *GetSubnetDetailResult: the result of the given subnet details +// - error: nil if success otherwise the specific error +func (c *Client) GetSubnetDetail(subnetId string) (*GetSubnetDetailResult, error) { + if subnetId == "" { + return nil, fmt.Errorf("The subnetId cannot be blank.") + } + + result := &GetSubnetDetailResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSubnetId(subnetId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateSubnet - update the given subnet +// +// PARAMS: +// - subnetId: the id of the given subnet +// - args: the arguments to update subnet +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSubnet(subnetId string, args *UpdateSubnetArgs) error { + if subnetId == "" { + return fmt.Errorf("The subnetId cannot be blank.") + } + if args == nil { + return fmt.Errorf("The UpdateSubnetArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForSubnetId(subnetId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("modifyAttribute", ""). + Do() +} + +// DeleteSubnet - delete the given subnet +// +// PARAMS: +// - subnetId: the id of the specified subnet +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSubnet(subnetId string, clientToken string) error { + if subnetId == "" { + return fmt.Errorf("The subnetId cannot be blank.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForSubnetId(subnetId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// CreateReservedCIDR - delete the given ReservedCIDR +// +// PARAMS: +// - ipReserveId: the id of the reserved subnet +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateIpreserve(args *CreateIpreserveArgs) (*CreateIpreserveResult, error) { + if args.SubnetId == "" { + return nil, fmt.Errorf("SubnetId cannot be nil.") + } + + if args.IpCidr == "" { + return nil, fmt.Errorf("ipCidr cannot be blank.") + } + + if args.IpVersion != 4 && args.IpVersion != 6 { + return nil, fmt.Errorf("wrong ipVersion.") + } + + result := &CreateIpreserveResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpreserve()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// DeleteIpreserve - delete the given ReservedCIDR +// +// PARAMS: +// - ipReserveId: the id of the reserved subnet +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteIpreserve(ipReserveId, clientToken string) error { + if ipReserveId == "" { + return fmt.Errorf("The ipReserveId cannot be blank.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForDeleteIpreserve(ipReserveId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// ListIpreserve - list all reserved CIDRs with the specified parameters +// +// PARAMS: +// - args: the arguments to list reserved CIDRs +// +// RETURNS: +// - *ListReservedCIDRResult: the result of all reserved CIDRs +// - error: nil if success otherwise the specific error +func (c *Client) ListIpreserve(args *ListIpeserveArgs) (*ListIpeserveResult, error) { + if args == nil { + args = &ListIpeserveArgs{} + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("The field maxKeys is out of range [0, 1000]") + } else if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListIpeserveResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForIpreserve()). + WithMethod(http.GET). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("subnetId", args.SubnetId). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} diff --git a/bce-sdk-go/services/vpc/vpc.go b/bce-sdk-go/services/vpc/vpc.go new file mode 100644 index 0000000..025ee15 --- /dev/null +++ b/bce-sdk-go/services/vpc/vpc.go @@ -0,0 +1,240 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// vpc.go - the vpc APIs definition supported by the VPC service + +package vpc + +import ( + "fmt" + "strconv" + + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" +) + +// CreateVPC - create a new VPC with the specified parameters +// +// PARAMS: +// - args: the arguments to create VPC +// +// RETURNS: +// - *CreateVPCResult: the id of the VPC newly created +// - error: nil if success otherwise the specific error +func (c *Client) CreateVPC(args *CreateVPCArgs) (*CreateVPCResult, error) { + if args == nil { + return nil, fmt.Errorf("The createVPCArgs cannot be nil.") + } + + result := &CreateVPCResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPC()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// ListVPC - list all VPCs with the specified parameters +// +// PARAMS: +// - args: the arguments to list VPCs +// +// RETURNS: +// - *ListVPCResult: the result of all VPCs +// - error: nil if success otherwise the specific error +func (c *Client) ListVPC(args *ListVPCArgs) (*ListVPCResult, error) { + if args == nil { + args = &ListVPCArgs{} + } + if args.IsDefault != "" && args.IsDefault != "true" && args.IsDefault != "false" { + return nil, fmt.Errorf("The field isDefault can only be true or false.") + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("The field maxKeys is out of range [0, 1000]") + } + + result := &ListVPCResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForVPC()). + WithMethod(http.GET). + WithResult(result). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("isDefault", args.IsDefault) + if args.MaxKeys != 0 { + builder.WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + } + err := builder.Do() + + return result, err +} + +// GetVPCDetail - get details of the specified VPC +// +// PARAMS: +// - vpcId: the VPC id +// +// RETURNS: +// - *GetVPCDetailResult: the details of the specified VPC +// - error: nil if success otherwise the specific error +func (c *Client) GetVPCDetail(vpcId string) (*GetVPCDetailResult, error) { + result := &GetVPCDetailResult{} + + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateVPC - update a specified VPC +// +// PARAMS: +// - vpcId: the id of the specified VPC +// - updateVPCArgs: the arguments to udpate VPC +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateVPC(vpcId string, updateVPCArgs *UpdateVPCArgs) error { + if updateVPCArgs == nil { + return fmt.Errorf("The updateVPCArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId)). + WithMethod(http.PUT). + WithQueryParam("modifyAttribute", ""). + WithBody(updateVPCArgs). + WithQueryParamFilter("clientToken", updateVPCArgs.ClientToken). + Do() +} + +// DeleteVPC - delete a specified VPC +// +// PARAMS: +// - vpcId: the VPC id to be deleted +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteVPC(vpcId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// GetPrivateIpAddressesInfo - get the privateIpAddressesInfo from vpc +// +// PARAMS: +// - getVpcPrivateIpArgs: the arguments to GetPrivateIpAddressInfo +// +// RETURNS: +// - *VpcPrivateIpAddressesResult: the privateIpAddresses info of the specified privateIps in specified vpc +// - error: nil if success otherwise the specific error +func (c *Client) GetPrivateIpAddressesInfo(args *GetVpcPrivateIpArgs) (*VpcPrivateIpAddressesResult, error) { + if args == nil { + return nil, fmt.Errorf("The GetVpcPrivateIpArgs cannot be nil.") + } + result := &VpcPrivateIpAddressesResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(args.VpcId)+"/privateIpAddressInfo"). + WithMethod(http.GET).WithQueryParamFilter("privateIpRange", args.PrivateIpRange) + if len(args.PrivateIpAddresses) != 0 { + for i := range args.PrivateIpAddresses { + builder.WithQueryParam("privateIpAddresses", args.PrivateIpAddresses[i]) + } + } + err := builder.WithResult(result).Do() + return result, err +} + +// GetNetworkTopologyInfo - get the network topology info +// +// PARAMS: +// - getNetworkTopologyArgs: the arguments to GetNetworkTopologyInfo +// +// RETURNS: +// - *NetworkTopologyResult: the network topologies info obtained based on host ip or host id +// - error: nil if success otherwise the specific error +func (c *Client) GetNetworkTopologyInfo(args *GetNetworkTopologyArgs) (*NetworkTopologyResult, error) { + if args == nil { + return nil, fmt.Errorf("The GetNetworkTopologyArgs cannot be nil.") + } + result := &NetworkTopologyResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForNetworkTopology()). + WithMethod(http.GET). + WithQueryParamFilter("hostIp", args.HostIp). + WithQueryParamFilter("hostId", args.HostId) + err := builder.WithResult(result).Do() + return result, err +} + +// CreateVPCDhcp - create vpc's dhcp info with the specified parameters +// +// PARAMS: +// - args: the arguments to create vpc's dhcp info +func (c *Client) CreateVPCDhcp(vpcId string, createVpcDhcpArgs *CreateVpcDhcpArgs) error { + if createVpcDhcpArgs == nil { + return fmt.Errorf("The CreateVPCDhcp cannot be nil.") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId)+"/dhcp"). + WithMethod(http.POST). + WithBody(createVpcDhcpArgs). + WithQueryParamFilter("clientToken", createVpcDhcpArgs.ClientToken). + Do() +} + +// UpdateVPCDhcp - update vpc's dhcp info with the specified parameters +// +// if domainNameServers is nil, will delete vpc's dhcp. +// +// PARAMS: +// - args: the arguments to create vpc's dhcp info +func (c *Client) UpdateVPCDhcp(vpcId string, updateVpcDhcpArgs *UpdateVpcDhcpArgs) error { + if updateVpcDhcpArgs == nil { + return fmt.Errorf("The UpdateVPCDhcp cannot be nil.") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId)+"/dhcp"). + WithMethod(http.PUT). + WithBody(updateVpcDhcpArgs). + WithQueryParamFilter("clientToken", updateVpcDhcpArgs.ClientToken). + Do() +} + +// GetVPCDhcpInfo - get the dhcp info of specified vpc +// PARAMS: +// - args: the vpc id to get vpc's dhcp info +// +// RETURNS: +// - *VpcDhcpInfo: the info of the VPC dhcp +// - error: nil if success otherwise the specific error +func (c *Client) GetVPCDhcpInfo(vpcId string) (*VpcDhcpInfo, error) { + result := &VpcDhcpInfo{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPCId(vpcId) + "/dhcp"). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} diff --git a/bce-sdk-go/services/vpn/client.go b/bce-sdk-go/services/vpn/client.go new file mode 100644 index 0000000..7515522 --- /dev/null +++ b/bce-sdk-go/services/vpn/client.go @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// client.go - define the client for VPC service + +// Package vpn defines the vpn services of BCE. +// The supported APIs are all defined in different files. +package vpn + +import ( + "github.com/baidubce/bce-sdk-go/bce" +) + +const ( + URI_PREFIX = bce.URI_PREFIX + "v1" + + DEFAULT_ENDPOINT = "bcc." + bce.DEFAULT_REGION + ".baidubce.com" + + REQUEST_VPN_URL = "/vpn" +) + +// Client of VPC service is a kind of BceClient, so derived from BceClient +type Client struct { + *bce.BceClient +} + +func NewClient(ak, sk, endPoint string) (*Client, error) { + if len(endPoint) == 0 { + endPoint = DEFAULT_ENDPOINT + } + client, err := bce.NewBceClientWithAkSk(ak, sk, endPoint) + if err != nil { + return nil, err + } + return &Client{client}, nil +} + +func getURLForVPN() string { + return URI_PREFIX + REQUEST_VPN_URL +} + +func getURLForVPNId(vpnId string) string { + return getURLForVPN() + "/" + vpnId +} + +func getURLForVpnConn() string { + return getURLForVPN() + "/vpnconn" +} +func getURLForVpnConnId(vpnConnId string) string { + return getURLForVPN() + "/vpnconn/" + vpnConnId +} + +func getURLForSslVpnServerByVpnId(vpnId string) string { + return getURLForVPNId(vpnId) + "/sslVpnServer" +} + +func getURLForSslVpnUserByVpnId(vpnId string) string { + return getURLForVPNId(vpnId) + "/sslVpnUser" +} diff --git a/bce-sdk-go/services/vpn/client_test.go b/bce-sdk-go/services/vpn/client_test.go new file mode 100644 index 0000000..f3a8b61 --- /dev/null +++ b/bce-sdk-go/services/vpn/client_test.go @@ -0,0 +1,359 @@ +package vpn + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/baidubce/bce-sdk-go/util" + "github.com/baidubce/bce-sdk-go/util/log" +) + +var ( + VPN_CLIENT *Client + + region string + + VPNID string +) + +// For security reason, ak/sk should not hard write here. +type Conf struct { + AK string `json:"AK"` + SK string `json:"SK"` + VPCEndpoint string `json:"VPC"` + EIPEndpoint string `json:"EIP"` +} + +func init() { + log.SetLogHandler(log.STDERR) + log.SetLogLevel(log.DEBUG) + _, f, _, _ := runtime.Caller(0) + // Get the directory of GOPATH, the config file should be under the directory. + for i := 0; i < 7; i++ { + f = filepath.Dir(f) + } + conf := filepath.Join(f, "config.json") + fp, err := os.Open(conf) + if err != nil { + log.Fatal("config json file of ak/sk not given:", conf) + os.Exit(1) + } + decoder := json.NewDecoder(fp) + confObj := &Conf{} + decoder.Decode(confObj) + + VPN_CLIENT, _ = NewClient(confObj.AK, confObj.SK, confObj.VPCEndpoint) + + region = confObj.VPCEndpoint[4:6] +} + +// ExpectEqual is the helper function for test each case +func ExpectEqual(alert func(format string, args ...interface{}), + expected interface{}, actual interface{}) bool { + expectedValue, actualValue := reflect.ValueOf(expected), reflect.ValueOf(actual) + equal := false + switch { + case expected == nil && actual == nil: + return true + case expected != nil && actual == nil: + equal = expectedValue.IsNil() + case expected == nil && actual != nil: + equal = actualValue.IsNil() + default: + if actualType := reflect.TypeOf(actual); actualType != nil { + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + equal = reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + } + } + if !equal { + _, file, line, _ := runtime.Caller(1) + alert("%s:%d: missmatch, expect %v but %v", file, line, expected, actual) + return false + } + return true +} + +func TestClient_CreateVpnGateway(t *testing.T) { + args := &CreateVpnGatewayArgs{ + VpnName: "TestSDK-VPN", + Description: "vpn test", + VpcId: "vpc-2pa2x0bjt26i", + Billing: &Billing{ + PaymentTiming: PAYMENT_TIMING_PREPAID, + Reservation: &Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, + ClientToken: getClientToken(), + } + result, err := VPN_CLIENT.CreateVpnGateway(args) + ExpectEqual(t.Errorf, nil, err) + VPNID := result.VpnId + log.Debug(VPNID) +} + +func TestClient_ListVpnGateway(t *testing.T) { + args := &ListVpnGatewayArgs{ + MaxKeys: 1000, + VpcId: "vpc-2pa2x0bjt26i", + } + res, err := VPN_CLIENT.ListVpnGateway(args) + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(res) + fmt.Println(string(r)) + +} + +func TestClient_BindEip(t *testing.T) { + args := &BindEipArgs{ + ClientToken: getClientToken(), + Eip: "100.88.4.213", + } + err := VPN_CLIENT.BindEip("vpn-sd3vxkwisgux", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UnBindEip(t *testing.T) { + err := VPN_CLIENT.UnBindEip("vpn-sd3vxkwisgux", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_UpdateVpnGateway(t *testing.T) { + args := &UpdateVpnGatewayArgs{ + ClientToken: getClientToken(), + Name: "vpnTest", + } + err := VPN_CLIENT.UpdateVpnGateway("vpn-sd3vxkwisgux", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_GetVpnGatewayDetail(t *testing.T) { + result, err := VPN_CLIENT.GetVpnGatewayDetail("vpn-shr6dtz1jjbk") + ExpectEqual(t.Errorf, nil, err) + r, err := json.Marshal(result) + fmt.Println(string(r)) + +} + +func TestClient_RenewVpnGateway(t *testing.T) { + args := &RenewVpnGatewayArgs{ + ClientToken: getClientToken(), + Billing: &Billing{ + Reservation: &Reservation{ + ReservationLength: 1, + ReservationTimeUnit: "month", + }, + }, + } + err := VPN_CLIENT.RenewVpnGateway("vpn-smt119kcvqb1", args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_ListVpnConn(t *testing.T) { + res, err := VPN_CLIENT.ListVpnConn("vpn-shr6dtz1jjbk") + ExpectEqual(t.Errorf, nil, err) + log.Debug("%+v", res) + r, _ := json.Marshal(*res) + fmt.Println(string(r)) + +} +func TestClient_UpdateVpnConn(t *testing.T) { + args := &UpdateVpnConnArgs{ + VpnConnId: "vpnconn-mpfwkca8zsuv", + UpdateVpnconn: &CreateVpnConnArgs{ + VpnId: "vpn-shr6dtz1jjbk", + VpnConnName: "vpnconntest", + LocalIp: "0.1.2.3", + SecretKey: "!sdse154d", + LocalSubnets: []string{"192.168.0.0/20"}, + RemoteIp: "3.4.5.6", + RemoteSubnets: []string{"192.168.100.0/24"}, + CreateIkeConfig: &CreateIkeConfig{ + IkeVersion: "v1", + IkeMode: "main", + IkeEncAlg: "aes", + IkeAuthAlg: "sha1", + IkePfs: "group2", + IkeLifeTime: 25500, + }, + CreateIpsecConfig: &CreateIpsecConfig{ + IpsecEncAlg: "aes", + IpsecAuthAlg: "sha1", + IpsecPfs: "group2", + IpsecLifetime: 25500, + }, + }, + } + err := VPN_CLIENT.UpdateVpnConn(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateVpnConn(t *testing.T) { + args := &CreateVpnConnArgs{ + VpnId: "vpn-shr6dtz1jjbk", + VpnConnName: "vpnconntest111", + LocalIp: "0.1.2.3", + SecretKey: "!sdse154d", + LocalSubnets: []string{"192.168.0.0/20"}, + RemoteIp: "3.4.5.6", + RemoteSubnets: []string{"192.168.100.0/24"}, + CreateIkeConfig: &CreateIkeConfig{ + IkeVersion: "v1", + IkeMode: "main", + IkeEncAlg: "aes", + IkeAuthAlg: "sha1", + IkePfs: "group2", + IkeLifeTime: 25500, + }, + CreateIpsecConfig: &CreateIpsecConfig{ + IpsecEncAlg: "aes", + IpsecAuthAlg: "sha1", + IpsecPfs: "group2", + IpsecLifetime: 25500, + }, + } + res, err := VPN_CLIENT.CreateVpnConn(args) + ExpectEqual(t.Errorf, nil, err) + log.Debug("%+v", res) +} + +func TestClient_DeleteVpnConn(t *testing.T) { + err := VPN_CLIENT.DeleteVpnConn("vpnconn-mpfwkca8zsuv", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteVpn(t *testing.T) { + err := VPN_CLIENT.DeleteVpn("vpn-sd3vxkwisgux", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_CreateSslVpnServer(t *testing.T) { + interfaceTypeStr := "tun" + ClientDnsStr := "" + args := &CreateSslVpnServerArgs{ + ClientToken: getClientToken(), + VpnId: "vpn-s5bi05y3yyds", // ssl vpn + SslVpnServerName: "server_1", + InterfaceType: &interfaceTypeStr, + LocalSubnets: []string{"192.168.0.0/20"}, + RemoteSubnet: "192.168.100.0/24", + ClientDns: &ClientDnsStr, + } + log.Info(args) + log.Info("args.InterfaceType:", args.InterfaceType) + log.Info("args.ClientDns:", args.ClientDns) + if args.InterfaceType == nil { + log.Info("args.InterfaceType is unassigned") + } + res, err := VPN_CLIENT.CreateSslVpnServer(args) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_GetSslVpnServer(t *testing.T) { + res, err := VPN_CLIENT.GetSslVpnServer("vpn-s5bi05y3yyds", getClientToken()) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_UpdateSslVpnServer(t *testing.T) { + clientDnsStr := "100.88.0.83" + args := &UpdateSslVpnServerArgs{ + ClientToken: getClientToken(), + VpnId: "vpn-s5bi05y3yyds", + SslVpnServerId: "sslvpn-s8aqk5iw6ki1", + UpdateSslVpnServer: &UpdateSslVpnServer{ + SslVpnServerName: "SslVpnServer1test twice", + LocalSubnets: []string{"192.168.0.0/20"}, + RemoteSubnet: "192.168.100.0/24", + ClientDns: &clientDnsStr, + }, + } + err := VPN_CLIENT.UpdateSslVpnServer(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteSslVpnServer(t *testing.T) { + err := VPN_CLIENT.DeleteSslVpnServer("vpn-s5bi05y3yyds", "sslvpn-1swvxqrzn1we", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_BatchCreateSslVpnUser(t *testing.T) { + desc1 := "user1 description" + args := &BatchCreateSslVpnUserArgs{ + ClientToken: getClientToken(), + VpnId: "vpn-s5bi05y3yyds", // ssl vpn + SslVpnUsers: []SslVpnUser{ + SslVpnUser{ + UserName: "user1test", + Password: "psd123456!", + Description: &desc1, + }, + SslVpnUser{ + UserName: "user2test", + Password: "psd123456!", + }, + }, + } + res, err := VPN_CLIENT.BatchCreateSslVpnUser(args) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(res) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_ListSslVpnUser(t *testing.T) { + args := &ListSslVpnUserArgs{ + MaxKeys: 1000, + VpnId: "vpn-s5bi05y3yyds", + } + result, err := VPN_CLIENT.ListSslVpnUser(args) + ExpectEqual(t.Errorf, nil, err) + e, err1 := json.Marshal(result) + if err1 != nil { + log.Error("json format result error") + } + log.Info(string(e)) +} + +func TestClient_UpdateSslVpnUser(t *testing.T) { + psdStr := "testpassword" + desc := "333" + args := &UpdateSslVpnUserArgs{ + ClientToken: getClientToken(), + VpnId: "vpn-s5bi05y3yyds", + UserId: "vpn-ssl-user-ebfysi53dye3", + SslVpnUser: &UpdateSslVpnUser{ + Password: psdStr, + Description: &desc, + }, + } + err := VPN_CLIENT.UpdateSslVpnUser(args) + ExpectEqual(t.Errorf, nil, err) +} + +func TestClient_DeleteSslVpnUser(t *testing.T) { + err := VPN_CLIENT.DeleteSslVpnUser("vpn-s5bi05y3yyds", "vpn-ssl-user-ggngktunui0k", getClientToken()) + ExpectEqual(t.Errorf, nil, err) +} + +func getClientToken() string { + return util.NewUUID() +} diff --git a/bce-sdk-go/services/vpn/model.go b/bce-sdk-go/services/vpn/model.go new file mode 100644 index 0000000..da5f7d5 --- /dev/null +++ b/bce-sdk-go/services/vpn/model.go @@ -0,0 +1,290 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// model.go - definitions of the request arguments and results data structure model + +package vpn + +type ( + PaymentTimingType string + PeerConnRoleType string + VpnStatusType string +) + +const ( + PAYMENT_TIMING_PREPAID PaymentTimingType = "Prepaid" + PAYMENT_TIMING_POSTPAID PaymentTimingType = "Postpaid" + + VPN_STATUS_BUILDING VpnStatusType = "building" + VPN_STATUS_UNCONFIGURED VpnStatusType = "unconfigured" + VPN_STATUS_CONFIGURING VpnStatusType = "configuring" + VPN_STATUS_ACTIVE VpnStatusType = "active" +) + +// CreateVpnGatewayArgs defines the structure of the input parameters for the CreateVpnGateway api +type CreateVpnGatewayArgs struct { + ClientToken string `json:"-"` + VpnName string `json:"vpnName"` + VpcId string `json:"vpcId"` + Description string `json:"description,omitempty"` + Eip string `json:"eip,omitempty"` + Billing *Billing `json:"billing"` +} + +type Reservation struct { + ReservationLength int `json:"reservationLength"` + ReservationTimeUnit string `json:"reservationTimeUnit"` +} + +type Billing struct { + PaymentTiming PaymentTimingType `json:"paymentTiming,omitempty"` + Reservation *Reservation `json:"reservation,omitempty"` +} + +// CreateVpnGatewayResult defines the structure of the output parameters for the CreateVpnGateway api +type CreateVpnGatewayResult struct { + VpnId string `json:"vpnId"` +} + +// ListVpnGatewayArgs defines the structure of the input parameters for the ListVpnGateway api +type ListVpnGatewayArgs struct { + VpcId string + Eip string + Marker string + MaxKeys int +} + +// ListVpnGatewayResult defines the structure of the output parameters for the ListVpnGateway api +type ListVpnGatewayResult struct { + Vpns []VPN `json:"vpns"` + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` +} + +// VPN is the result for getVpnDetail api. +type VPN struct { + Status VpnStatusType `json:"status"` + Eip string `json:"eip"` + VpnId string `json:"vpnId"` + VpcId string `json:"vpcId"` + Description string `json:"description"` + ExpiredTime string `json:"expiredTime"` + ProductType string `json:"paymentTiming"` + VpnConnNum int `json:"vpnConnNum"` + BandwidthInMbps int `json:"bandwidthInMbps"` + VpnConns []VpnConn `json:"vpnConns"` + Name string `json:"vpnName"` +} + +// UpdateVpnGatewayArgs defines the structure of the input parameters for the UpdateVpnGateway api +type UpdateVpnGatewayArgs struct { + ClientToken string `json:"-"` + Name string `json:"vpnName"` +} + +// BindEipArgs defines the structure of the input parameters for the BindEip api +type BindEipArgs struct { + ClientToken string `json:"-"` + Eip string `json:"eip"` +} + +type VpnConn struct { + VpnId string `json:"vpnId"` + VpnConnId string `json:"vpnConnId"` + VpnConnName string `json:"vpnConnName"` + LocalIp string `json:"localIp"` + SecretKey string `json:"secretKey"` + LocalSubnets []string `json:"localSubnets"` + RemoteIp string `json:"remoteIp"` + RemoteSubnets []string `json:"remoteSubnets"` + Description string `json:"description"` + Status string `json:"status"` + CreatedTime string `json:"createdTime"` + HealthStatus string `json:"healthStatus"` + IkeConfig IkeConfig `json:"ikeConfig"` + IpsecConfig IpsecConfig `json:"ipsecConfig"` +} + +type IkeConfig struct { + IkeVersion string `json:"ikeVersion"` + IkeMode string `json:"ikeMode"` + IkeEncAlg string `json:"ikeEncAlg"` + IkeAuthAlg string `json:"ikeAuthAlg"` + IkePfs string `json:"ikePfs"` + IkeLifeTime string `json:"ikeLifeTime"` +} +type IpsecConfig struct { + IpsecEncAlg string `json:"ipsecEncAlg"` + IpsecAuthAlg string `json:"ipsecAuthAlg"` + IpsecPfs string `json:"ipsecPfs"` + IpsecLifetime string `json:"ipsecLifetime"` +} + +// RenewVpnGatewayArgs defines the structure of the input parameters for the RenewVpnGateway api +type RenewVpnGatewayArgs struct { + ClientToken string `json:"-"` + Billing *Billing `json:"billing"` +} + +type CreateIkeConfig struct { + IkeVersion string `json:"ikeVersion"` + IkeMode string `json:"ikeMode"` + IkeEncAlg string `json:"ikeEncAlg"` + IkeAuthAlg string `json:"ikeAuthAlg"` + IkePfs string `json:"ikePfs"` + IkeLifeTime int `json:"ikeLifeTime"` +} +type CreateIpsecConfig struct { + IpsecEncAlg string `json:"ipsecEncAlg"` + IpsecAuthAlg string `json:"ipsecAuthAlg"` + IpsecPfs string `json:"ipsecPfs"` + IpsecLifetime int `json:"ipsecLifetime"` +} + +// CreateVpnConnArgs defines the structure of the input parameters for the CreateVpnGatewayConn api +type CreateVpnConnArgs struct { + ClientToken string `json:"-"` + VpnId string `json:"vpnId"` + VpnConnName string `json:"vpnConnName"` + LocalIp string `json:"localIp"` + SecretKey string `json:"secretKey"` + LocalSubnets []string `json:"localSubnets"` + RemoteIp string `json:"remoteIp"` + RemoteSubnets []string `json:"remoteSubnets"` + Description string `json:"description,omitempty"` + CreateIkeConfig *CreateIkeConfig `json:"ikeConfig"` + CreateIpsecConfig *CreateIpsecConfig `json:"ipsecConfig"` +} + +// CreateVpnConnResult defines the structure of the output parameters for the CreateVpnConn api +type CreateVpnConnResult struct { + VpnConnId string `json:"vpnConnId"` +} + +// UpdateVpnConnArgs defines the structure of input parameters for the UpdateVpnConn api +type UpdateVpnConnArgs struct { + VpnConnId string `json:"vpnConnId"` + UpdateVpnconn *CreateVpnConnArgs `json:"updateVpnconn"` +} + +// ListVpnConnResult defines the structure of output parameters for the ListVpnConn api +type ListVpnConnResult struct { + VpnConns []VpnConn `json:"vpnConns"` +} + +// SslVpnServer defines the structure of the output parameters for the GetSslVpnServer api +type SslVpnServer struct { + VpnId string `json:"vpnId"` + SslVpnServerId string `json:"sslVpnServerId"` + SslVpnServerName string `json:"sslVpnServerName"` + InterfaceType string `json:"interfaceType"` + Status string `json:"status"` + LocalSubnets []string `json:"localSubnets"` + RemoteSubnet string `json:"remoteSubnet"` + ClientDns string `json:"clientDns"` + MaxConnection int `json:"maxConnection"` +} + +// CreateSslVpnServerArgs defines the structure of the input parameters for the CreateSslVpnServer api +type CreateSslVpnServerArgs struct { + ClientToken string `json:"-"` + VpnId string `json:"vpnId"` + SslVpnServerName string `json:"sslVpnServerName"` + InterfaceType *string `json:"interfaceType,omitempty"` + LocalSubnets []string `json:"localSubnets"` + RemoteSubnet string `json:"remoteSubnet"` + ClientDns *string `json:"clientDns,omitempty"` +} + +// UpdateSslVpnServer defines part of the structure of the input parameters for the UpdateSslVpnServer api +type UpdateSslVpnServer struct { + SslVpnServerName string `json:"sslVpnServerName,omitempty"` + LocalSubnets []string `json:"localSubnets,omitempty"` + RemoteSubnet string `json:"remoteSubnet,omitempty"` + ClientDns *string `json:"clientDns,omitempty"` +} + +// CreateSslVpnServerResult defines the structure of the output parameters for the CreateSslVpnServer api +type CreateSslVpnServerResult struct { + SslVpnServerId string `json:"sslVpnServerId"` +} + +// UpdateSslVpnServerArgs defines the structure of input parameters for the UpdateSslVpnServer api +type UpdateSslVpnServerArgs struct { + ClientToken string `json:"-"` + VpnId string `json:"VpnId"` + SslVpnServerId string `json:"sslVpnServerId"` + UpdateSslVpnServer *UpdateSslVpnServer `json:"updateSslVpnServer"` +} + +// ListSslVpnServerResult defines the structure of output parameters for the ListSslVpnServer api +type ListSslVpnServerResult struct { + SslVpnServers []SslVpnServer `json:"sslVpnServers"` +} + +type SslVpnUser struct { + UserName string `json:"userName"` + Password string `json:"password"` + Description *string `json:"description,omitempty"` +} + +type SelectSslVpnUser struct { + UserId string `json:"userId"` + UserName string `json:"userName"` + Description string `json:"description"` +} + +type UpdateSslVpnUser struct { + Password string `json:"password,omitempty"` + Description *string `json:"description,omitempty"` +} + +// BatchCreateSslVpnUserArgs defines the structure of the input parameters for the CreateSslVpnUser api +type BatchCreateSslVpnUserArgs struct { + ClientToken string `json:"-"` + VpnId string `json:"vpnId"` + SslVpnUsers []SslVpnUser `json:"sslVpnUsers"` +} + +// BatchCreateSslVpnUserResult defines the structure of the output parameters for the BatchCreateSslVpnUser api +type BatchCreateSslVpnUserResult struct { + SslVpnUserIds []string `json:"sslVpnUserIds"` +} + +// UpdateSslVpnUserArgs defines the structure of input parameters for the UpdateSslVpnUser api +type UpdateSslVpnUserArgs struct { + ClientToken string `json:"-"` + VpnId string `json:"vpnId"` + UserId string `json:"userId"` + SslVpnUser *UpdateSslVpnUser `json:"sslVpnUser"` +} + +// ListSslVpnUserArgs defines the structure of input parameters for the ListSslVpnUser api +type ListSslVpnUserArgs struct { + Marker string `json:"marker"` + MaxKeys int `json:"maxKeys"` + VpnId string `json:"vpnId"` + UserName string `json:"username"` +} + +// ListSslVpnUserResult defines the structure of output parameters for the ListSslVpnUser api +type ListSslVpnUserResult struct { + Marker string `json:"marker"` + IsTruncated bool `json:"isTruncated"` + NextMarker string `json:"nextMarker"` + MaxKeys int `json:"maxKeys"` + SslVpnUsers []SelectSslVpnUser `json:"sslVpnUsers"` +} diff --git a/bce-sdk-go/services/vpn/vpn.go b/bce-sdk-go/services/vpn/vpn.go new file mode 100644 index 0000000..ae3b311 --- /dev/null +++ b/bce-sdk-go/services/vpn/vpn.go @@ -0,0 +1,455 @@ +/* + * Copyright 2020 Baidu, Inc. + * + * 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. + */ + +// vpn.go - the vpn gateway APIs definition supported by the VPN service + +package vpn + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/bce" + "github.com/baidubce/bce-sdk-go/http" + "strconv" +) + +// CreateVPNGateway - create a new vpn gateway +// +// PARAMS: +// - args: the arguments to create vpn gateway +// RETURNS: +// - *CreateVpnGatewayResult: the id of the vpn gateway newly created +// - error: nil if success otherwise the specific error + +func (c *Client) CreateVpnGateway(args *CreateVpnGatewayArgs) (*CreateVpnGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The createVpnGatewayArgs cannot be nil.") + } + + result := &CreateVpnGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPN()). + WithMethod(http.POST). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithResult(result). + Do() + + return result, err +} + +// +// ListVpn - list all vpn gateways with the specific parameters +// PARAMS: +// - args: the arguments to list vpn gateways +// RETURNS: +// - *ListVpnGatewayResult: the result of vpn gateway list +// - error: nil if success otherwise the specific error + +func (c *Client) ListVpnGateway(args *ListVpnGatewayArgs) (*ListVpnGatewayResult, error) { + if args == nil { + return nil, fmt.Errorf("The listVpnGatewayArgs cannot be nil.") + } + if args.MaxKeys == 0 { + args.MaxKeys = 1000 + } + + result := &ListVpnGatewayResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPN()). + WithMethod(http.GET). + WithQueryParam("vpcId", args.VpcId). + WithQueryParamFilter("eip", args.Eip). + WithQueryParamFilter("marker", args.Marker). + WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)). + WithResult(result). + Do() + + return result, err +} + +// DeleteVpnGateway - delete the specific vpn gateway +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - clientToken: the idempotent token +// RETURNS: +// - error: nil if success otherwise the specific error + +func (c *Client) DeleteVpn(vpnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// DeleteVpnGateway - delete the specific vpn gateway +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteVpnGateway(vpcId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpcId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// GetVpnGatewayDetail - get details of the specific vpn gateway +// +// PARAMS: +// - vpnId: the id of the specified vpn +// +// RETURNS: +// - *VPN: the result of the specific vpn gateway details +// - error: nil if success otherwise the specific error +func (c *Client) GetVpnGatewayDetail(vpnId string) (*VPN, error) { + result := &VPN{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.GET). + WithResult(result). + Do() + + return result, err +} + +// UpdateVpnGateway - update the specified vpn gateway +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - args: the arguments to update vpn gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateVpnGateway(vpnId string, args *UpdateVpnGatewayArgs) error { + if args == nil { + return fmt.Errorf("The updateVpnGatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParam("modifyAttribute", ""). + WithQueryParamFilter("clientToken", args.ClientToken). + Do() +} + +// BindEip - bind eip for the specific vpn gateway +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - args: the arguments to bind eip +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BindEip(vpnId string, args *BindEipArgs) error { + if args == nil { + return fmt.Errorf("The bindEipArgs cannot be nil.") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("bind", ""). + Do() +} + +// UnBindEips - unbind eip for the specific vpn gateway +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UnBindEip(vpnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", clientToken). + WithQueryParam("unbind", ""). + Do() +} + +// RenewVpnGateway - renew vpn gateway with the specific parameters +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - args: the arguments to renew vpn gateway +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) RenewVpnGateway(vpnId string, args *RenewVpnGatewayArgs) error { + if args == nil { + return fmt.Errorf("The renewVpnGatewayArgs cannot be nil.") + } + + return bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(vpnId)). + WithMethod(http.PUT). + WithBody(args). + WithQueryParamFilter("clientToken", args.ClientToken). + WithQueryParam("purchaseReserved", ""). + Do() +} + +// CreateVpnConn - create vpnconn with the specific parameters +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - args: the arguments to create vpnconn +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateVpnConn(args *CreateVpnConnArgs) (*CreateVpnConnResult, error) { + if args == nil { + return nil, fmt.Errorf("The CreateVpnConnArgs cannot be nil.") + } + result := &CreateVpnConnResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVPNId(args.VpnId) + "/vpnconn"). + WithMethod(http.POST). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateVpnConn - create vpnconn with the specific parameters +// +// PARAMS: +// - args: the arguments to update vpnconn +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateVpnConn(args *UpdateVpnConnArgs) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVpnConnId(args.VpnConnId)). + WithMethod(http.PUT). + WithBody(args.UpdateVpnconn). + Do() +} + +// ListVpnConn - list vpnconn with the specific vpnId +// +// PARAMS: +// - vpnId:the id you want to list vpnconn +// +// RETURNS: +// - *ListVpnConnResult: the result of vpn gateway'conn list +// - error: nil if success otherwise the specific error +func (c *Client) ListVpnConn(vpnId string) (*ListVpnConnResult, error) { + result := &ListVpnConnResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForVpnConn() + "/" + vpnId). + WithMethod(http.GET). + WithResult(result). + Do() + return result, err +} + +// DeleteVpnConn - delete the specific vpnconn +// +// PARAMS: +// - vpnConnId: the id of the specific vpnconn +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteVpnConn(vpnConnId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForVpnConnId(vpnConnId)). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// CreateSslVpnServer - create ssl-vpn server with the specific parameters +// +// PARAMS: +// - args: the arguments to create ssl-vpn server +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) CreateSslVpnServer(args *CreateSslVpnServerArgs) (*CreateSslVpnServerResult, error) { + if args == nil { + return nil, fmt.Errorf("the CreateSslVpnServerArgs cannot be nil") + } + + if args.InterfaceType == nil { + defaultInterfaceType := "tap" + args.InterfaceType = &defaultInterfaceType + } + result := &CreateSslVpnServerResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnServerByVpnId(args.VpnId)). + WithMethod(http.POST). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateSslVpnServer - update ssl-vpn server with the specific parameters +// +// PARAMS: +// - args: the arguments to update ssl ssl-vpn server +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSslVpnServer(args *UpdateSslVpnServerArgs) error { + if args == nil { + return fmt.Errorf("the UpdateSslVpnServerArgs cannot be nil") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnServerByVpnId(args.VpnId)+"/"+args.SslVpnServerId). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args.UpdateSslVpnServer). + Do() +} + +// GetSslVpnServer - get details of the specific ssl-vpn server +// +// PARAMS: +// - vpnId: the id of the specified vpn +// +// RETURNS: +// - *SslVpnServer: the result of the specific ssl-vpn server details +// - error: nil if success otherwise the specific error +func (c *Client) GetSslVpnServer(vpnId, clientToken string) (*SslVpnServer, error) { + result := &SslVpnServer{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnServerByVpnId(vpnId)). + WithMethod(http.GET). + WithQueryParamFilter("clientToken", clientToken). + WithResult(result). + Do() + + return result, err +} + +// DeleteSslVpnServer - delete ssl-vpn server with the specific vpnId +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - sslVpnServerId: the id of the specific ssl-vpn server +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSslVpnServer(vpnId, sslVpnServerId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnServerByVpnId(vpnId)+"/"+sslVpnServerId). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() +} + +// BatchCreateSslVpnUser - batch create ssl-vpn user with the specific parameters +// +// PARAMS: +// - args: the arguments to create ssl-vpn users +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) BatchCreateSslVpnUser(args *BatchCreateSslVpnUserArgs) (*BatchCreateSslVpnUserResult, error) { + if args == nil { + return nil, fmt.Errorf("the BatchCreateSslVpnUserArgs cannot be nil") + } + result := &BatchCreateSslVpnUserResult{} + err := bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnUserByVpnId(args.VpnId)). + WithMethod(http.POST). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args). + WithResult(result). + Do() + return result, err +} + +// UpdateSslVpnUser - update ssl-vpn user with the specific parameters +// +// PARAMS: +// - args: the arguments to update the specific ssl-vpn user +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) UpdateSslVpnUser(args *UpdateSslVpnUserArgs) error { + if args == nil { + return fmt.Errorf("the UpdateSslVpnUserArgs cannot be nil") + } + if len(args.SslVpnUser.Password) == 0 && args.SslVpnUser.Description == nil { + return fmt.Errorf("both password and description are nil") + } + return bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnUserByVpnId(args.VpnId)+"/"+args.UserId). + WithMethod(http.PUT). + WithQueryParamFilter("clientToken", args.ClientToken). + WithBody(args.SslVpnUser). + Do() +} + +// ListSslVpnUser - list ssl-vpn user with the specific vpnId +// +// PARAMS: +// - args: the arguments to list ssl-vpn users +// +// RETURNS: +// - *ListSslVpnUserResult: the result of vpn ssl-vpn user list contains page infos +// - error: nil if success otherwise the specific error +func (c *Client) ListSslVpnUser(args *ListSslVpnUserArgs) (*ListSslVpnUserResult, error) { + if args == nil { + args = &ListSslVpnUserArgs{} + } + if args.MaxKeys < 0 || args.MaxKeys > 1000 { + return nil, fmt.Errorf("the field maxKeys is out of range [0, 1000]") + } + result := &ListSslVpnUserResult{} + builder := bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnUserByVpnId(args.VpnId)). + WithMethod(http.GET). + WithResult(result). + WithQueryParamFilter("marker", args.Marker) + if args.MaxKeys != 0 { + builder.WithQueryParamFilter("maxKeys", strconv.Itoa(args.MaxKeys)) + } + err := builder.Do() + return result, err +} + +// DeleteSslVpnUser - delete ssl-vpn user with the specific vpnId and userId +// +// PARAMS: +// - vpnId: the id of the specific vpn gateway +// - userId: the id of the specific ssl-vpn user +// - clientToken: the idempotent token +// +// RETURNS: +// - error: nil if success otherwise the specific error +func (c *Client) DeleteSslVpnUser(vpnId, userId, clientToken string) error { + return bce.NewRequestBuilder(c). + WithURL(getURLForSslVpnUserByVpnId(vpnId)+"/"+userId). + WithMethod(http.DELETE). + WithQueryParamFilter("clientToken", clientToken). + Do() + +} diff --git a/bce-sdk-go/util/crypto/ebc.go b/bce-sdk-go/util/crypto/ebc.go new file mode 100644 index 0000000..34fa3fc --- /dev/null +++ b/bce-sdk-go/util/crypto/ebc.go @@ -0,0 +1,37 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "fmt" +) + +func EBCEncrypto(key, data []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + content := pKCS5Padding([]byte(data), block.BlockSize()) + crypted := make([]byte, len(content)) + + if len(content)%block.BlockSize() != 0 { + return nil, fmt.Errorf("crypto/cipher: input not full blocks") + } + + src := content + dst := crypted + for len(src) > 0 { + block.Encrypt(dst, src[:block.BlockSize()]) + src = src[block.BlockSize():] + dst = dst[block.BlockSize():] + } + + return crypted, nil +} + +func pKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} diff --git a/bce-sdk-go/util/log/cce_logger.go b/bce-sdk-go/util/log/cce_logger.go new file mode 100644 index 0000000..5d2c645 --- /dev/null +++ b/bce-sdk-go/util/log/cce_logger.go @@ -0,0 +1,15 @@ +package log + +var _ SDKLogger = &logger{} + +type SDKLogger interface { + Logging(level Level, format string, args ...interface{}) +} + +func (l *logger) Logging(level Level, format string, args ...interface{}) { + l.logging(level, format, args...) +} + +func SetLogger(logger SDKLogger) { + gDefaultLogger = logger +} diff --git a/bce-sdk-go/util/log/logger.go b/bce-sdk-go/util/log/logger.go new file mode 100644 index 0000000..85eb85f --- /dev/null +++ b/bce-sdk-go/util/log/logger.go @@ -0,0 +1,400 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// logger.go - defines the logger structure and methods + +// Package log implements the log facilities for BCE. It supports log to stderr, stdout as well as +// log to file with rotating. It is safe to be called by multiple goroutines. +// By using the package level function to use the default logger: +// +// log.SetLogHandler(log.STDOUT | log.FILE) // default is log to stdout +// log.SetLogDir("/tmp") // default is /tmp +// log.SetRotateType(log.ROTATE_DAY) // default is log.HOUR +// log.SetRotateSize(1 << 30) // default is 1GB +// log.SetLogLevel(log.INFO) // default is log.DEBUG +// log.Debug(1, 1.2, "a") +// log.Debugln(1, 1.2, "a") +// log.Debugf(1, 1.2, "a") +// +// User can also create new logger without using the default logger: +// +// customLogger := log.NewLogger() +// customLogger.SetLogHandler(log.FILE) +// customLogger.Debug(1, 1.2, "a") +// +// The log format can also support custom setting by using the following interface: +// +// log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_TIME, log.FMT_MSG}) +// +// Most of the cases just use the default format is enough: +// +// []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG} +package log + +import ( + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +type Handler uint8 + +// Constants for log handler flags, default is STDOUT +const ( + NONE Handler = 0 + STDOUT Handler = 1 + STDERR Handler = 1 << 1 + FILE Handler = 1 << 2 +) + +type RotateStrategy uint8 + +// Constants for log rotating strategy when logging to file, default is by hour +const ( + ROTATE_NONE RotateStrategy = iota + ROTATE_DAY + ROTATE_HOUR + ROTATE_MINUTE + ROTATE_SIZE + + DEFAULT_ROTATE_TYPE = ROTATE_HOUR + DEFAULT_ROTATE_SIZE int64 = 1 << 30 + DEFAULT_LOG_DIR = "/tmp" + ROTATE_SIZE_FILE_PREFIX = "rotating" +) + +type Level uint8 + +// Constants for log levels, default is DEBUG +const ( + DEBUG Level = iota + INFO + WARN + ERROR + FATAL + PANIC +) + +var gLevelString = [...]string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"} + +// Constants of the log format components to support user custom specification +const ( + FMT_LEVEL = "level" + FMT_LTIME = "ltime" // long time with microsecond + FMT_TIME = "time" // just with second + FMT_LOCATION = "location" // caller's location with file, line, function + FMT_MSG = "msg" +) + +var ( + LOG_FMT_STR = map[string]string{ + FMT_LEVEL: "[%s]", + FMT_LTIME: "2006-01-02 15:04:05.000000", + FMT_TIME: "2006-01-02 15:04:05", + FMT_LOCATION: "%s:%d:%s:", + FMT_MSG: "%s", + } + gDefaultLogFormat = []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG} +) + +type writerArgs struct { + record string + rotateArgs interface{} // used for rotating: the size of the record or the logging time +} + +// Logger defines the internal implementation of the log facility +type logger struct { + writers map[Handler]io.WriteCloser // the destination writer to log message + writerChan chan *writerArgs // the writer channal to pass each record and time or size + logFormat []string + levelThreshold Level + handler Handler + + // Fields that used when logging to file + logDir string + logFile string + rotateType RotateStrategy + rotateSize int64 + done chan bool +} + +func (l *logger) logging(level Level, format string, args ...interface{}) { + // Only log message that set the handler and is greater than or equal to the threshold + if l.handler == NONE || level < l.levelThreshold { + return + } + + // Generate the log record string and pass it to the writer channel + now := time.Now() + pc, file, line, ok, funcname := uintptr(0), "???", 0, true, "???" + pc, file, line, ok = runtime.Caller(2) + if ok { + funcname = runtime.FuncForPC(pc).Name() + funcname = filepath.Ext(funcname) + funcname = strings.TrimPrefix(funcname, ".") + file = filepath.Base(file) + } + buf := make([]string, 0, len(l.logFormat)) + msg := fmt.Sprintf(format, args...) + for _, f := range l.logFormat { + if _, exists := LOG_FMT_STR[f]; !exists { // skip not supported part + continue + } + fmtStr := LOG_FMT_STR[f] + switch f { + case FMT_LEVEL: + buf = append(buf, fmt.Sprintf(fmtStr, gLevelString[level])) + case FMT_LTIME: + buf = append(buf, now.Format(fmtStr)) + case FMT_TIME: + buf = append(buf, now.Format(fmtStr)) + case FMT_LOCATION: + buf = append(buf, fmt.Sprintf(fmtStr, file, line, funcname)) + case FMT_MSG: + buf = append(buf, fmt.Sprintf(fmtStr, msg)) + } + } + record := strings.Join(buf, " ") + if l.rotateType == ROTATE_SIZE { + l.writerChan <- &writerArgs{record, int64(len(record))} + } else { + l.writerChan <- &writerArgs{record, now} + } + + // wait for current record done logging +} + +func (l *logger) buildWriter(args interface{}) { + if l.handler&STDOUT == STDOUT { + l.writers[STDOUT] = os.Stdout + } else { + delete(l.writers, STDOUT) + } + if l.handler&STDERR == STDERR { + l.writers[STDERR] = os.Stderr + } else { + delete(l.writers, STDERR) + } + if l.handler&FILE == FILE { + l.writers[FILE] = l.buildFileWriter(args) + } else { + delete(l.writers, FILE) + } +} + +func (l *logger) buildFileWriter(args interface{}) io.WriteCloser { + if l.handler&FILE != FILE { + return os.Stderr + } + + if len(l.logDir) == 0 { + l.logDir = DEFAULT_LOG_DIR + } + if l.rotateType < ROTATE_NONE || l.rotateType > ROTATE_SIZE { + l.rotateType = DEFAULT_ROTATE_TYPE + } + if l.rotateType == ROTATE_SIZE && l.rotateSize == 0 { + l.rotateSize = DEFAULT_ROTATE_SIZE + } + + logFile, needCreateFile := "", false + if l.rotateType == ROTATE_SIZE { + recordSize, _ := args.(int64) + logFile, needCreateFile = l.buildFileWriterBySize(recordSize) + } else { + recordTime, _ := args.(time.Time) + switch l.rotateType { + case ROTATE_NONE: + logFile = "default.log" + case ROTATE_DAY: + logFile = recordTime.Format("2006-01-02.log") + case ROTATE_HOUR: + logFile = recordTime.Format("2006-01-02_15.log") + case ROTATE_MINUTE: + logFile = recordTime.Format("2006-01-02_15-04.log") + } + if _, exist := getFileInfo(filepath.Join(l.logDir, logFile)); !exist { + needCreateFile = true + } + } + l.logFile = logFile + logFile = filepath.Join(l.logDir, l.logFile) + + // Should create new file + if needCreateFile { + if w, ok := l.writers[FILE]; ok { + w.Close() + } + if writer, err := os.Create(logFile); err == nil { + return writer + } else { + return os.Stderr + } + } + + // Already open the file + if w, ok := l.writers[FILE]; ok { + return w + } + + // Newly open the file + if writer, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND, 0666); err == nil { + return writer + } else { + return os.Stderr + } +} + +func (l *logger) buildFileWriterBySize(recordSize int64) (string, bool) { + logFile, needCreateFile := "", false + // First running the program and need to get filename by checking the existed files + if len(l.logFile) == 0 { + fname := fmt.Sprintf("%s-%s.0.log", ROTATE_SIZE_FILE_PREFIX, getSizeString(l.rotateSize)) + for { + size, exist := getFileInfo(filepath.Join(l.logDir, fname)) + if !exist { + logFile, needCreateFile = fname, true + break + } + if exist && size+recordSize <= l.rotateSize { + logFile, needCreateFile = fname, false + break + } + fname = getNextFileName(fname) + } + } else { // check the file size to append to the existed file or create a new file + currentFile := filepath.Join(l.logDir, l.logFile) + size, exist := getFileInfo(currentFile) + if !exist { + logFile, needCreateFile = l.logFile, true + } else { + if size+recordSize > l.rotateSize { // size exceeded + logFile, needCreateFile = getNextFileName(l.logFile), true + } else { + logFile, needCreateFile = l.logFile, false + } + } + } + return logFile, needCreateFile +} + +func (l *logger) SetHandler(h Handler) { l.handler = h } + +func (l *logger) SetLogDir(dir string) { l.logDir = dir } + +func (l *logger) SetLogLevel(level Level) { l.levelThreshold = level } + +func (l *logger) SetLogFormat(format []string) { l.logFormat = format } + +func (l *logger) SetRotateType(rotate RotateStrategy) { l.rotateType = rotate } + +func (l *logger) SetRotateSize(size int64) { l.rotateSize = size } + +func (l *logger) Debug(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) } + +func (l *logger) Debugln(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) } + +func (l *logger) Debugf(f string, msg ...interface{}) { l.logging(DEBUG, f+"\n", msg...) } + +func (l *logger) Info(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) } + +func (l *logger) Infoln(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) } + +func (l *logger) Infof(f string, msg ...interface{}) { l.logging(INFO, f+"\n", msg...) } + +func (l *logger) Warn(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) } + +func (l *logger) Warnln(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) } + +func (l *logger) Warnf(f string, msg ...interface{}) { l.logging(WARN, f+"\n", msg...) } + +func (l *logger) Error(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) } + +func (l *logger) Errorln(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) } + +func (l *logger) Errorf(f string, msg ...interface{}) { l.logging(ERROR, f+"\n", msg...) } + +func (l *logger) Fatal(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) } + +func (l *logger) Fatalln(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) } + +func (l *logger) Fatalf(f string, msg ...interface{}) { l.logging(FATAL, f+"\n", msg...) } + +func (l *logger) Panic(msg ...interface{}) { + record := concat(msg...) + l.logging(PANIC, "%s\n", record) + panic(record) +} + +func (l *logger) Panicln(msg ...interface{}) { + record := concat(msg...) + l.logging(PANIC, "%s\n", record) + panic(record) +} + +func (l *logger) Panicf(format string, msg ...interface{}) { + record := fmt.Sprintf(format, msg...) + l.logging(PANIC, format+"\n", msg...) + panic(record) +} + +func (l *logger) Close() { + select { + case <-l.done: + return + default: + } + l.writerChan <- nil +} + +func NewLogger() *logger { + obj := &logger{ + writers: make(map[Handler]io.WriteCloser, 3), // now only support 3 kinds of handler + writerChan: make(chan *writerArgs, 100), + logFormat: gDefaultLogFormat, + levelThreshold: DEBUG, + handler: NONE, + done: make(chan bool), + } + // The backend writer goroutine to write each log record + go func() { + defer func() { + if e := recover(); e != nil { + fmt.Println(e) + } + }() + for { + select { + case <-obj.done: + return + case args := <-obj.writerChan: // wait until a record comes to log + if args == nil { + close(obj.done) + close(obj.writerChan) + return + } + obj.buildWriter(args.rotateArgs) + for _, w := range obj.writers { + fmt.Fprint(w, args.record) + } + } + } + }() + + return obj +} diff --git a/bce-sdk-go/util/log/util.go b/bce-sdk-go/util/log/util.go new file mode 100644 index 0000000..dc46a51 --- /dev/null +++ b/bce-sdk-go/util/log/util.go @@ -0,0 +1,243 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// util.go - define the package-level default logger and functions for easily use. + +package log + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// The global default logger object to perform logging in this package-level functions +var gDefaultLogger SDKLogger + +func init() { + gDefaultLogger = NewLogger() +} + +// Helper functions +func getFileInfo(path string) (int64, bool) { + info, err := os.Stat(path) + if err != nil { + return -1, false + } else { + return info.Size(), true + } +} + +func getSizeString(size int64) string { + if size < 0 { + return fmt.Sprintf("%d", size) + } + if size < (1 << 10) { + return fmt.Sprintf("%dB", size) + } else if size < (1 << 20) { + return fmt.Sprintf("%dkB", size>>10) + } else if size < (1 << 30) { + return fmt.Sprintf("%dMB", size>>20) + } else if size < (1 << 40) { + return fmt.Sprintf("%dGB", size>>30) + } else { + return "SUPER-LARGE" + } +} + +func getNextFileName(nowFileName string) string { + pos1 := strings.Index(nowFileName, ".") + rest := nowFileName[pos1+1:] + pos2 := strings.Index(rest, ".") + num, _ := strconv.Atoi(rest[0:pos2]) + return fmt.Sprintf("%s.%d.log", nowFileName[0:pos1], num+1) +} + +func concat(msg ...interface{}) string { + buf := make([]string, 0, len(msg)) + for _, m := range msg { + buf = append(buf, fmt.Sprintf("%v", m)) + } + return strings.Join(buf, " ") +} + +// Export package-level functions for logging messages by using the default logger. It supports 3 +// kinds of interface which is similar to the standard package "fmt": with suffix of "ln", "f" or +// nothing. +func Debug(msg ...interface{}) { + gDefaultLogger.Logging(DEBUG, "%s\n", concat(msg...)) +} + +func Debugln(msg ...interface{}) { + gDefaultLogger.Logging(DEBUG, "%s\n", concat(msg...)) +} + +func Debugf(format string, msg ...interface{}) { + gDefaultLogger.Logging(DEBUG, format+"\n", msg...) +} + +func Info(msg ...interface{}) { + gDefaultLogger.Logging(INFO, "%s\n", concat(msg...)) +} + +func Infoln(msg ...interface{}) { + gDefaultLogger.Logging(INFO, "%s\n", concat(msg...)) +} + +func Infof(format string, msg ...interface{}) { + gDefaultLogger.Logging(INFO, format+"\n", msg...) +} + +func Warn(msg ...interface{}) { + gDefaultLogger.Logging(WARN, "%s\n", concat(msg...)) +} + +func Warnln(msg ...interface{}) { + gDefaultLogger.Logging(WARN, "%s\n", concat(msg...)) +} + +func Warnf(format string, msg ...interface{}) { + gDefaultLogger.Logging(WARN, format+"\n", msg...) +} + +func Error(msg ...interface{}) { + gDefaultLogger.Logging(ERROR, "%s\n", concat(msg...)) +} + +func Errorln(msg ...interface{}) { + gDefaultLogger.Logging(ERROR, "%s\n", concat(msg...)) +} + +func Errorf(format string, msg ...interface{}) { + gDefaultLogger.Logging(ERROR, format+"\n", msg...) +} + +func Fatal(msg ...interface{}) { + gDefaultLogger.Logging(FATAL, "%s\n", concat(msg...)) +} + +func Fatalln(msg ...interface{}) { + gDefaultLogger.Logging(FATAL, "%s\n", concat(msg...)) +} + +func Fatalf(format string, msg ...interface{}) { + gDefaultLogger.Logging(FATAL, format+"\n", msg...) +} + +func Panic(msg ...interface{}) { + record := concat(msg...) + gDefaultLogger.Logging(PANIC, "%s\n", record) + panic(record) +} + +func Panicln(msg ...interface{}) { + record := concat(msg...) + gDefaultLogger.Logging(PANIC, "%s\n", record) + panic(record) +} + +func Panicf(format string, msg ...interface{}) { + record := fmt.Sprintf(format, msg...) + gDefaultLogger.Logging(PANIC, format+"\n", msg...) + panic(record) +} + +func Close() { + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.Close() + } +} + +// SetLogHandler - set the handler of the logger +// +// PARAMS: +// - Handler: the handler defined in this package, now just support STDOUT, STDERR, FILE +func SetLogHandler(h Handler) { + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.handler = h + } +} + +// SetLogLevel - set the level threshold of the logger, only level equal to or bigger than this +// value will be logged. +// +// PARAMS: +// - Level: the level defined in this package, now support 6 levels. +func SetLogLevel(l Level) { + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.levelThreshold = l + } +} + +// SetLogFormat - set the log component of each record when logging it. The default log format is +// {FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG}. +// +// PARAMS: +// - format: the format component array. +func SetLogFormat(format []string) { + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.logFormat = format + } +} + +// SetLogDir - set the logging directory if logging to file. +// +// PARAMS: +// - dir: the logging directory +// +// RETURNS: +// - error: check the directory and try to make it, otherwise return the error. +func SetLogDir(dir string) error { + if _, err := os.Stat(dir); err != nil { + if os.IsNotExist(err) { + if mkErr := os.Mkdir(dir, os.ModePerm); mkErr != nil { + return mkErr + } + } else { + return err + } + } + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.logDir = dir + } + return nil +} + +// SetRotateType - set the rotating strategy if logging to file. +// +// PARAMS: +// - RotateStrategy: the rotate strategy defined in this package, now support 5 strategy. +func SetRotateType(r RotateStrategy) { + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.rotateType = r + } +} + +// SetRotateSize - set the rotating size if logging to file and set the strategy of size. +// +// PARAMS: +// - size: the rotating size +// +// RETURNS: +// - error: check the value and return any error if error occurs. +func SetRotateSize(size int64) error { + if size <= 0 { + return fmt.Errorf("%s", "rotate size should not be negative") + } + if sdkLogger, ok := gDefaultLogger.(*logger); ok { + sdkLogger.rotateSize = size + } + return nil +} diff --git a/bce-sdk-go/util/mime.go b/bce-sdk-go/util/mime.go new file mode 100644 index 0000000..4246344 --- /dev/null +++ b/bce-sdk-go/util/mime.go @@ -0,0 +1,664 @@ +package util + +import ( + "sync" +) + +var DefaultMimeMap map[string]string +var once sync.Once + +func LoadMimeMap() { + DefaultMimeMap = make(map[string]string) + DefaultMimeMap[".ez"] = "application/andrew-inset" + DefaultMimeMap[".atom"] = "application/atom+xml" + DefaultMimeMap[".atomcat"] = "application/atomcat+xml" + DefaultMimeMap[".atomsvc"] = "application/atomsvc+xml" + DefaultMimeMap[".ccxml"] = "application/ccxml+xml" + DefaultMimeMap[".davmount"] = "application/davmount+xml" + DefaultMimeMap[".ecma"] = "application/ecmascript" + DefaultMimeMap[".pfr"] = "application/font-tdpfr" + DefaultMimeMap[".stk"] = "application/hyperstudio" + DefaultMimeMap[".js"] = "text/javascript" + DefaultMimeMap[".json"] = "text/json" + DefaultMimeMap[".hqx"] = "application/mac-binhex40" + DefaultMimeMap[".cpt"] = "application/mac-compactpro" + DefaultMimeMap[".mrc"] = "application/marc" + DefaultMimeMap[".ma"] = "application/mathematica" + DefaultMimeMap[".nb"] = "application/mathematica" + DefaultMimeMap[".mb"] = "application/mathematica" + DefaultMimeMap[".mathml"] = "application/mathml+xml" + DefaultMimeMap[".mbox"] = "application/mbox" + DefaultMimeMap[".mscml"] = "application/mediaservercontrol+xml" + DefaultMimeMap[".mp4s"] = "application/mp4" + DefaultMimeMap[".doc"] = "application/msword" + DefaultMimeMap[".dot"] = "application/msword" + DefaultMimeMap[".mxf"] = "application/mxf" + DefaultMimeMap[".bin"] = "application/octet-stream" + DefaultMimeMap[".dms"] = "application/octet-stream" + DefaultMimeMap[".lha"] = "application/octet-stream" + DefaultMimeMap[".lzh"] = "application/octet-stream" + DefaultMimeMap[".class"] = "application/octet-stream" + DefaultMimeMap[".so"] = "application/octet-stream" + DefaultMimeMap[".iso"] = "application/octet-stream" + DefaultMimeMap[".dmg"] = "application/octet-stream" + DefaultMimeMap[".dist"] = "application/octet-stream" + DefaultMimeMap[".distz"] = "application/octet-stream" + DefaultMimeMap[".pkg"] = "application/octet-stream" + DefaultMimeMap[".bpk"] = "application/octet-stream" + DefaultMimeMap[".dump"] = "application/octet-stream" + DefaultMimeMap[".elc"] = "application/octet-stream" + DefaultMimeMap[".oda"] = "application/oda" + DefaultMimeMap[".ogg"] = "application/ogg" + DefaultMimeMap[".pdf"] = "application/pdf" + DefaultMimeMap[".pgp"] = "application/pgp-encrypted" + DefaultMimeMap[".asc"] = "application/pgp-signature" + DefaultMimeMap[".sig"] = "application/pgp-signature" + DefaultMimeMap[".prf"] = "application/pics-rules" + DefaultMimeMap[".p10"] = "application/pkcs10" + DefaultMimeMap[".p7m"] = "application/pkcs7-mime" + DefaultMimeMap[".p7c"] = "application/pkcs7-mime" + DefaultMimeMap[".p7s"] = "application/pkcs7-signature" + DefaultMimeMap[".cer"] = "application/pkix-cert" + DefaultMimeMap[".crl"] = "application/pkix-crl" + DefaultMimeMap[".pkipath"] = "application/pkix-pkipath" + DefaultMimeMap[".pki"] = "application/pkixcmp" + DefaultMimeMap[".pls"] = "application/pls+xml" + DefaultMimeMap[".ai"] = "application/postscript" + DefaultMimeMap[".eps"] = "application/postscript" + DefaultMimeMap[".ps"] = "application/postscript" + DefaultMimeMap[".cww"] = "application/prs.cww" + DefaultMimeMap[".rdf"] = "application/rdf+xml" + DefaultMimeMap[".rif"] = "application/reginfo+xml" + DefaultMimeMap[".rnc"] = "application/relax-ng-compact-syntax" + DefaultMimeMap[".rl"] = "application/resource-lists+xml" + DefaultMimeMap[".rs"] = "application/rls-services+xml" + DefaultMimeMap[".rsd"] = "application/rsd+xml" + DefaultMimeMap[".rss"] = "application/rss+xml" + DefaultMimeMap[".rtf"] = "application/rtf" + DefaultMimeMap[".sbml"] = "application/sbml+xml" + DefaultMimeMap[".scq"] = "application/scvp-cv-request" + DefaultMimeMap[".scs"] = "application/scvp-cv-response" + DefaultMimeMap[".spq"] = "application/scvp-vp-request" + DefaultMimeMap[".spp"] = "application/scvp-vp-response" + DefaultMimeMap[".sdp"] = "application/sdp" + DefaultMimeMap[".setpay"] = "application/set-payment-initiation" + DefaultMimeMap[".setreg"] = "application/set-registration-initiation" + DefaultMimeMap[".shf"] = "application/shf+xml" + DefaultMimeMap[".smi"] = "application/smil+xml" + DefaultMimeMap[".smil"] = "application/smil+xml" + DefaultMimeMap[".rq"] = "application/sparql-query" + DefaultMimeMap[".srx"] = "application/sparql-results+xml" + DefaultMimeMap[".gram"] = "application/srgs" + DefaultMimeMap[".grxml"] = "application/srgs+xml" + DefaultMimeMap[".ssml"] = "application/ssml+xml" + DefaultMimeMap[".plb"] = "application/vnd.3gpp.pic-bw-large" + DefaultMimeMap[".psb"] = "application/vnd.3gpp.pic-bw-small" + DefaultMimeMap[".pvb"] = "application/vnd.3gpp.pic-bw-var" + DefaultMimeMap[".tcap"] = "application/vnd.3gpp2.tcap" + DefaultMimeMap[".pwn"] = "application/vnd.3m.post-it-notes" + DefaultMimeMap[".aso"] = "application/vnd.accpac.simply.aso" + DefaultMimeMap[".imp"] = "application/vnd.accpac.simply.imp" + DefaultMimeMap[".acu"] = "application/vnd.acucobol" + DefaultMimeMap[".atc"] = "application/vnd.acucorp" + DefaultMimeMap[".acutc"] = "application/vnd.acucorp" + DefaultMimeMap[".xdp"] = "application/vnd.adobe.xdp+xml" + DefaultMimeMap[".xfdf"] = "application/vnd.adobe.xfdf" + DefaultMimeMap[".ami"] = "application/vnd.amiga.ami" + DefaultMimeMap[".cii"] = "application/vnd.anser-web-certificate-issue-initiation" + DefaultMimeMap[".fti"] = "application/vnd.anser-web-funds-transfer-initiation" + DefaultMimeMap[".atx"] = "application/vnd.antix.game-component" + DefaultMimeMap[".mpkg"] = "application/vnd.apple.installer+xml" + DefaultMimeMap[".aep"] = "application/vnd.audiograph" + DefaultMimeMap[".mpm"] = "application/vnd.blueice.multipass" + DefaultMimeMap[".bmi"] = "application/vnd.bmi" + DefaultMimeMap[".rep"] = "application/vnd.businessobjects" + DefaultMimeMap[".cdxml"] = "application/vnd.chemdraw+xml" + DefaultMimeMap[".mmd"] = "application/vnd.chipnuts.karaoke-mmd" + DefaultMimeMap[".cdy"] = "application/vnd.cinderella" + DefaultMimeMap[".cla"] = "application/vnd.claymore" + DefaultMimeMap[".c4g"] = "application/vnd.clonk.c4group" + DefaultMimeMap[".c4d"] = "application/vnd.clonk.c4group" + DefaultMimeMap[".c4f"] = "application/vnd.clonk.c4group" + DefaultMimeMap[".c4p"] = "application/vnd.clonk.c4group" + DefaultMimeMap[".c4u"] = "application/vnd.clonk.c4group" + DefaultMimeMap[".csp"] = "application/vnd.commonspace" + DefaultMimeMap[".cst"] = "application/vnd.commonspace" + DefaultMimeMap[".cdbcmsg"] = "application/vnd.contact.cmsg" + DefaultMimeMap[".cmc"] = "application/vnd.cosmocaller" + DefaultMimeMap[".clkx"] = "application/vnd.crick.clicker" + DefaultMimeMap[".clkk"] = "application/vnd.crick.clicker.keyboard" + DefaultMimeMap[".clkp"] = "application/vnd.crick.clicker.palette" + DefaultMimeMap[".clkt"] = "application/vnd.crick.clicker.template" + DefaultMimeMap[".clkw"] = "application/vnd.crick.clicker.wordbank" + DefaultMimeMap[".wbs"] = "application/vnd.criticaltools.wbs+xml" + DefaultMimeMap[".pml"] = "application/vnd.ctc-posml" + DefaultMimeMap[".ppd"] = "application/vnd.cups-ppd" + DefaultMimeMap[".curl"] = "application/vnd.curl" + DefaultMimeMap[".rdz"] = "application/vnd.data-vision.rdz" + DefaultMimeMap[".fe_launch"] = "application/vnd.denovo.fcselayout-link" + DefaultMimeMap[".dna"] = "application/vnd.dna" + DefaultMimeMap[".mlp"] = "application/vnd.dolby.mlp" + DefaultMimeMap[".dpg"] = "application/vnd.dpgraph" + DefaultMimeMap[".dfac"] = "application/vnd.dreamfactory" + DefaultMimeMap[".mag"] = "application/vnd.ecowin.chart" + DefaultMimeMap[".nml"] = "application/vnd.enliven" + DefaultMimeMap[".esf"] = "application/vnd.epson.esf" + DefaultMimeMap[".msf"] = "application/vnd.epson.msf" + DefaultMimeMap[".qam"] = "application/vnd.epson.quickanime" + DefaultMimeMap[".slt"] = "application/vnd.epson.salt" + DefaultMimeMap[".ssf"] = "application/vnd.epson.ssf" + DefaultMimeMap[".es3"] = "application/vnd.eszigno3+xml" + DefaultMimeMap[".et3"] = "application/vnd.eszigno3+xml" + DefaultMimeMap[".ez2"] = "application/vnd.ezpix-album" + DefaultMimeMap[".ez3"] = "application/vnd.ezpix-package" + DefaultMimeMap[".fdf"] = "application/vnd.fdf" + DefaultMimeMap[".gph"] = "application/vnd.flographit" + DefaultMimeMap[".ftc"] = "application/vnd.fluxtime.clip" + DefaultMimeMap[".fm"] = "application/vnd.framemaker" + DefaultMimeMap[".frame"] = "application/vnd.framemaker" + DefaultMimeMap[".maker"] = "application/vnd.framemaker" + DefaultMimeMap[".fnc"] = "application/vnd.frogans.fnc" + DefaultMimeMap[".ltf"] = "application/vnd.frogans.ltf" + DefaultMimeMap[".fsc"] = "application/vnd.fsc.weblaunch" + DefaultMimeMap[".oas"] = "application/vnd.fujitsu.oasys" + DefaultMimeMap[".oa2"] = "application/vnd.fujitsu.oasys2" + DefaultMimeMap[".oa3"] = "application/vnd.fujitsu.oasys3" + DefaultMimeMap[".fg5"] = "application/vnd.fujitsu.oasysgp" + DefaultMimeMap[".bh2"] = "application/vnd.fujitsu.oasysprs" + DefaultMimeMap[".ddd"] = "application/vnd.fujixerox.ddd" + DefaultMimeMap[".xdw"] = "application/vnd.fujixerox.docuworks" + DefaultMimeMap[".xbd"] = "application/vnd.fujixerox.docuworks.binder" + DefaultMimeMap[".fzs"] = "application/vnd.fuzzysheet" + DefaultMimeMap[".txd"] = "application/vnd.genomatix.tuxedo" + DefaultMimeMap[".kml"] = "application/vnd.google-earth.kml+xml" + DefaultMimeMap[".kmz"] = "application/vnd.google-earth.kmz" + DefaultMimeMap[".gqf"] = "application/vnd.grafeq" + DefaultMimeMap[".gqs"] = "application/vnd.grafeq" + DefaultMimeMap[".gac"] = "application/vnd.groove-account" + DefaultMimeMap[".ghf"] = "application/vnd.groove-help" + DefaultMimeMap[".gim"] = "application/vnd.groove-identity-message" + DefaultMimeMap[".grv"] = "application/vnd.groove-injector" + DefaultMimeMap[".gtm"] = "application/vnd.groove-tool-message" + DefaultMimeMap[".tpl"] = "application/vnd.groove-tool-template" + DefaultMimeMap[".vcg"] = "application/vnd.groove-vcard" + DefaultMimeMap[".zmm"] = "application/vnd.handheld-entertainment+xml" + DefaultMimeMap[".hbci"] = "application/vnd.hbci" + DefaultMimeMap[".les"] = "application/vnd.hhe.lesson-player" + DefaultMimeMap[".hpgl"] = "application/vnd.hp-hpgl" + DefaultMimeMap[".hpid"] = "application/vnd.hp-hpid" + DefaultMimeMap[".hps"] = "application/vnd.hp-hps" + DefaultMimeMap[".jlt"] = "application/vnd.hp-jlyt" + DefaultMimeMap[".pcl"] = "application/vnd.hp-pcl" + DefaultMimeMap[".pclxl"] = "application/vnd.hp-pclxl" + DefaultMimeMap[".x3d"] = "application/vnd.hzn-3d-crossword" + DefaultMimeMap[".mpy"] = "application/vnd.ibm.minipay" + DefaultMimeMap[".afp"] = "application/vnd.ibm.modcap" + DefaultMimeMap[".listafp"] = "application/vnd.ibm.modcap" + DefaultMimeMap[".list3820"] = "application/vnd.ibm.modcap" + DefaultMimeMap[".irm"] = "application/vnd.ibm.rights-management" + DefaultMimeMap[".sc"] = "application/vnd.ibm.secure-container" + DefaultMimeMap[".igl"] = "application/vnd.igloader" + DefaultMimeMap[".ivp"] = "application/vnd.immervision-ivp" + DefaultMimeMap[".ivu"] = "application/vnd.immervision-ivu" + DefaultMimeMap[".xpw"] = "application/vnd.intercon.formnet" + DefaultMimeMap[".xpx"] = "application/vnd.intercon.formnet" + DefaultMimeMap[".qbo"] = "application/vnd.intu.qbo" + DefaultMimeMap[".qfx"] = "application/vnd.intu.qfx" + DefaultMimeMap[".rcprofile"] = "application/vnd.ipunplugged.rcprofile" + DefaultMimeMap[".irp"] = "application/vnd.irepository.package+xml" + DefaultMimeMap[".xpr"] = "application/vnd.is-xpr" + DefaultMimeMap[".jam"] = "application/vnd.jam" + DefaultMimeMap[".rms"] = "application/vnd.jcp.javame.midlet-rms" + DefaultMimeMap[".jisp"] = "application/vnd.jisp" + DefaultMimeMap[".joda"] = "application/vnd.joost.joda-archive" + DefaultMimeMap[".ktz"] = "application/vnd.kahootz" + DefaultMimeMap[".ktr"] = "application/vnd.kahootz" + DefaultMimeMap[".karbon"] = "application/vnd.kde.karbon" + DefaultMimeMap[".chrt"] = "application/vnd.kde.kchart" + DefaultMimeMap[".kfo"] = "application/vnd.kde.kformula" + DefaultMimeMap[".flw"] = "application/vnd.kde.kivio" + DefaultMimeMap[".kon"] = "application/vnd.kde.kontour" + DefaultMimeMap[".kpr"] = "application/vnd.kde.kpresenter" + DefaultMimeMap[".kpt"] = "application/vnd.kde.kpresenter" + DefaultMimeMap[".ksp"] = "application/vnd.kde.kspread" + DefaultMimeMap[".kwd"] = "application/vnd.kde.kword" + DefaultMimeMap[".kwt"] = "application/vnd.kde.kword" + DefaultMimeMap[".htke"] = "application/vnd.kenameaapp" + DefaultMimeMap[".kia"] = "application/vnd.kidspiration" + DefaultMimeMap[".kne"] = "application/vnd.kinar" + DefaultMimeMap[".knp"] = "application/vnd.kinar" + DefaultMimeMap[".skp"] = "application/vnd.koan" + DefaultMimeMap[".skd"] = "application/vnd.koan" + DefaultMimeMap[".skt"] = "application/vnd.koan" + DefaultMimeMap[".skm"] = "application/vnd.koan" + DefaultMimeMap[".lbd"] = "application/vnd.llamagraphics.life-balance.desktop" + DefaultMimeMap[".lbe"] = "application/vnd.llamagraphics.life-balance.exchange+xml" + DefaultMimeMap[".123"] = "application/vnd.lotus-1-2-3" + DefaultMimeMap[".apr"] = "application/vnd.lotus-approach" + DefaultMimeMap[".pre"] = "application/vnd.lotus-freelance" + DefaultMimeMap[".nsf"] = "application/vnd.lotus-notes" + DefaultMimeMap[".org"] = "application/vnd.lotus-organizer" + DefaultMimeMap[".scm"] = "application/vnd.lotus-screencam" + DefaultMimeMap[".lwp"] = "application/vnd.lotus-wordpro" + DefaultMimeMap[".portpkg"] = "application/vnd.macports.portpkg" + DefaultMimeMap[".mcd"] = "application/vnd.mcd" + DefaultMimeMap[".mc1"] = "application/vnd.medcalcdata" + DefaultMimeMap[".cdkey"] = "application/vnd.mediastation.cdkey" + DefaultMimeMap[".mwf"] = "application/vnd.mfer" + DefaultMimeMap[".mfm"] = "application/vnd.mfmp" + DefaultMimeMap[".flo"] = "application/vnd.micrografx.flo" + DefaultMimeMap[".igx"] = "application/vnd.micrografx.igx" + DefaultMimeMap[".mif"] = "application/vnd.mif" + DefaultMimeMap[".daf"] = "application/vnd.mobius.daf" + DefaultMimeMap[".dis"] = "application/vnd.mobius.dis" + DefaultMimeMap[".mbk"] = "application/vnd.mobius.mbk" + DefaultMimeMap[".mqy"] = "application/vnd.mobius.mqy" + DefaultMimeMap[".msl"] = "application/vnd.mobius.msl" + DefaultMimeMap[".plc"] = "application/vnd.mobius.plc" + DefaultMimeMap[".txf"] = "application/vnd.mobius.txf" + DefaultMimeMap[".mpn"] = "application/vnd.mophun.application" + DefaultMimeMap[".mpc"] = "application/vnd.mophun.certificate" + DefaultMimeMap[".cil"] = "application/vnd.ms-artgalry" + DefaultMimeMap[".asf"] = "application/vnd.ms-asf" + DefaultMimeMap[".cab"] = "application/vnd.ms-cab-compressed" + DefaultMimeMap[".xls"] = "application/vnd.ms-excel" + DefaultMimeMap[".xlm"] = "application/vnd.ms-excel" + DefaultMimeMap[".xla"] = "application/vnd.ms-excel" + DefaultMimeMap[".xlc"] = "application/vnd.ms-excel" + DefaultMimeMap[".xlt"] = "application/vnd.ms-excel" + DefaultMimeMap[".xlw"] = "application/vnd.ms-excel" + DefaultMimeMap[".eot"] = "application/vnd.ms-fontobject" + DefaultMimeMap[".chm"] = "application/vnd.ms-htmlhelp" + DefaultMimeMap[".ims"] = "application/vnd.ms-ims" + DefaultMimeMap[".lrm"] = "application/vnd.ms-lrm" + DefaultMimeMap[".ppt"] = "application/vnd.ms-powerpoint" + DefaultMimeMap[".pps"] = "application/vnd.ms-powerpoint" + DefaultMimeMap[".pot"] = "application/vnd.ms-powerpoint" + DefaultMimeMap[".mpp"] = "application/vnd.ms-project" + DefaultMimeMap[".mpt"] = "application/vnd.ms-project" + DefaultMimeMap[".wps"] = "application/vnd.ms-works" + DefaultMimeMap[".wks"] = "application/vnd.ms-works" + DefaultMimeMap[".wcm"] = "application/vnd.ms-works" + DefaultMimeMap[".wdb"] = "application/vnd.ms-works" + DefaultMimeMap[".wpl"] = "application/vnd.ms-wpl" + DefaultMimeMap[".xps"] = "application/vnd.ms-xpsdocument" + DefaultMimeMap[".mseq"] = "application/vnd.mseq" + DefaultMimeMap[".mus"] = "application/vnd.musician" + DefaultMimeMap[".msty"] = "application/vnd.muvee.style" + DefaultMimeMap[".nlu"] = "application/vnd.neurolanguage.nlu" + DefaultMimeMap[".nnd"] = "application/vnd.noblenet-directory" + DefaultMimeMap[".nns"] = "application/vnd.noblenet-sealer" + DefaultMimeMap[".nnw"] = "application/vnd.noblenet-web" + DefaultMimeMap[".ngdat"] = "application/vnd.nokia.n-gage.data" + DefaultMimeMap[".n-gage"] = "application/vnd.nokia.n-gage.symbian.install" + DefaultMimeMap[".rpst"] = "application/vnd.nokia.radio-preset" + DefaultMimeMap[".rpss"] = "application/vnd.nokia.radio-presets" + DefaultMimeMap[".edm"] = "application/vnd.novadigm.edm" + DefaultMimeMap[".edx"] = "application/vnd.novadigm.edx" + DefaultMimeMap[".ext"] = "application/vnd.novadigm.ext" + DefaultMimeMap[".odc"] = "application/vnd.oasis.opendocument.chart" + DefaultMimeMap[".otc"] = "application/vnd.oasis.opendocument.chart-template" + DefaultMimeMap[".odf"] = "application/vnd.oasis.opendocument.formula" + DefaultMimeMap[".otf"] = "application/vnd.oasis.opendocument.formula-template" + DefaultMimeMap[".odg"] = "application/vnd.oasis.opendocument.graphics" + DefaultMimeMap[".otg"] = "application/vnd.oasis.opendocument.graphics-template" + DefaultMimeMap[".odi"] = "application/vnd.oasis.opendocument.image" + DefaultMimeMap[".oti"] = "application/vnd.oasis.opendocument.image-template" + DefaultMimeMap[".odp"] = "application/vnd.oasis.opendocument.presentation" + DefaultMimeMap[".otp"] = "application/vnd.oasis.opendocument.presentation-template" + DefaultMimeMap[".ods"] = "application/vnd.oasis.opendocument.spreadsheet" + DefaultMimeMap[".ots"] = "application/vnd.oasis.opendocument.spreadsheet-template" + DefaultMimeMap[".odt"] = "application/vnd.oasis.opendocument.text" + DefaultMimeMap[".otm"] = "application/vnd.oasis.opendocument.text-master" + DefaultMimeMap[".ott"] = "application/vnd.oasis.opendocument.text-template" + DefaultMimeMap[".oth"] = "application/vnd.oasis.opendocument.text-web" + DefaultMimeMap[".xo"] = "application/vnd.olpc-sugar" + DefaultMimeMap[".dd2"] = "application/vnd.oma.dd2+xml" + DefaultMimeMap[".oxt"] = "application/vnd.openofficeorg.extension" + DefaultMimeMap[".dp"] = "application/vnd.osgi.dp" + DefaultMimeMap[".prc"] = "application/vnd.palm" + DefaultMimeMap[".pdb"] = "application/vnd.palm" + DefaultMimeMap[".pqa"] = "application/vnd.palm" + DefaultMimeMap[".oprc"] = "application/vnd.palm" + DefaultMimeMap[".str"] = "application/vnd.pg.format" + DefaultMimeMap[".ei6"] = "application/vnd.pg.osasli" + DefaultMimeMap[".efif"] = "application/vnd.picsel" + DefaultMimeMap[".plf"] = "application/vnd.pocketlearn" + DefaultMimeMap[".pbd"] = "application/vnd.powerbuilder6" + DefaultMimeMap[".box"] = "application/vnd.previewsystems.box" + DefaultMimeMap[".mgz"] = "application/vnd.proteus.magazine" + DefaultMimeMap[".qps"] = "application/vnd.publishare-delta-tree" + DefaultMimeMap[".ptid"] = "application/vnd.pvi.ptid1" + DefaultMimeMap[".qxd"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".qxt"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".qwd"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".qwt"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".qxl"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".qxb"] = "application/vnd.quark.quarkxpress" + DefaultMimeMap[".mxl"] = "application/vnd.recordare.musicxml" + DefaultMimeMap[".rm"] = "application/vnd.rn-realmedia" + DefaultMimeMap[".see"] = "application/vnd.seemail" + DefaultMimeMap[".sema"] = "application/vnd.sema" + DefaultMimeMap[".semd"] = "application/vnd.semd" + DefaultMimeMap[".semf"] = "application/vnd.semf" + DefaultMimeMap[".ifm"] = "application/vnd.shana.informed.formdata" + DefaultMimeMap[".itp"] = "application/vnd.shana.informed.formtemplate" + DefaultMimeMap[".iif"] = "application/vnd.shana.informed.interchange" + DefaultMimeMap[".ipk"] = "application/vnd.shana.informed.package" + DefaultMimeMap[".twd"] = "application/vnd.simtech-mindmapper" + DefaultMimeMap[".twds"] = "application/vnd.simtech-mindmapper" + DefaultMimeMap[".mmf"] = "application/vnd.smaf" + DefaultMimeMap[".sdkm"] = "application/vnd.solent.sdkm+xml" + DefaultMimeMap[".sdkd"] = "application/vnd.solent.sdkm+xml" + DefaultMimeMap[".dxp"] = "application/vnd.spotfire.dxp" + DefaultMimeMap[".sfs"] = "application/vnd.spotfire.sfs" + DefaultMimeMap[".sus"] = "application/vnd.sus-calendar" + DefaultMimeMap[".susp"] = "application/vnd.sus-calendar" + DefaultMimeMap[".svd"] = "application/vnd.svd" + DefaultMimeMap[".xsm"] = "application/vnd.syncml+xml" + DefaultMimeMap[".bdm"] = "application/vnd.syncml.dm+wbxml" + DefaultMimeMap[".xdm"] = "application/vnd.syncml.dm+xml" + DefaultMimeMap[".tao"] = "application/vnd.tao.intent-module-archive" + DefaultMimeMap[".tmo"] = "application/vnd.tmobile-livetv" + DefaultMimeMap[".tpt"] = "application/vnd.trid.tpt" + DefaultMimeMap[".mxs"] = "application/vnd.triscape.mxs" + DefaultMimeMap[".tra"] = "application/vnd.trueapp" + DefaultMimeMap[".ufd"] = "application/vnd.ufdl" + DefaultMimeMap[".ufdl"] = "application/vnd.ufdl" + DefaultMimeMap[".utz"] = "application/vnd.uiq.theme" + DefaultMimeMap[".umj"] = "application/vnd.umajin" + DefaultMimeMap[".unityweb"] = "application/vnd.unity" + DefaultMimeMap[".uoml"] = "application/vnd.uoml+xml" + DefaultMimeMap[".vcx"] = "application/vnd.vcx" + DefaultMimeMap[".vsd"] = "application/vnd.visio" + DefaultMimeMap[".vst"] = "application/vnd.visio" + DefaultMimeMap[".vss"] = "application/vnd.visio" + DefaultMimeMap[".vsw"] = "application/vnd.visio" + DefaultMimeMap[".vis"] = "application/vnd.visionary" + DefaultMimeMap[".vsf"] = "application/vnd.vsf" + DefaultMimeMap[".wbxml"] = "application/vnd.wap.wbxml" + DefaultMimeMap[".wmlc"] = "application/vnd.wap.wmlc" + DefaultMimeMap[".wmlsc"] = "application/vnd.wap.wmlscriptc" + DefaultMimeMap[".wtb"] = "application/vnd.webturbo" + DefaultMimeMap[".wpd"] = "application/vnd.wordperfect" + DefaultMimeMap[".wqd"] = "application/vnd.wqd" + DefaultMimeMap[".stf"] = "application/vnd.wt.stf" + DefaultMimeMap[".xar"] = "application/vnd.xara" + DefaultMimeMap[".xfdl"] = "application/vnd.xfdl" + DefaultMimeMap[".hvd"] = "application/vnd.yamaha.hv-dic" + DefaultMimeMap[".hvs"] = "application/vnd.yamaha.hv-script" + DefaultMimeMap[".hvp"] = "application/vnd.yamaha.hv-voice" + DefaultMimeMap[".saf"] = "application/vnd.yamaha.smaf-audio" + DefaultMimeMap[".spf"] = "application/vnd.yamaha.smaf-phrase" + DefaultMimeMap[".cmp"] = "application/vnd.yellowriver-custom-menu" + DefaultMimeMap[".zaz"] = "application/vnd.zzazz.deck+xml" + DefaultMimeMap[".vxml"] = "application/voicexml+xml" + DefaultMimeMap[".hlp"] = "application/winhlp" + DefaultMimeMap[".wsdl"] = "application/wsdl+xml" + DefaultMimeMap[".wspolicy"] = "application/wspolicy+xml" + DefaultMimeMap[".ace"] = "application/x-ace-compressed" + DefaultMimeMap[".bcpio"] = "application/x-bcpio" + DefaultMimeMap[".torrent"] = "application/x-bittorrent" + DefaultMimeMap[".bz"] = "application/x-bzip" + DefaultMimeMap[".bz2"] = "application/x-bzip2" + DefaultMimeMap[".boz"] = "application/x-bzip2" + DefaultMimeMap[".vcd"] = "application/x-cdlink" + DefaultMimeMap[".chat"] = "application/x-chat" + DefaultMimeMap[".pgn"] = "application/x-chess-pgn" + DefaultMimeMap[".cpio"] = "application/x-cpio" + DefaultMimeMap[".csh"] = "application/x-csh" + DefaultMimeMap[".dcr"] = "application/x-director" + DefaultMimeMap[".dir"] = "application/x-director" + DefaultMimeMap[".dxr"] = "application/x-director" + DefaultMimeMap[".fgd"] = "application/x-director" + DefaultMimeMap[".dvi"] = "application/x-dvi" + DefaultMimeMap[".spl"] = "application/x-futuresplash" + DefaultMimeMap[".gtar"] = "application/x-gtar" + DefaultMimeMap[".hdf"] = "application/x-hdf" + DefaultMimeMap[".latex"] = "application/x-latex" + DefaultMimeMap[".wmd"] = "application/x-ms-wmd" + DefaultMimeMap[".wmz"] = "application/x-ms-wmz" + DefaultMimeMap[".mdb"] = "application/x-msaccess" + DefaultMimeMap[".obd"] = "application/x-msbinder" + DefaultMimeMap[".crd"] = "application/x-mscardfile" + DefaultMimeMap[".clp"] = "application/x-msclip" + DefaultMimeMap[".exe"] = "application/x-msdownload" + DefaultMimeMap[".dll"] = "application/x-msdownload" + DefaultMimeMap[".com"] = "application/x-msdownload" + DefaultMimeMap[".bat"] = "application/x-msdownload" + DefaultMimeMap[".msi"] = "application/x-msdownload" + DefaultMimeMap[".mvb"] = "application/x-msmediaview" + DefaultMimeMap[".m13"] = "application/x-msmediaview" + DefaultMimeMap[".m14"] = "application/x-msmediaview" + DefaultMimeMap[".wmf"] = "application/x-msmetafile" + DefaultMimeMap[".mny"] = "application/x-msmoney" + DefaultMimeMap[".pub"] = "application/x-mspublisher" + DefaultMimeMap[".scd"] = "application/x-msschedule" + DefaultMimeMap[".trm"] = "application/x-msterminal" + DefaultMimeMap[".wri"] = "application/x-mswrite" + DefaultMimeMap[".nc"] = "application/x-netcdf" + DefaultMimeMap[".cdf"] = "application/x-netcdf" + DefaultMimeMap[".p12"] = "application/x-pkcs12" + DefaultMimeMap[".pfx"] = "application/x-pkcs12" + DefaultMimeMap[".p7b"] = "application/x-pkcs7-certificates" + DefaultMimeMap[".spc"] = "application/x-pkcs7-certificates" + DefaultMimeMap[".p7r"] = "application/x-pkcs7-certreqresp" + DefaultMimeMap[".rar"] = "application/x-rar-compressed" + DefaultMimeMap[".sh"] = "application/x-sh" + DefaultMimeMap[".shar"] = "application/x-shar" + DefaultMimeMap[".swf"] = "application/x-shockwave-flash" + DefaultMimeMap[".sit"] = "application/x-stuffit" + DefaultMimeMap[".sitx"] = "application/x-stuffitx" + DefaultMimeMap[".sv4cpio"] = "application/x-sv4cpio" + DefaultMimeMap[".sv4crc"] = "application/x-sv4crc" + DefaultMimeMap[".tar"] = "application/x-tar" + DefaultMimeMap[".tcl"] = "application/x-tcl" + DefaultMimeMap[".tex"] = "application/x-tex" + DefaultMimeMap[".texinfo"] = "application/x-texinfo" + DefaultMimeMap[".texi"] = "application/x-texinfo" + DefaultMimeMap[".ustar"] = "application/x-ustar" + DefaultMimeMap[".src"] = "application/x-wais-source" + DefaultMimeMap[".der"] = "application/x-x509-ca-cert" + DefaultMimeMap[".crt"] = "application/x-x509-ca-cert" + DefaultMimeMap[".xenc"] = "application/xenc+xml" + DefaultMimeMap[".xhtml"] = "application/xhtml+xml" + DefaultMimeMap[".xht"] = "application/xhtml+xml" + DefaultMimeMap[".xml"] = "text/xml" + DefaultMimeMap[".xsl"] = "application/xml" + DefaultMimeMap[".dtd"] = "application/xml-dtd" + DefaultMimeMap[".xop"] = "application/xop+xml" + DefaultMimeMap[".xslt"] = "application/xslt+xml" + DefaultMimeMap[".xspf"] = "application/xspf+xml" + DefaultMimeMap[".mxml"] = "application/xv+xml" + DefaultMimeMap[".xhvml"] = "application/xv+xml" + DefaultMimeMap[".xvml"] = "application/xv+xml" + DefaultMimeMap[".xvm"] = "application/xv+xml" + DefaultMimeMap[".zip"] = "application/zip" + DefaultMimeMap[".au"] = "audio/basic" + DefaultMimeMap[".snd"] = "audio/basic" + DefaultMimeMap[".mid"] = "audio/midi" + DefaultMimeMap[".midi"] = "audio/midi" + DefaultMimeMap[".kar"] = "audio/midi" + DefaultMimeMap[".rmi"] = "audio/midi" + DefaultMimeMap[".mp4a"] = "audio/mp4" + DefaultMimeMap[".mpga"] = "audio/mpeg" + DefaultMimeMap[".mp2"] = "audio/mpeg" + DefaultMimeMap[".mp2a"] = "audio/mpeg" + DefaultMimeMap[".mp3"] = "audio/mpeg" + DefaultMimeMap[".m2a"] = "audio/mpeg" + DefaultMimeMap[".m3a"] = "audio/mpeg" + DefaultMimeMap[".eol"] = "audio/vnd.digital-winds" + DefaultMimeMap[".lvp"] = "audio/vnd.lucent.voice" + DefaultMimeMap[".ecelp4800"] = "audio/vnd.nuera.ecelp4800" + DefaultMimeMap[".ecelp7470"] = "audio/vnd.nuera.ecelp7470" + DefaultMimeMap[".ecelp9600"] = "audio/vnd.nuera.ecelp9600" + DefaultMimeMap[".wav"] = "audio/wav" + DefaultMimeMap[".aif"] = "audio/x-aiff" + DefaultMimeMap[".aiff"] = "audio/x-aiff" + DefaultMimeMap[".aifc"] = "audio/x-aiff" + DefaultMimeMap[".m3u"] = "audio/x-mpegurl" + DefaultMimeMap[".wax"] = "audio/x-ms-wax" + DefaultMimeMap[".wma"] = "audio/x-ms-wma" + DefaultMimeMap[".ram"] = "audio/x-pn-realaudio" + DefaultMimeMap[".ra"] = "audio/x-pn-realaudio" + DefaultMimeMap[".rmp"] = "audio/x-pn-realaudio-plugin" + DefaultMimeMap[".cdx"] = "chemical/x-cdx" + DefaultMimeMap[".cif"] = "chemical/x-cif" + DefaultMimeMap[".cmdf"] = "chemical/x-cmdf" + DefaultMimeMap[".cml"] = "chemical/x-cml" + DefaultMimeMap[".csml"] = "chemical/x-csml" + DefaultMimeMap[".xyz"] = "chemical/x-xyz" + DefaultMimeMap[".bmp"] = "image/bmp" + DefaultMimeMap[".cgm"] = "image/cgm" + DefaultMimeMap[".g3"] = "image/g3fax" + DefaultMimeMap[".gif"] = "image/gif" + DefaultMimeMap[".ief"] = "image/ief" + DefaultMimeMap[".jpeg"] = "image/jpeg" + DefaultMimeMap[".jpg"] = "image/jpeg" + DefaultMimeMap[".jpe"] = "image/jpeg" + DefaultMimeMap[".png"] = "image/png" + DefaultMimeMap[".webp"] = "image/webp" + DefaultMimeMap[".heic"] = "image/heic" + DefaultMimeMap[".btif"] = "image/prs.btif" + DefaultMimeMap[".svg"] = "image/svg+xml" + DefaultMimeMap[".svgz"] = "image/svg+xml" + DefaultMimeMap[".tiff"] = "image/tiff" + DefaultMimeMap[".tif"] = "image/tiff" + DefaultMimeMap[".psd"] = "image/vnd.adobe.photoshop" + DefaultMimeMap[".djvu"] = "image/vnd.djvu" + DefaultMimeMap[".djv"] = "image/vnd.djvu" + DefaultMimeMap[".dwg"] = "image/vnd.dwg" + DefaultMimeMap[".dxf"] = "image/vnd.dxf" + DefaultMimeMap[".fbs"] = "image/vnd.fastbidsheet" + DefaultMimeMap[".fpx"] = "image/vnd.fpx" + DefaultMimeMap[".fst"] = "image/vnd.fst" + DefaultMimeMap[".mmr"] = "image/vnd.fujixerox.edmics-mmr" + DefaultMimeMap[".rlc"] = "image/vnd.fujixerox.edmics-rlc" + DefaultMimeMap[".mdi"] = "image/vnd.ms-modi" + DefaultMimeMap[".npx"] = "image/vnd.net-fpx" + DefaultMimeMap[".wbmp"] = "image/vnd.wap.wbmp" + DefaultMimeMap[".xif"] = "image/vnd.xiff" + DefaultMimeMap[".ras"] = "image/x-cmu-raster" + DefaultMimeMap[".cmx"] = "image/x-cmx" + DefaultMimeMap[".ico"] = "image/x-icon" + DefaultMimeMap[".pcx"] = "image/x-pcx" + DefaultMimeMap[".pic"] = "image/x-pict" + DefaultMimeMap[".pct"] = "image/x-pict" + DefaultMimeMap[".pnm"] = "image/x-portable-anymap" + DefaultMimeMap[".pbm"] = "image/x-portable-bitmap" + DefaultMimeMap[".pgm"] = "image/x-portable-graymap" + DefaultMimeMap[".ppm"] = "image/x-portable-pixmap" + DefaultMimeMap[".rgb"] = "image/x-rgb" + DefaultMimeMap[".xbm"] = "image/x-xbitmap" + DefaultMimeMap[".xpm"] = "image/x-xpixmap" + DefaultMimeMap[".xwd"] = "image/x-xwindowdump" + DefaultMimeMap[".eml"] = "message/rfc822" + DefaultMimeMap[".mime"] = "message/rfc822" + DefaultMimeMap[".mht"] = "message/rfc822" + DefaultMimeMap[".mhtml"] = "message/rfc822" + DefaultMimeMap[".igs"] = "model/iges" + DefaultMimeMap[".iges"] = "model/iges" + DefaultMimeMap[".msh"] = "model/mesh" + DefaultMimeMap[".mesh"] = "model/mesh" + DefaultMimeMap[".silo"] = "model/mesh" + DefaultMimeMap[".dwf"] = "model/vnd.dwf" + DefaultMimeMap[".gdl"] = "model/vnd.gdl" + DefaultMimeMap[".gtw"] = "model/vnd.gtw" + DefaultMimeMap[".mts"] = "model/vnd.mts" + DefaultMimeMap[".vtu"] = "model/vnd.vtu" + DefaultMimeMap[".wrl"] = "model/vrml" + DefaultMimeMap[".vrml"] = "model/vrml" + DefaultMimeMap[".ics"] = "text/calendar" + DefaultMimeMap[".ifb"] = "text/calendar" + DefaultMimeMap[".css"] = "text/css" + DefaultMimeMap[".csv"] = "text/csv" + DefaultMimeMap[".html"] = "text/html" + DefaultMimeMap[".htm"] = "text/html" + DefaultMimeMap[".txt"] = "text/plain" + DefaultMimeMap[".text"] = "text/plain" + DefaultMimeMap[".conf"] = "text/plain" + DefaultMimeMap[".def"] = "text/plain" + DefaultMimeMap[".list"] = "text/plain" + DefaultMimeMap[".log"] = "text/plain" + DefaultMimeMap[".in"] = "text/plain" + DefaultMimeMap[".dsc"] = "text/prs.lines.tag" + DefaultMimeMap[".rtx"] = "text/richtext" + DefaultMimeMap[".sgml"] = "text/sgml" + DefaultMimeMap[".sgm"] = "text/sgml" + DefaultMimeMap[".tsv"] = "text/tab-separated-values" + DefaultMimeMap[".t"] = "text/troff" + DefaultMimeMap[".tr"] = "text/troff" + DefaultMimeMap[".roff"] = "text/troff" + DefaultMimeMap[".man"] = "text/troff" + DefaultMimeMap[".me"] = "text/troff" + DefaultMimeMap[".ms"] = "text/troff" + DefaultMimeMap[".uri"] = "text/uri-list" + DefaultMimeMap[".uris"] = "text/uri-list" + DefaultMimeMap[".urls"] = "text/uri-list" + DefaultMimeMap[".fly"] = "text/vnd.fly" + DefaultMimeMap[".flx"] = "text/vnd.fmi.flexstor" + DefaultMimeMap[".3dml"] = "text/vnd.in3d.3dml" + DefaultMimeMap[".spot"] = "text/vnd.in3d.spot" + DefaultMimeMap[".jad"] = "text/vnd.sun.j2me.app-descriptor" + DefaultMimeMap[".wml"] = "text/vnd.wap.wml" + DefaultMimeMap[".wmls"] = "text/vnd.wap.wmlscript" + DefaultMimeMap[".s"] = "text/x-asm" + DefaultMimeMap[".asm"] = "text/x-asm" + DefaultMimeMap[".c"] = "text/x-c" + DefaultMimeMap[".cc"] = "text/x-c" + DefaultMimeMap[".cxx"] = "text/x-c" + DefaultMimeMap[".cpp"] = "text/x-c" + DefaultMimeMap[".h"] = "text/x-c" + DefaultMimeMap[".hh"] = "text/x-c" + DefaultMimeMap[".dic"] = "text/x-c" + DefaultMimeMap[".f"] = "text/x-fortran" + DefaultMimeMap[".for"] = "text/x-fortran" + DefaultMimeMap[".f77"] = "text/x-fortran" + DefaultMimeMap[".f90"] = "text/x-fortran" + DefaultMimeMap[".p"] = "text/x-pascal" + DefaultMimeMap[".pas"] = "text/x-pascal" + DefaultMimeMap[".java"] = "text/x-java-source" + DefaultMimeMap[".etx"] = "text/x-setext" + DefaultMimeMap[".uu"] = "text/x-uuencode" + DefaultMimeMap[".vcs"] = "text/x-vcalendar" + DefaultMimeMap[".vcf"] = "text/x-vcard" + DefaultMimeMap[".3gp"] = "video/3gpp" + DefaultMimeMap[".3g2"] = "video/3gpp2" + DefaultMimeMap[".h261"] = "video/h261" + DefaultMimeMap[".h263"] = "video/h263" + DefaultMimeMap[".h264"] = "video/h264" + DefaultMimeMap[".jpgv"] = "video/jpeg" + DefaultMimeMap[".jpm"] = "video/jpm" + DefaultMimeMap[".jpgm"] = "video/jpm" + DefaultMimeMap[".mj2"] = "video/mj2" + DefaultMimeMap[".mjp2"] = "video/mj2" + DefaultMimeMap[".mp4"] = "video/mp4" + DefaultMimeMap[".mp4v"] = "video/mp4" + DefaultMimeMap[".mpg4"] = "video/mp4" + DefaultMimeMap[".mpeg"] = "video/mpeg" + DefaultMimeMap[".mpg"] = "video/mpeg" + DefaultMimeMap[".mpe"] = "video/mpeg" + DefaultMimeMap[".m1v"] = "video/mpeg" + DefaultMimeMap[".m2v"] = "video/mpeg" + DefaultMimeMap[".qt"] = "video/quicktime" + DefaultMimeMap[".mov"] = "video/quicktime" + DefaultMimeMap[".fvt"] = "video/vnd.fvt" + DefaultMimeMap[".mxu"] = "video/vnd.mpegurl" + DefaultMimeMap[".m4u"] = "video/vnd.mpegurl" + DefaultMimeMap[".viv"] = "video/vnd.vivo" + DefaultMimeMap[".fli"] = "video/x-fli" + DefaultMimeMap[".wm"] = "video/x-ms-wm" + DefaultMimeMap[".wmv"] = "video/x-ms-wmv" + DefaultMimeMap[".wmx"] = "video/x-ms-wmx" + DefaultMimeMap[".wvx"] = "video/x-ms-wvx" + DefaultMimeMap[".avi"] = "video/x-msvideo" + DefaultMimeMap[".movie"] = "video/x-sgi-movie" + DefaultMimeMap[".ice"] = "x-conference/x-cooltalk" + DefaultMimeMap[".ipa"] = "application/vnd.iphone" + DefaultMimeMap[".apk"] = "application/vnd.android.package-archive" +} + +func GetMimeMap() map[string]string { + once.Do(LoadMimeMap) + return DefaultMimeMap +} diff --git a/bce-sdk-go/util/string.go b/bce-sdk-go/util/string.go new file mode 100644 index 0000000..3bb0b10 --- /dev/null +++ b/bce-sdk-go/util/string.go @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// string.go - define the string util function + +// Package util defines the common utilities including string and time. +package util + +import ( + "bytes" + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" + "io" +) + +func HmacSha256Hex(key, str_to_sign string) string { + hasher := hmac.New(sha256.New, []byte(key)) + hasher.Write([]byte(str_to_sign)) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func CalculateContentMD5(data io.Reader, size int64) (string, error) { + hasher := md5.New() + n, err := io.CopyN(hasher, data, size) + if err != nil { + return "", fmt.Errorf("calculate content-md5 occurs error: %v", err) + } + if n != size { + return "", fmt.Errorf("calculate content-md5 writing size %d != size %d", n, size) + } + return base64.StdEncoding.EncodeToString(hasher.Sum(nil)), nil +} + +func UriEncode(uri string, encodeSlash bool) string { + var byte_buf bytes.Buffer + for _, b := range []byte(uri) { + if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') || + b == '-' || b == '_' || b == '.' || b == '~' || (b == '/' && !encodeSlash) { + byte_buf.WriteByte(b) + } else { + byte_buf.WriteString(fmt.Sprintf("%%%02X", b)) + } + } + return byte_buf.String() +} + +func NewUUID() string { + var buf [16]byte + for { + if _, err := rand.Read(buf[:]); err == nil { + break + } + } + buf[6] = (buf[6] & 0x0f) | (4 << 4) + buf[8] = (buf[8] & 0xbf) | 0x80 + + res := make([]byte, 36) + hex.Encode(res[0:8], buf[0:4]) + res[8] = '-' + hex.Encode(res[9:13], buf[4:6]) + res[13] = '-' + hex.Encode(res[14:18], buf[6:8]) + res[18] = '-' + hex.Encode(res[19:23], buf[8:10]) + res[23] = '-' + hex.Encode(res[24:], buf[10:]) + return string(res) +} + +func NewRequestId() string { + return NewUUID() +} diff --git a/bce-sdk-go/util/time.go b/bce-sdk-go/util/time.go new file mode 100644 index 0000000..e9f9e3d --- /dev/null +++ b/bce-sdk-go/util/time.go @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Baidu, Inc. + * + * 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. + */ + +// time.go - define the time utility functions for BCE + +package util + +import "time" + +const ( + RFC822Format = "Mon, 02 Jan 2006 15:04:05 MST" + ISO8601Format = "2006-01-02T15:04:05Z" +) + +func NowUTCSeconds() int64 { return time.Now().UTC().Unix() } + +func NowUTCNanoSeconds() int64 { return time.Now().UTC().UnixNano() } + +func FormatRFC822Date(timestamp_second int64) string { + tm := time.Unix(timestamp_second, 0).UTC() + return tm.Format(RFC822Format) +} + +func ParseRFC822Date(time_string string) (time.Time, error) { + return time.Parse(RFC822Format, time_string) +} + +func FormatISO8601Date(timestamp_second int64) string { + tm := time.Unix(timestamp_second, 0).UTC() + return tm.Format(ISO8601Format) +} + +func ParseISO8601Date(time_string string) (time.Time, error) { + return time.Parse(ISO8601Format, time_string) +} diff --git a/cce-network-v2/Makefile b/cce-network-v2/Makefile index 8cda982..09233c1 100644 --- a/cce-network-v2/Makefile +++ b/cce-network-v2/Makefile @@ -193,8 +193,6 @@ tags: $(GOLANG_SRCFILES) $(BPF_SRCFILES) cscope.files ## Generate tags for Go an clean: ## Perform overall cleanup for CCE. -$(QUIET) for i in $(SUBDIRS); do $(MAKE) $(SUBMAKEOPTS) -C $$i clean; done - -$(QUIET) $(MAKE) $(SUBMAKEOPTS) -C ./contrib/packaging/deb clean - -$(QUIET) $(MAKE) $(SUBMAKEOPTS) -C ./contrib/packaging/rpm clean veryclean: ## Perform complete cleanup for container engine images(including build cache). -$(QUIET) $(CONTAINER_ENGINE) image prune -af @@ -212,7 +210,7 @@ GIT_VERSION: force @if [ "$(GIT_VERSION)" != "`cat 2>/dev/null GIT_VERSION`" ] ; then echo "$(GIT_VERSION)" >GIT_VERSION; fi # DOCKER_IMAGES := cce-network-agent cce-network-operator-vpc-eni cce-network-operator -DOCKER_IMAGES := cce-network-operator-vpc-eni cce-network-agent +DOCKER_IMAGES := cce-network-agent cce-network-operator-vpc-eni docker: GOARCH = amd64 docker: $(DOCKER_IMAGES) docker-arm: GOARCH = arm64 @@ -220,13 +218,13 @@ docker-arm: image-suffix = -arm64 docker-arm: $(DOCKER_IMAGES) # use docker build base on profile variable -ifeq ($(PROFILE), dev) -# build docker image with native arch when profile is dev +# build docker image for amd64 and arm64 when GOARCH is amd64 and arm64 +ifeq ($(GOARCH), amd64) +# for amd64 $(DOCKER_IMAGES): build docker build -t registry.baidubce.com/cce-plugin-$(PROFILE)/$(@):$(VERSION) -f deploy/dockerfile/$(@).Dockerfile . docker push registry.baidubce.com/cce-plugin-$(PROFILE)/$(@):$(VERSION) else -# build docker image for amd64 and arm64 when prifile is pro # for amd64 $(DOCKER_IMAGES): build echo "GOARCH=$(GOARCH)" diff --git a/cce-network-v2/VERSION b/cce-network-v2/VERSION index e3bed5e..10c2c0c 100644 --- a/cce-network-v2/VERSION +++ b/cce-network-v2/VERSION @@ -1 +1 @@ -2.9.5 \ No newline at end of file +2.10.0 diff --git a/cce-network-v2/cmd/Makefile b/cce-network-v2/cmd/Makefile index 631a1b9..fc25495 100644 --- a/cce-network-v2/cmd/Makefile +++ b/cce-network-v2/cmd/Makefile @@ -19,7 +19,7 @@ $(TARGET): clean: @$(ECHO_CLEAN) - $(QUIET)rm -f $(TARGETS) + $(forea target, $(QUIET)rm -f $(PWD)/output/bin/cmd/$(target)) $(GO) clean $(GOCLEAN) install: diff --git a/cce-network-v2/cmd/agent/cmd/daemon.go b/cce-network-v2/cmd/agent/cmd/daemon.go index 5ac5cf9..66c0b88 100644 --- a/cce-network-v2/cmd/agent/cmd/daemon.go +++ b/cce-network-v2/cmd/agent/cmd/daemon.go @@ -165,19 +165,6 @@ func NewDaemon(ctx context.Context, cancel context.CancelFunc) (*Daemon, error) return nil, fmt.Errorf("invalid daemon configuration: %s", err) } - if option.Config.ReadCNIConfiguration != "" { - netConf, err = cnitypes.ReadNetConf(option.Config.ReadCNIConfiguration) - if err != nil { - log.WithError(err).Error("Unable to read CNI configuration") - return nil, fmt.Errorf("unable to read CNI configuration: %w", err) - } - - if netConf.MTU != 0 { - configuredMTU = netConf.MTU - log.WithField("mtu", configuredMTU).Info("Overwriting MTU based on CNI configuration") - } - } - set, err := rate.NewAPILimiterSet(option.Config.APIRateLimit, apiRateLimitDefaults, rate.SimpleMetricsObserver) if err != nil { log.WithError(err).Error("unable to configure API rate limiting") diff --git a/cce-network-v2/cmd/agent/cmd/daemon_main.go b/cce-network-v2/cmd/agent/cmd/daemon_main.go index 2ea3e55..1b98e64 100644 --- a/cce-network-v2/cmd/agent/cmd/daemon_main.go +++ b/cce-network-v2/cmd/agent/cmd/daemon_main.go @@ -27,6 +27,7 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/api/v1/server" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/api/v1/server/restapi" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/plugins/pluginmanager" "github.com/go-openapi/loads" gops "github.com/google/gops/agent" "github.com/sirupsen/logrus" @@ -243,6 +244,18 @@ func initializeFlags() { flags.String(option.IPAM, ipamOption.IPAMClusterPool, "Backend to use for IPAM") option.BindEnv(option.IPAM) + flags.Int(option.IPPoolMinAllocateIPs, 2, + "MinAllocate is the minimum number of IPs that must be allocated when the node is first bootstrapped.") + option.BindEnv(option.IPPoolMinAllocateIPs) + + flags.Int(option.IPPoolPreAllocate, 2, + "PreAllocate defines the number of IP addresses that must be available for allocation in the IPAMspec. ") + option.BindEnv(option.IPPoolPreAllocate) + + flags.Int(option.IPPoolMaxAboveWatermark, 2, + "MaxAboveWatermark is the maximum number of addresses to allocate beyond the addresses needed to reach the PreAllocate watermark.") + option.BindEnv(option.IPPoolMaxAboveWatermark) + flags.String(option.IPv4Range, AutoCIDR, "Per-node IPv4 endpoint prefix, e.g. 10.16.0.0/16") option.BindEnv(option.IPv4Range) @@ -348,9 +361,6 @@ func initializeFlags() { flags.String(option.IPv4NodeAddr, "auto", "IPv4 address of node") option.BindEnv(option.IPv4NodeAddr) - flags.String(option.ReadCNIConfiguration, "", "Read to the CNI configuration at specified path to extract per node configuration") - option.BindEnv(option.ReadCNIConfiguration) - flags.Bool(option.Restore, true, "Restores state, if possible, from previous daemon") option.BindEnv(option.Restore) @@ -412,12 +422,9 @@ func initializeFlags() { flags.MarkHidden(option.EndpointGCInterval) option.BindEnv(option.EndpointGCInterval) - flags.String(option.WriteCNIConfigurationWhenReady, "", fmt.Sprintf("Write the CNI configuration as specified via --%s to path when agent is ready", option.ReadCNIConfiguration)) + flags.String(option.WriteCNIConfigurationWhenReady, "", "Write the CNI configuration as specified path when agent is ready") option.BindEnv(option.WriteCNIConfigurationWhenReady) - flags.Bool(option.OverwriteCNIConfigurationWhenStart, true, "Overwrite the CNI configuration when agent starts") - option.BindEnv(option.OverwriteCNIConfigurationWhenStart) - flags.Duration(option.K8sHeartbeatTimeout, 30*time.Second, "Configures the timeout for api-server heartbeat, set to 0 to disable") option.BindEnv(option.K8sHeartbeatTimeout) @@ -488,6 +495,9 @@ func initializeFlags() { flags.Bool(option.EnableEgressPriorityDSCP, false, "enable engress priority by dscp") option.BindEnv(option.EnableEgressPriorityDSCP) + flags.StringSlice(option.ExtCNIPluginsList, []string{}, "external cni plugins list") + option.BindEnv(option.ExtCNIPluginsList) + viper.BindPFlags(flags) } @@ -666,34 +676,19 @@ func runDaemon() { log.WithField("bootstrapTime", time.Since(bootstrapTimestamp)). Info("Daemon initialization completed") - firstWriteCNIConfig := false - if option.Config.OverwriteCNIConfigurationWhenStart { - firstWriteCNIConfig = true - } if option.Config.WriteCNIConfigurationWhenReady != "" { d.controllers.UpdateController(cniUpdateControllerName, controller.ControllerParams{ RunInterval: option.Config.ResourceResyncInterval, DoFunc: func(ctx context.Context) error { // Overwrite the CNI configuration when agent starts, so we should skip read config file which has been existed. - if !firstWriteCNIConfig { - _, err = os.Open(option.Config.WriteCNIConfigurationWhenReady) - if err == nil { - return nil - } - } - firstWriteCNIConfig = false - input, err := os.ReadFile(option.Config.ReadCNIConfiguration) + override, err := pluginmanager.OverwriteCNIConfigList(option.Config.WriteCNIConfigurationWhenReady) if err != nil { - log.WithError(err).Fatal("Unable to read CNI configuration file") + log.WithError(err).Error("Unable to overwrite CNI configuration") } - - if err = os.WriteFile(option.Config.WriteCNIConfigurationWhenReady, input, 0644); err != nil { - log.WithError(err).Fatalf("Unable to write CNI configuration file to %s", option.Config.WriteCNIConfigurationWhenReady) - } else { - log.Infof("Wrote CNI configuration file to %s", option.Config.WriteCNIConfigurationWhenReady) + if override { + log.Infof("Overwrite CNI configuration file to %s", option.Config.WriteCNIConfigurationWhenReady) } - return nil }, }) diff --git a/cce-network-v2/cmd/agent/cmd/ipam.go b/cce-network-v2/cmd/agent/cmd/ipam.go index 2669d10..aee722b 100644 --- a/cce-network-v2/cmd/agent/cmd/ipam.go +++ b/cce-network-v2/cmd/agent/cmd/ipam.go @@ -225,7 +225,7 @@ func (d *Daemon) configureIPAM() { if option.Config.IPv4Range != AutoCIDR { allocCIDR, err := cidr.ParseCIDR(option.Config.IPv4Range) if err != nil { - log.WithError(err).WithField(logfields.V4Prefix, option.Config.IPv4Range).Fatal("Invalid IPv4 allocation prefix") + ipamLog.WithError(err).WithField(logfields.V4Prefix, option.Config.IPv4Range).Fatal("Invalid IPv4 allocation prefix") } node.SetIPv4AllocRange(allocCIDR) } @@ -233,20 +233,20 @@ func (d *Daemon) configureIPAM() { if option.Config.IPv6Range != AutoCIDR { allocCIDR, err := cidr.ParseCIDR(option.Config.IPv6Range) if err != nil { - log.WithError(err).WithField(logfields.V6Prefix, option.Config.IPv6Range).Fatal("Invalid IPv6 allocation prefix") + ipamLog.WithError(err).WithField(logfields.V6Prefix, option.Config.IPv6Range).Fatal("Invalid IPv6 allocation prefix") } node.SetIPv6NodeRange(allocCIDR) } if err := node.AutoComplete(); err != nil { - log.WithError(err).Fatal("Cannot autocomplete node addresses") + ipamLog.WithError(err).Fatal("Cannot autocomplete node addresses") } } func (d *Daemon) startIPAM() { bootstrapStats.ipam.Start() - log.Info("Initializing ipam") + ipamLog.Info("Initializing ipam") // Set up ipam conf after init() because we might be running d.conf.KVStoreIPv4Registration d.ipam = endpoint.NewIPAM(d.nodeAddressing, option.Config, d.nodeDiscovery, d.k8sWatcher, nil) bootstrapStats.ipam.End(true) diff --git a/cce-network-v2/deploy/cce-network-v2-2.9.tar.gz b/cce-network-v2/deploy/cce-network-v2-2.9.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9bf4922f248212afe92f5e6748a2c625a775bfdc GIT binary patch literal 19969 zcmV)YK&-zXiwFP!000001MEG^ZX`!?I`bwbWbz8n6Yh4wQX5t&)f%<66qIr75HgT$`NjEszo zcShE@u5dz;-A2icvpCrO;xXDDwA15b`aiVh|LO1jqvPY_)4ij^lY=kz_74sZkH28Y zkAh93iyqnbL;z`iFq9GOr#IV37hZ#(c$6Y)Bb;sX5If(Bn#ga zy(OOqpEw6@@X67Ub^lLJ4{84oPoN!~!2X|{oPzxCJ;(}IwEyS(|M8>4{0Zy5=Rq#g zo??Yw6pMssQPR5=<5?8l^hC(VfndMUY}h_ZrPfPjC(xe z{Vm2qJ{NsfXE60Rjkyc&K7M4qcOnox6}?OS80TOt;A0V_2!=7jF?5666Ks1Xf_YEA z-DW-ZPb>rkM3}K79GJ$!#X;FJ2Ka*jc2^|B5j=^bBvbGl`q5`cM~Czqy7OczvJpL1 zD^_pfB+4Q;3i|ANFjCLt!e&_(!-Ev?cY#t8G>_vRthhJkzL$^T4>y|EX`m@-Te31RO%Kzot>x-*icJ}~Gze@f=0W{?Q`1JJjN&Y`a+wZZjM8>k2K;8{} zmuDc^?3;J5*?7s4JOt*O;_Fl};N>|FJ&>ZU-x}?w_CU=l|DK40Xj!PMPw@k~<$oVU zzajqzdxs}SPxAjcTGg6^0!ViYlYUMNFbBi4!m3v++Gnxti6A|d$~kO~i|RFqau`q3 z)-Cm%XYTBE1(S`9_on9i1kpg>M9G-Kqe5E^^VZL1#meHyI}5`oBgLG`r4Y8|Lp@fG z&Z6%?R%=r~FNc->gL)9uXmAfIFxKq8>xRB^qx#JAQ*<4@8jkBztw3FGKm9G;%`?}< z?Y%5m66TdortIYAiJvV8QJ9Il%uJ+`$XE65X6m`{lKUY@ObO0WGSMOnWB!~^;mvjz zqf#Ga*C_Sj_Yx-CUELq2gr5D?Ju^OqwRmE}^B`lr!Hg$a?<|g$*aHv)3>tcsPbU5y zbl7e#X_N=SC<6Ifb_=1zATD~Bgp|Z2f1+P`gw)&}`{8aUzX6jb)3iCxabR~Q{`BRp z$Xo=lTN*a?imX^`*v<3QDKyv14#ia;=817WeOXyPOz92eJoK(UNQ8?f#*%LagNqNN zx91-&&)!^Ijm`!awO))Z5c-!1NMYjzV-sHl-a9dAzd<>!#cAyhG!}A)g1+{0JCD6;_K&EA1>dXUl27uKOs=b^uov!xbs#T5R#kk zRyUe!AiBw(H3ItzZ;oB}BJ|?O*M_mZ$XA2Ghl|Ve(c9tWwRwxT&3s>nz1_k{S{}yM zMRmLy)mQgLb^M^H3O`cwz$KUC|mt*CBQUR2T@O*CR)P5}{Jo;HvujGIh^?7w(% za?t2vN{7^;0~4_ccP4m{&3*1ZUf+e49p8aciJ@s@MwkM}&9jAwVzfF#gqTlu(S9&*~BYZFO!>OajsO`)X ziUP=mdD(@S-c16;5t7NRndcdQh*>lo(;OPK^5Z4YR;JiC#!W=F){qUII+TWlhf~3R zcO#ZN?02Mv_|mKrQ?4B-DAd=(C^BTv6*S^d3VsYOtCF*^aCAqMn-C`ULx;%ubDp?ct znE?_2R)4gaEAklh)hm{qnigj zp-1o}b)!lF0z6QQq}d$CJ&(x7gHZxOy+{V=t%L2;_LpT5X$F%{O;e#a%A$!{glPuwS>KfK!d-a@*aAOMMgfs;s91*QGn-Kn2x z<%to0jy~ru=|O*-gKLTh>rvB97i93TJ!OikS>;f4bLOmn}+M9cSSA;*$YPhy| zQZ~|G9Xo*9i~(i28Y)*&5?a-Juy+?%*G14qo6)2`(Y+j)mRo=X1A2fQ&?ZijXihMN z&SK&ZH}IRM0Km`rc_-)Gu*2l8<0goF3n>wGQ#VzVF*v>A{byq|JWOVlujKa z?0Q?!JkzaqmQ<^}sGe)eW|pt!PPW3v+BR2yv^rX_#wL*aK^3GzABTR0)^zB#P+?7V zs|Ob6uF?=Q-)l0CB_v*fntsWXgfE*y>#aam`Tvl<2J7ga++3O{B%1rv;(AExpwAQ|}) zJ@)NPgbY+;@&wd8}Xo0QxH6S$O^g{1wdnk+-7bzm4f* zvGUcICJQ#_u5~l(n)pr869*OgLWWd@MrUV}Oh}_s{*ajs{|fGBF* zg0oM_1R2;sTuJ^snEN>x79hTV({9c5t^rE|XR$1ui0Ii)5#pG@&Hd1y=kq?>vn-*SJS$te zS%CYj84y!y7`Lqv?+(vXb{3L`m)6rj_ZeRZFux275&%XhiUl@+6{hq1?WuDYFJA~h zyPHk?@%A6vw)3H*YP;+-C}R|xnl~~81L2C0Cw`Q&+Zph#DT)JZf@Z%@ zb9aXJdK7u`(RBnI5>1y^G1iFvJ&BS5q{2uBqzKYW9SB75Mg=Xx!-6X$;@kC$*R7!_wTn82^m*0WN7170Hdur&sTIwN6 zrRia$vM{QLZ3TkLY-${NX2a>H?a^x6(6P%gx1^Y;$Pa7i;KJsHs(aF~Dzq3e3mVRzcA(g8rzQZ`-NAIX#)#UvPl>|kU{wKOIb0R(WfK=wxeQzi7u z_-2Qt5vZ8bBb`SJ(UH7Y0Zai{CGd58Q`~z%@PHVVOGJr>?r+5SBJxd48be6o(`h)c zah`~jd3mC%L3Bx;g1EzO1!0Q-u_7ihL9CU0xOsxn2Vh$vC6#YukOE*Pc|sog1PfHk zGl;hK>(+hdV*)~_n^nt?7u;ubZmL08Ij)1#i;VQ*Xa%c&rF-fKx+pGtrgM$(hmN7h z>)*sYmWZ6R5$P<9@WkB;)Mcrg#+wpAIBExl0Ul08h?V^E^oG7s)4DiZB4G~0>V zxGZa;W##j>JhIaz&h!%#a=t9+X5ZsYMVwf8?{!f#KK!jK*`L~d+Gh7Zpl88dB3t(= zNca7Z*wa-F z`xboSRgHR54qn$p1>Rz4P*)4Pb*YzP+aPUSh_Tso9;!gflnkk1cc%O4uu#{p;8g?C zO9iBMiT!%%Z@g)!Yg7F~c`C4`y8TsIY(F+XZ$`b8`2hVQ6KcGA^{&}gCq<358HQP@ z2t(0QXQGR144+iCcF(dbu%K&{oqECr&q6Yvyj~&y*0lu`Oyig_#?stWE`Z{ts|%J< z&Tcv8NkhHil^Y(oE%LL%m{I`2QgQx6zud1-* zsk^m!1lw(+j1LxVWM&P!ihphrYgAk8dqx_=QQqZcwLUFXc?Xrsk5S3+seHN z$dfA~N|cG%q80^-q$VQeEqOWyj+u+Bm7CJdN7n|c*D|H}R6&xM@r55HszOI1P~eM9 zXRC2g6fKwBMRI%btK~i$`sr&>{=z;Q1W=eRy;&AQfr#3!F;&t7Igpc>=uLY}q*Eum zo$bGfU;OOmKP5Z#WypJuI z^Sd*-#R5cN4XoeP2H<$y|Ke){6RAu{zqsHCS#1DWW5s}+@(clnx8PJ8Y9Kn>QSrvRg$Sj5O6>$-=%m^IzZsH#)hv?iBS!nK1b7h z0JAwtg*?ixuD$?JUyq?`zqO)IzXDj%6`1EBZ`t=spP1t<11&$Qz`rUppB|ywOSPPazhdrC zSZ2fPy7@D7=&VIAQ8(s9DrXq$d~-v6#X`j-<>x@E3G|{ybB$XQ)el$P&V10~2zwdh z7c2u=Nb%)AoL@HKn*^Pn)8kA+_#@M(mX3rK~` zL5%6hK!dZX$Z6teXGZ4BDMY5 zA}MQ+leMP@i(f{VT9{RGxm5h3>-5OQfVUOz(Pm*aWk7c>z3Vn)dQc_gk>SUp^ zyP7ic;6RSB0L6JJ@c}Vb5=q_zB-Lph!35s}wUa)elX;QV%!xof{0=w=^XEmkbSk=s z^>{^pDIYL1?v1?AUL&Z)tLBE<5-alCC8VyT zlmc#r*4b%lFmmr&8r1?ih^D^FGJXR{LH9%-()(d9>X-E&N5138bMSi=PU(F+*$k*O zi-L9Bz)|E4hUf3jiqY44aP8!=&K-7bJV}I*a!QU%YloyR@KxagUYNxbUs!|%B;p3I zqo6oB$f68y-d4D@!WQatGI%2xM_Pa)JY>%R-3JoQrXJA%(pq%j*bjBcYtqZo$ysC@ z474%misexWvKE%eY4^ZZ;*YH`X3~0ToN6RgPL5P|0$fXQb`~PAg>cm^9p~tYwn$iR zl|P>g4^MMo@mP2a9<>Fm^z@O$ZZ^#V*#$}B@ASJA3j%R4SED&iCv{vo6f zfXEdWg(ZQQfU^?t@J4cFi0APuwh#0zR%uD$9idvK@4>-RLE^z+qVX@8LVjx_wDGHK zJV>KLWwew-&-4K_-B#)WY`U&*Z8p)RteW4EIAhq5iWJjX!U}1WjRJGORufCf_e!cSf*Dyc8r#9L1TJ-#q}u7OYO1=W5w=gj z0t*J;2rwLsS>POCy&H?|J?w(<1^=0mX8ei2aNbL1T{E+?Tbdc0sDkjcsj--m?!$M$+Wo5yw@Rz;iX0o zlQ=ec?4jx=HLug8v`Kk2(v0OG#MG`-V-3{kYgQTKw%|naXEG^UR3|d7JTtFsbt1=p z-$Th>@I1XhFWruYl|$U_xUQLnygk*1d&vo>dEQhb1r60+c6e0zOQJDM(}@gQnI8EJ$d-*BAgbs(||Z*8=XP$G+$W_ucC0U~WFP-GZG{Hc)_gl)|i(XFNFS#?v=vUzt=O8*_6*=iSv2R817w!BawLw2&nByFK zNxpGXcPs2@1LvzF5O<5IUB(|P(2+BOiRi4UHm@caosgTm&5+loofo?9ZD%zmJ}LHv zo>>;rPCI@fLD?;8hOt39@ z8K7JEu@SxnFDt&W2F3-i@u6I-TQ@&x zGpt1kxe^Q}B@Ty!&Y37v4HAx|d&;LuycRd>lH#dxa?3#>3CY)8e+WAu$;nV`w@DQQ z?71H4be16j;jmW@nT&xG7mPeI?TJY^Gr4z+=Vw5_>zFXStRhZ&zB`tyTT1?Sgl8I$ zoZOpk+rT7fxB}Mx!lC{IFHYZsYCZqSt-hb~{KPCnG3se83^D~yhSCERBZw8Y!<^R7DR zs!kq0S4tm>d#0(CVxU+gf^~_f`3yP6B3qk=4zORE7@BNEe&+n}^^GJWI4qK6Z^ZEm z{A#;AU25fB&7K}Ll4m1%Hj-x}d8St*c{Y;ggaaLqwY3#DbAqL&-6F-|6-h8?IL?V! zU5p#0n91`Jp2lO`p4wicc>NmIALCTjc;kE(#e%a60EUMmwc;8^N$O0xB^RFZ3v#6s zkZ76{lHx8!+>3A%QFQ$5=7<~qz7{R2=F{NMe=|NUmQOE^h9sM5{wXAVzPk_Mlpykll3`9!~I4XyKU)8;4F*mQUcC&%E{H)p7-VX)b`_32 zyP<$~FE=MumE$%WOPxFBso6g+^x#H8Ra@7IjZzk>LdLs%Us$7&f6C46f zIKb7G#JHg*7)Oh$^Qlnv=2NTsdxeE4y+2VEeg!lPV8V@hBf)+hEr> z*!50>U9bNHXT+{I1ftv?J_aW+bQ4p{)d9jnuu zO!H%iw^6}#%Lj>dw-cXuxH^&3oO= zOavBlO0R5YZxrS=R};>v_)}#LDC`Gs0AhExPox<`!ktMxJ(1f4Eg7Rw<0QVht^f%z z>##au(2avboe`)o0W!lSm8VWlr23KrvBTKgT+d>5c8{&5VT>;5RYb)k3TRi#hYqDR zOr*oBu04xB$mN%26y02(MkDV*YBI_jADpPu84dJdL=vLzEaLzg>((UbER5g_k0iM? zsAv_UBf`(~@)LA{$*SFI!Y+uzE1(aF_~+6@=)n;mz}aImMp*O*y{9bK6?P}JTb|2= zz~TsP9N(xMS#&UelEzq7r|cYmpp`LRT1Os&q2#DLP;xsW6_;k^B3A~5Qpt;;CVj7a zdHf+5j1yv$)g1|96kA-HlFf+UB9Ro07}#rPL-$e5^m!K+=uyxGDWQInKPSsRG2l34 zVW;Q6fHtI+DeJQEW!%?8=Hg|}*D0_1fJ0=(A6Qd;WAxlvT`76WwvA?i`ib8~*EFli zSkR?Ju$zo-($S{(InQ>?c9N`?$tOOqw3u|-L*vq@q*=-wc`_#*!dxiudq&q7U}}W6 zfZ%AcA_+G@(0jy-?*fHE<2BZ;L50dD$Y^^XfS9GuV5}VE_Rh_nd=g`Jx8Z@}GiwhZ z3RY$pO%YN{uN$2StRf!^x+&AB1p4*Oo7e1yelbIbK()d#NU-pk1Q0&dvXT>q4Few@ zWQlmX$1r~&Q5kBU7G?Dx*@5#0xpzKQMSsxAlBQUuK!Hi#3vT-az@BtK-h=ID?k0Ul z^hpYm{MSn1Ddoz+=Ll?etd9aV`&zrkZ{YIq&ax1nVYoXtL1!RH?QSi zek=%sKM5oB6vHl0S`5)tr5%eu_+Js4{g$t8S>rs3HPs z_XPg%4W;T|C8&)}7kfDH`CS9*xZg1Pmeor9rb zV;H8vJO_0s~nWbTK)UDxHi;u4MTvl}zUBG${g(6dFp1F}YY56fGocVT);#nf~fjUTay&GQt!BGZ0#7 zu85TGRD7_qk9gUovCMj+pNndjCPC3DUFDD9EHKxB<>g2^w0)V>q@KF)~FPo3NAle)y6IuNVz%K zPZj7_+38jtu>;NtF&PxXJ-bLeMzQa95J}$#t>t`H-$QB^8HjYq{X~;vFkfShvwESz zVd^7tF4c-irgN-T*7!hYy>@qwMtYpAq7(d+nUg>LKBwJj!Q5zUX$9k5bqo{XCcg%s(dA1M8Pn+HXJn+xpoaz!5lpI<$@7)#c_5I=zX}2&8LppU z?=O{wyj|$3@tc@9Np|J9X@Es5lDo8cn%X_+``Hi$K{)afIxn=N6doj+=e8(pXM+?o ze+>~EJ}zmlC7TuB4>4tI01Y%rc#6cf#skD&7;c;|Qt&FhZtutp;EF{)X%Olcys!;xV0Y`7co)8!q!GDbk4Ff1}EyG>|)jK{+ zua!p1uDyH+!r0B!YA=e0M_HQL+~QZj<0aO^?%W?KADH9L!NzSga;H%)h5@Xm8Kl<< za?oU5On72&bsS(sQ4ZX=w*w6h-h3EZMT+s@;| zX(KwyHLc@B?L=^Yt)^i$(lD0G4VaklTr`>5#x>ZTj95zu)IkP{uKvsq^V zJ4<=r@Z~2Y@mc&?4RPN}3#LY*>`q4RGNdY<@wW!=o86+6{i*b6b9-@SIiEWxCovM& ztM$Oe%lL_l+nrn3_5xb?bi52akznL-5ed=^fz2JJz0s{JD;t#-DMZn?G(V%4p1z2Z z!?$4`cw4cDqM=TQT$H-7Btca3$W%hXSgaID1Ws9iIVux>Z`2boLB<-DIA0RG$0Jl4 z0Vzaa6w+zQSSP*&b9fk_js`JpD1a?@>WKLW?FSxkMW#&1RLo2f@Uk*Q)lmk%_Pjn! zw)HA_jEc*hiLqxE#JgkSN z{4>_o91c2Wa4##5*ScIEympMoQmw{Cw>q_$t|h`;aULpWOD+*=O<#@VyG}8q6@n*r z;+C%3n%qnp-CPmxX1x=9fP+JHNazx76UjJc#R4iT|QqhG)Zq;UR8E z0`TvqsC6cs2}ajdrVSNT81(4oM=2JJriLjST$nS1b!di}1H{MFf^e92 zgM=Xu$grtMY%fDWNOLjTbvm??7%zhpBc+8K;Ex9Q!)bs&^q=6q0e|$p+dH@XBNcNd zB3S0keA~tATtg{iMi)a<{Y)!BBmzGoPI(CP6e`7rjHnPw_r;4`8}t%02Rkqb_?9Tz zcLU64?1!C<-&M==S$bJAF>v%7lPgS*3Ve_w$*j|jFz>YR_umf=u~P9&qXuiEH@4&7 z_AtxtG*#*>xL?Br1f3qxJ3pRw~lKBGWCpbqKOMe(=n;!ccc=YPXbg^&dtot z%&s`kIoQ~o_*WG+%TE^I-{;`3ZMu{F%OzaYvE=$WM@jvFSnG_#g&;sAg56P%Wh`zC ze(VBOgZ^Vb=l)KXA)THwxlSnFox4N>x$GPf&Fy=)SU6gRz2x<y9?NlEn!DVt40JvlXgaL?h;%*0rGpL2Me3!}1xL-bopll2#7EkE$-?C{wWF7#~J6dRWU2* zF+kSIK1^ePNvyd7m~7bks5+$grI`x%3K~IKFg^(O50suSA`t@jP#nl~j(Bk|pwq=q zy-1QYwg#!u)A_!r)`jRD*&@;|mD{2{BiwUxVLj2FrmuTJj7p8PM?=^TVO0{oFDpcv z{%St>#Z_T9g!2`nfzX4D2pQQB(bU%WLT^7yKhpL5zS3V8$RnJ*sL&u)A8PoItiZaD6A(lIH1G8KbjFH2p0*o za7{{PxYC&m^sg(YEU9i{vpT-s{#}{(@+(EnJbx0UVO*zo#f?)ipyRh&-CBqFKC4y3 z&>e*dU2m=sru9k8=-Nun=5@SmEGY&vdu2M%z%F%`^%i%t9YAZ1r>8cW;ySB*rTghL z$F(MUW8cf>dpk3|xk~t!1uOoU)#ye`ePwEdX_`|EneFz*aTIO(X?``!Ro z{3et2J%o`GSQv!`5^Pe^JPg0A_ZSrQhEJFg!so++ zhOHChaWd(^;3x_l@6q4Z>odgC+fE{UP zY+MK=wU@FUenk&fWCALx^T!=(um{RWM9$}8x4CUI zMMk+J9qHJUWT@6g9f6mx%h#nD^#+DBp(aIcDbde;G|tO@*0}XiyBO>(O`_;*F)Ss; z&|Q{Nn!}aWjm@&QI*AT17w5(JoO)C%SsFyUtdL&(tf|EGx+0aQMAS;x9dR;3N6U>& zb5cisu4++Q&JchAm_Rhd3SBiV9**ihMkPHmibKlGo#koedR05@*29a-CIczguXefpkC)=g_7 zxDpI8$=Ai!!W>)&tlL&$id0dU!5*m@XkEZ>RHl(3HE+3$ggsmCNMZei>V>&e>25u_ z^_omPiW=>#*9&MBu2`?7ty)nnTCcb1e^o0su=a|TmiD)>AXJin#R9A~Y|oRmdEs)- zzP&IrKQng;$D=et!&nQJ>$%i&oz^9K4WBA+sYb2uP;J9JiejlI5ub^A&8rOqn)KaOzD4ZkarHeWGh1J>gM+3 zmFSVOYWtr@W!Lh?_ZRLeufFm!skQ45;rjx25O)38%mDE9g^e$+*Ppw{ia$_Y! zlz0M3REkmbjK*tIU@e8bu(HYF&~`l-6uy>uuApaos{2t4t{#NTNrNrX*D(gZ$sECN z;FE*F;quy97Wp_301WOR-XI=PMYG0e+ys$X`!am|H4~&@5lcE6BA`s_{YD-ca{1xm zXj*?ZsTk^L(WGXIGVxZDLvshu*X=3lu9Cx%ubcg%CY9zL_o;t-KyL}}6;D)hj$?sV zuA^DYu4l>>!TvKv?~HTo;V5j*;>kMEoWPvqTg*xEA$B z{V=ULpVu83iZa(Ls2@+;>fcZ0iK;%IJe=f<#IK6-#&(@huUhvElG0b;7Bq6pn( z%Z+8|ZWB1$1kO55;H>@=+_%8ldToP2hEQt;3@_x@%{cF+l&?lN)JjRHG-dDgynUx~ zmn>rxs7M}HYR@&4ObK;|!h1F`m^QPI_r3U*0NSfmKaD1+j#3v9M&z3a%UHTxQVp`R z7%c-!2?gIy&T$!O+dqWP)2lbORD#-dk1^#K_0KmA_Ba|Lt>Q6@+ZuQk!kb`9R+NZxu$x}gM^OqdV>RREGe|;J8c|)Esj*~AQ0hy4UUwYjz%1A z#Nkh{}QQZ$kDP|^jKKmnXlxs?U7 z03W9%NS!Pc9}{s<(5Rx)QvawHpWHGeDip#VAVaGus#bW(cPmdDD4_;QsDTp7Uk#Ly z_G+Mn@M@rh#_C@KCDcF(86GxJpbZqLxEd(X1`4#{-wRj6zc>7Q!@oEDd&9ps{QD=9 ze{Y~b8z|7iXd5Wd1`4!+0&So`8z|5Q3bcU&ZJu)gBrHK4pA+usODqVAgkeZCUiCHj+ju^tShNQTdNG`Oo(fE*`Tgv-8a)c zOIx0{fE5OtU8n21tN&Ux->DuY6|XZQrJm0iYf)1xJ)Imm^?Z74gQAL8!%s}osfM3u z_=$#}X!r@!aQ>?J2}@L0yKb91Y_=QjXk_biMg*@pHOD1@b~cxB&Q5EBYJYy(o}l$- zsCDP+&r#dM&-yI2uh4v+T948COr7LZ9mDE1(aBoVxG-Fdb+*=2c|T6x`*rq?cluhM zzji0E^@q1Oh0oy}w(d^dS!`XA&3Sy@C$jySY)eZmRzz(P>y!C>fZ@-Mf^Q-9pNrgo zUgNcr{?AqZKd+wKY5^L4rr~EAex~7P8h&Qn?lk<2qSV>=nK5(?4>#2f%?B@d{TgTUeet=+-2wzbIrBH;t!tg4{!0H~U2PYf2 zrWoWh4^nO)(T5VeX0H^1J6?yJ3T*LACk&4K-GeB)wSpLz#Ym~~2Ji?Epa}CRqE%si zWSw>$gempP5d^L?vm1mn#SUY{Y8imvbzGMtGCw=x4?Bo4K9dn3@rZ~{<+ZNAJKFy& zu92rWU>@ED&Wc{3XL1aD`Qc%?X{AurL;rO#vA^g(z!3U zgAS|S)#7E&2_#oUyAoI_`?j{W@s%$;vP1pb*iS#7kKPORDB!dtWDKHYxT6YV@o$#_c=1y1Mmhf0%t6%mX^#aV9CW>&3pAsh z9ORwJgFf+{t=z^E8?wzD02OG*KJ+X=stem}rKa%E+XvzzV{O}O2QZ%Y>R=!-wV!`p zQ31WWGuqn=ZbN}e7q>^f-ZsVRtvIW_Bkwpb1xdNG4tVCmvzl6ntV1mr!c$h?X7z-%V-m-Mx`x8M ziDSc1S8`mQ71Y$Awue8yqn`TGTg5s@N$eg(6=ZgPRr#bY{3@r#LRbR`>OoH6Z(Emh*=r*;s<)g8sJ9{65wc<{ASwJ8g-4Q~Rc_vj;6 zA1xIb#L+P7L?GbaT-(k)!|oz*f3h7%yCouv@LrGm6%vaR4wP_i{%U&`{x?^SxfzJf zHaDa0?SML|enz1FFwW24yt!R&7JBn}=xu?~D$h@AHxB$gRTL1NVNd|SihSM;6Gt58H3NQ^}Hn6l1m-Fn2YC>+kt06Ck;MlO(tUI2Oa-f5Z+1(D*z{Lb9=3F zZ;Q8Q<^J~Odf{H|Q;ejNv?tu?YKPP?Tj|!vb(WG#EtN+Wco=;Vc**pv8ARQkVsRA{ zjGTPj4?@zbJ{|*OvF*c@;j2;DO@zcjb5AElfj_1(;zB8&Hf9RT3ZnHOCJz1hiFl1- zYbz(5iUQX(sVB$dG+HLtHH}tJ#_Fk9Q-#G5X*&SL5o+0Z^*pt0vwGgvG?^pkGl~{l zt?Fbd2z*T=a4gx?T{*vMDlQ+D+KQnd{WZq&wX0a3n1!nOFuLvyiggRl^g2+x6=!k2 z2!E-B1R(sb6%xX$CTZ@fg&1qcyx5eQY-svZTDj?~N4Y45l$8QHC0!jxJ)aSlk{l7u zZmNm0n)YG|gc~T_D701-IlMhWgE-D!kas3*74C2Xmm+=beT`g+X7%{X5R{mAlnDNZlA@M$%AVeZ|;{YE!j3nZPdFQUz5oY?mX% z6vBkawH@|uui_g|%NL9-XqI516R^jyQEdS#6i1_>e-r2aiM_qVAHo}R^H;U+7-ur- zy6Buos0vlm>9lkDJf10aX%P+|Si^^=K z!nCCA5q3!26Nzs*-6yw^8p6HLjlwR9cWe8f{cKS;naP+cvAyt6aJk4YyR2_b9}8uN z?3SBPTESZ7#YKic+5=G&SZk>+-P{kOn6*#bzN`XOoz+ap)bXI}Z;*F=vf^w-p)Xi^ z@hNWjnA9TY5Y{@JYcVxs2exviK7EP`Qkpo`gv&Y4g&fUI^9?YjeeQ`0Y~OWS(&vFm zd5n56S;j>Z?ci>%$)SiPAUlPivTTq=da!UQzHHl+IF zh1pNiTFWO-FfO*oJb@=$UTRRod9O9g&slEO=JU>v`+4!!liTZ-UtF8H`xCti1Y+1Z ztcK2~Z8_v}oZpv4VUyLpx;F6cRF2)vexBp`uh;+e&%yJPuNwYuc0u@m0wcJ*Jiq*z z*|{bBr{Vt|!1ZADe;SL%I2u4y14wm{!oHhn3Cb?04|}FRnU{9|f91gM0Ub=*Alsj@ zjXL>n;p%c}|1U3JZT9~Ixt@J?=E@ZC?lM~q1;Ggny#1?P>3F5JOm{KA^RMsz~w2Ftysbwg$P7j^QIsBMntS$+V z<@K!{EQ`V-x^iA_Pi^kW1Seccn$?G9jPC`*G~$jBn>~a|#mti|vTuFh3r~NG49J6zSI0%P(PU~+H_ixa5y-*i7 zYQMJBKMN}^_bDk4pm1FEk|F>%m7u+05>npAC zH&4{>Lq&XWYq}>xHn4vjC2%l9U8b|R7(gI~(&ol$qYH8?Sm6LXh2alH*5OccBFF_) zUU?37eKhn-BdyjVl8U;!$3=ERII!VMc99g7`AqSujWb%F2v^z5R8>`+Mc({v^~lJF zJ5;errj=YK>q3geut9XHLwW~GT05As1D=vJ6^pey+;Zt~xKtc!M(f&_brpsAvE`_N ztgHUk)YkZOkVS|R64EvYyv>tw!!rqiS~ z?X=L@nSaQ>(qAl>_Pqi9Ron=4%RltRufzvZH_Vq))FADSNoMV*F3bV04N>xN2zT0( z{NGuJcoc9n?$W6C{UH`OLY+JQz_iZzFFUMJ<3xP%7bQrg3QALhk)HbL!EX3Kof9## zF0didabJ!Zxsssc(bN0UIXHhin8)#0EPzT!p-eiZy})*kl+zISw}*a6Ni9f2Exq4dB1K-Yxj=u#dlijOwnxp-{Jhw1EzqCmEf4YNtTsGbFE}5+_QuIKUpaa8<+~qz{VyN9c=FqSa$GO&FJrdc92|g%_evdv1S3Oo z2t%tmeEKq$fnZoJIH$jV^YmM-g@!$wg0;N`nO~^UJ`F+ zFJM-2;-rpC3PbczC#P5RCQTc>^WjH#fAr?zpo8%VcRqaM&b#l)PIDWgWYr!ji@D-u zQE;FGJ-FaVEj(u`q>(#_yKr<6C4QpV<-qOHsFr?9=_iC zGTuej6~ow8ay(^*V^LVp<1WiW=xiPv;~}pqyALk21CIN>xjw0@zv;*2U{1H@Bd4Dk z9mh*gOkRqGuUxrLf>sb?%10N~`aOR*RSJ-FbCtK)ccL{lHI<^D?69^8MgK2PPuN`(2NpWcA{;=Y1Q}SY zSWb>p3OwUULgeal_ok{)0sP>CvzG`-a~}8?oMJ^O>~Y5%Fh;Tf$GrfHpc_X6`P+3? zuU(_x5?bR1a2bywBTBw{xtWef;cBuKg;+q`7B?{oIRX-JPf`a#jzATH?j|BmurJ>u zP^_{gFE~%5nHrWRaNr0K+wY@=MCQB|$(rTnPhY3+00ltAJ@naVPG)XQW7|PSnP=f3 z!cQu5N@MopHnSF9FNx6p6_a`ksY%%Jg%v%)(P2tdKZIQqg`|FDSpg<=*b~ztyD|+@ zN#_8YzYb&s_g5Pi3@{%~H*$`=urQL`Yom}y%h{LrqFygLVmQ|eBzr&+ultXfY8syR zv4POy!9<=Bi92l#IFK9QN2+uKb5C^Vi~8zmjBFxbqGb2J`gO?Q6H5UvJGB5*?qd5?8dGi z42osMO^gh`fK0*3Ti-qX-mmX{^s~Efe*fcdzjXJ*x27_eEPnOmgRh^w`ZfW!oxb_2 zlMi0GbmxQDPk#OS=?gzR{mE}mKKco!%caLF&K@ig{?aFJy!r9Z|K-*r$=#RVKKVc2 zy!X+o_kQ&vvs?5Wo)B3T@ukAM6(Jn5yQu$xV(f40?vCPwzyoKC_rHp&v?Y1d8w75u zkw(Zf!zDfzn^^X z&6Dr`?BxCTVAkjyyZ7JUI(_d|D3sg^2Ci_qW0;n_Lmq#P^7#T(cQlM#CY32CAeW>I zq%{2_r8Uf+xgZ$_w20)0)99)Fj*Gar@gZW$F3>p1e4%>M#ZTHj|IqKXR$7Sp-I|tn_q-0l@WTU)A?z)+p64lree3WR z1j*{o`!Aoq_??rtUO)ZuckaCZhkI|ofAY$YQR4dNpPqdCHz(hH=j4^&-g)=yci#Q? zdw+cU!lV*DgmhHMLV?g-8~KfURqqR7Q^$5KLrkrc~_nd2^D$g9JfzD@XRuq->h zXw=<>-LvA{{nu}tzVX7H_kM8l-fJiC{p;TeQE0{a+}eio)b^UQv9*bUA=CnEJ(ZJa z`@qHt28WB;m&3oxiyGm|v$ciXv1wKF6rTL-J(#x5?Ylqv{k^yUc=vZNoc#IQ_x|wh z)1UkS{{Qh${%Z=s{%-qSs635|b%i%~RugaVlehnL@1r+>G^Ull{;iXr{mbc({&4cW zU)=lVhnILcf;@ck;ScZr{{6e}|KjA8AKZQU9e(THTko8F_!>-+e0}=PhyM(6EYPVt z@BZ%e|NRPQ@7VjnJN^Mhv@^gbQWekV_Y zaW>>U!2eJG^PixB(o|%VK(ValX#k#zyMRNa_JhHlkA8mlwVzfe6At+tL4D-v^6cz^ zSlJ!_&bvQ^`4lG%e@ZEo1rquC0tS(Yj#O`u9g7$)F^W*Qlbo%3W zxMlp%Tv@J~axc<)mKS-TDP58qARRD|E$CvoT^c;>BGMi3@SVbaLgOI^a9{&scVD3u`{H{*CE&-M_g?<^>o1&s|KHZ|HD`5u6U)5u&dJZeUZ{`wc04IN zVc|Z?tN0yD%?re}7LS)?RJpVBgT4FPZ$KYl`#Wnl){#A6A1y3~$4h>dLLp`gn9ls= zgV#Bx0vI;~taO_=u=33kEx}tT@}e{=8VW~ULG;K&Px89AC;N~TjH zkaFlt({LcKWH5Z{rq_`*^B+R%tHYt!Ie_I|Y;vyD^x)B&strEtd! zQF-VGtX~YBi{60jHCW9>CqkX$#a^%*!-;xPhMHW!3bJBS6NDxv)GGCa98KCnX;E^9 z2VUBouvH7wlkg|U=!>{>08&;0^|OVFecJpkw|R~GA=?Al?hwNp;PBrY^&B|TS%-(c z-W!D-R(MK<7LT`!!Wr^wsN3?7FG81vyEDdqKpPmsGVn1Vt#gb6@T%iP3w($sWvCam z?ODU9Xv$iq!C})e)<@ZgfVB<=J{il)qv=MZ$P;eeUU;0FI}!F1`+I2j?~snRI6bUb z$aQuqXBf&f5RmKvn(0NHFD;yWT0vIt0V~({!eP*n1+kh-2*R7J%*-TyAF66c@&3%^ zsWc*CzWr!>t~~V8Fog~~|6zB1tUH}e^Xx$FcJm`=C)ZNDIB5Kgxq+k3T03-Ju(BJl z6neY@v+h_y7n304If0&Hw-a literal 0 HcmV?d00001 diff --git a/cce-network-v2/deploy/cce-network-v2/Chart.yaml b/cce-network-v2/deploy/cce-network-v2/Chart.yaml index f75b394..7fc451e 100644 --- a/cce-network-v2/deploy/cce-network-v2/Chart.yaml +++ b/cce-network-v2/deploy/cce-network-v2/Chart.yaml @@ -15,10 +15,10 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 2.9.1 +version: 2.10.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.9.1" +appVersion: "2.10.0" diff --git a/cce-network-v2/deploy/cce-network-v2/templates/cni-config-template.yaml b/cce-network-v2/deploy/cce-network-v2/templates/cni-config-template.yaml deleted file mode 100644 index 937ce21..0000000 --- a/cce-network-v2/deploy/cce-network-v2/templates/cni-config-template.yaml +++ /dev/null @@ -1,58 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - addonmanager.kubernetes.io/mode: EnsureExists - name: cni-config-template - namespace: {{ .Release.Namespace }} -data: - primary-eni.temlplate: | - { - "name":"podlink", - "cniVersion":"0.4.0", - "plugins":[ - { - "type":"exclusive-device", - "ipam":{ - "type":"enim" - } - } - {{- range .Values.extplugins }} - ,{ - "type": "{{ .type }}" - } - {{- end }} - ] - } - cce-ptp.template: | - { - "name":"cce", - "cniVersion":"0.4.0", - "plugins":[ - { - "type":"cptp", - "ipam":{ - "type":"cipam", - {{- if .Values.ccedConfig.ippool.enabled }} - "min-allocate": {{ .Values.ccedConfig.ippool.minAllocate }}, - "pre-allocate": {{ .Values.ccedConfig.ippool.preAllocate }}, - "max-above-watermark": {{ .Values.ccedConfig.ippool.maxAboveWatermark }}, - {{- end }} - "eni": { - "routeTableOffset": 127 - }, - "pod-cidr-allocation-threshold": 1, - "pod-cidr-release-threshold": 1 - }, - "mtu": 1500 - } - {{- range .Values.extplugins }} - ,{ - "type": "{{ .type }}" - } - {{- end }} - ,{ - "type": "endpoint-probe" - } - ] - } diff --git a/cce-network-v2/deploy/cce-network-v2/templates/ds-agent.yaml b/cce-network-v2/deploy/cce-network-v2/templates/ds-agent.yaml index 8ca35d0..6b9bcc2 100644 --- a/cce-network-v2/deploy/cce-network-v2/templates/ds-agent.yaml +++ b/cce-network-v2/deploy/cce-network-v2/templates/ds-agent.yaml @@ -77,9 +77,6 @@ spec: name: etc-host - mountPath: /lib/modules name: lib-modules - - mountPath: /etc/cce/cni-config-template - readOnly: true - name: cni-config-template ports: - name: healthz containerPort: 19879 @@ -147,10 +144,6 @@ spec: path: network-v2-config.yaml name: cce-network-v2-config name: cce-network-v2-config - - configMap: - defaultMode: 420 - name: cni-config-template - name: cni-config-template {{- with .Values.tolerations }} tolerations: @@ -163,5 +156,5 @@ spec: updateStrategy: rollingUpdate: - maxUnavailable: 1 + maxUnavailable: 10 type: RollingUpdate \ No newline at end of file diff --git a/cce-network-v2/deploy/cce-network-v2/values.yaml b/cce-network-v2/deploy/cce-network-v2/values.yaml index b6dc16d..0df6b66 100644 --- a/cce-network-v2/deploy/cce-network-v2/values.yaml +++ b/cce-network-v2/deploy/cce-network-v2/values.yaml @@ -107,8 +107,8 @@ ccedConfig: debug: false # 在非k8s环境运行使用 # k8s-kubeconfig-path: /run/kubeconfig - k8s-client-burst: 15 - k8s-client-qps: 10 + k8s-client-burst: 100 + k8s-client-qps: 50 k8s-api-discovery: false leader-election-lease-duration: 60s leader-election-renew-deadline: 30s @@ -138,9 +138,7 @@ ccedConfig: {"syslog.level":"info","syslog.facility":"local5"} # cni 配置,从指定文件读取,写入到另一个路径 - read-cni-conf: "" write-cni-conf-when-ready: "" - overwrite-cni-conf: true # ipam 模式. privatecloudbase: 私有云底座;vpc-eni: BCE VPC ENI @@ -169,7 +167,6 @@ ccedConfig: # operator BCE VPC 配置 # vpc id - bce-cloud-vpc-id: "" bce-cloud-access-key: "" bce-cloud-secure-key: "" @@ -189,23 +186,28 @@ ccedConfig: eni-enterprise-security-group-ids: "" eni-pre-allocate-num: 1 eni-route-table-offset: 127 + # IP池设置 - ippool: - enabled: true - minAllocate: 2 - preAllocate: 2 - maxAboveWatermark: 2 + ippool-min-allocate-ips: 2 + # 每个节点缓存的IP数量 + ippool-pre-allocate-ips: 2 + ippool-max-above-watermark: 2 # api 限流配置 - default-api-burst: 1 - default-api-qps: 1 + default-api-burst: 5 + default-api-qps: 5 default-api-timeout: 30s api-rate-limit: # bce api 限流 bcecloud/apis/v1/BatchAddPrivateIP: "rate-limit:5/1s,rate-burst:10,max-wait-duration:15s,parallel-requests:5,log:true" bcecloud/apis/v1/BatchDeletePrivateIP: "rate-limit:5/1s,rate-burst:10,max-wait-duration:15s,parallel-requests:5,log:true" - bcecloud/apis/v1/AttachENI: "rate-limit:1/1s,rate-burst:2,max-wait-duration:30s,parallel-requests:1,log:true" - bcecloud/apis/v1/CreateENI: "rate-limit:1/1s,rate-burst:2,max-wait-duration:30s,parallel-requests:1,log:true" - bcecloud/apis/v1/DescribeSubnet: "rate-limit:3/1s,rate-burst:5,max-wait-duration:30s,parallel-requests:3" + bcecloud/apis/v1/AttachENI: "rate-limit:5/1s,rate-burst:5,max-wait-duration:30s,parallel-requests:5,log:true" + bcecloud/apis/v1/CreateENI: "rate-limit:5/1s,rate-burst:5,max-wait-duration:30s,parallel-requests:5,log:true" + bcecloud/apis/v1/DescribeSubnet: "rate-limit:5/1s,rate-burst:5,max-wait-duration:30s,parallel-requests:5" bcecloud/apis/v1/StatENI: "rate-limit:10/1s,rate-burst:15,max-wait-duration:30s,parallel-requests:10" - # cni 接口限流 \ No newline at end of file + # cni 接口限流 + # 扩展插件列表 + ext-cni-plugins: + - "endpoint-probe" + # - "cilium-cni" + # - "portmap" \ No newline at end of file diff --git a/cce-network-v2/docs/plugin/user-spec-cni-plugin.md b/cce-network-v2/docs/plugin/user-spec-cni-plugin.md new file mode 100644 index 0000000..78fe153 --- /dev/null +++ b/cce-network-v2/docs/plugin/user-spec-cni-plugin.md @@ -0,0 +1,58 @@ +# 用户自定义 CNI 插件方法 +cce-network-v2 组件提供了用户自定义 CNI 插件的方法,用户可以自行编写 CNI 插件,然后通过配置文件进行部署。同时,cce-network-v2 组件也提供了默认的 CNI 插件,用户也可以通过配置文件来选择适当的配置文件。 + +## 1. 前提条件 +- 已安装 cce-network-v2 组件,版本不小于 2.10.0。 + +## 2. 自定义 CCE 官方支持的插件 +下面提供 CCE 官方支持的插件列表,用户可以通过安装 cce-network-v2 时通过 `ccedConfig.ext-cni-plugins` 配置文件进行选择。 +| 插件名称 | 提供者 | 插件描述 | 插件版本 | +| --- | --- | --- | --- | +| portmap | 社区 | 社区的 CNI 插件,支持Pod 上配置端口直通能力。当开启 ebpf 加速时,该插件会自动失效。 | v1.0.0 及以上版本 | +| cilium-cni | CCE | Cilium CNI 插件,支持网络策略、service加速等。 | v1.12.5-baidu 及以上版本 | +| endpoint-probe | CCE 提供的 CNI 插件,用于支持 Pod Qos 等能力。 | v2.9.0 及以上版本 | +| cptp | CCE | CCE 提供的默认 CNI 插件,支持Pod基础网络通信能力。 | v1.0.0 及以上版本 | +| exclusive-device | CCE | CCE 提供的 CNI 插件,支持Pod独占网卡能力。 | v2.6.0 及以上版本 | +| sbr-eip | CCE | CCE 提供的 CNI 插件,支持Pod 直通 EIP 功能。 | v2.6.0 及以上版本 | + +例如需要开启 portmap 插件,则可以通过如下配置文件进行重新部署。 +```yaml +ccedConfig: + ext-cni-plugins: + - portmap + - endpoint-probe + +``` + +## 3. 自定义 CNI 插件 +CCE 也支持保留用户自定义的 CNI 插件,用户可以通过修改主机配置文件 `/etc/cni/net.d/00-cce-cni.conflist` 自行进行部署CNI 配置文件。CCE 在更新时,会使用用户自定义的 CNI 插件。 +```json +{ + "name":"generic-veth", + "cniVersion":"0.4.0", + "plugins":[ + { + "type":"cptp", + "ipam":{ + "type":"cipam", + }, + "mtu": 1500 + } + ,{ + "type": "endpoint-probe" + }, + { + "type": "user-cni" + } + ] +} +``` + +CCE 容器网络在启动时会自动读取该配置文件,如果发现了用户的自定义 CNI 插件,则在更新 CNI 配置时,会把用户自定义 CNI 插件自动插入到插件链的尾端。 + +### 3.1 使用限制 +用户自定义的 CNI 插件,必须满足以下条件: +- 确保 CNI 插件使用的规范版本 >= 0.4.0。 +- 确保 CNI 插件的 `type` 字段不为空且不要与已有插件重复。 +- 确保 CNI 插件的可执行程序`/opt/cni/bin/{user-cni}` 存在且可执行,否则 kubelet 无法正常创建 Pod。 +- 确保 CNI 插件完整的遵循了 CNI 规范,确保 CNI DEL 操作可以正常执行。防止 CNI 插件异常导致 Pod 无法删除重建,持续在故障中无法恢复。 diff --git a/cce-network-v2/docs/release.md b/cce-network-v2/docs/release.md index 5eb7573..3908b1a 100644 --- a/cce-network-v2/docs/release.md +++ b/cce-network-v2/docs/release.md @@ -1,6 +1,20 @@ -# 2.0 +# 2.0 v2 版本新架构,支持VPC-ENI 辅助IP和vpc路由。版本发布历史如下: +### 2.10 (2024/03/05) +### 2.10.0 (2024/03/05) +1. [Feature] VPC-ENI 支持自动获取节点 eni 配额信息,去掉了自定义 ENI 配额的参数。 +2. [Feature] VPC-ENI 支持 ebc 主网卡辅助 IP 模式 +3. [Feature] VPC-ENI bbc 升级为主网卡辅助 IP 模式 +4. [Opimize] 增加 CNI 插件日志持久化 +5. [Feature] 重构 CNI 配置文件管理逻辑,支持保留自定义 CNI 插件配置 +6. [Feature] 增加对portmap 插件的支持,默认开启 +7. [Feature] 支持通过在Node上添加 Annotation `network.cce.baidubce.com/node-eni-max-ips-num` 指定节点上 ENI 的最大辅助 IP 数量。 +8. [BUG] 修复arm64架构下,cni 插件无法执行的问题 +9. [Opimize] 增加 BCE SDK 日志持久化 +10. [Opimize] 优化去掉 bce sdk 的退避重试策略,避免频繁重试 +11. [Opimize] 支持使用`default-api-timeout` 自定义参数指定 bce openAPI 超时时间 + ### 2.9 (2023/11/10) 新特性功能: 1. 新的 CRD: 支持集群级 psts ClusterPodSubnetTopologyStrategy (cpsts),单个cpsts 可以控制作用于整个集群的 psts 策略。 @@ -8,10 +22,6 @@ v2 版本新架构,支持VPC-ENI 辅助IP和vpc路由。版本发布历史如 3. 新特性: 支持ubuntu 22.04 操作系统,在容器网络环境下,定义 systemd-networkd 的 MacAddressPolicy 为 none。 4. 新特性:支持 pod 级 Qos -### 2.9.5 [20240325] -1. [BUG] 修复 vpc-route 模式下,重启 operator 可能导致多个节点的 cidr 重复的问题 -2. [BUG] 修复调用 bce sdk 出错时,可能出现的stack overflow,导致operator重启的问题 - ### 2.9.4 [20240305] 1. [Feature] 支持 BBC 实例通过 Node 上增加 `network.cce.baidubce.com/node-eni-subnet` Anotation 配置指定节点上 ENI 的子网。 @@ -29,6 +39,7 @@ v2 版本新架构,支持VPC-ENI 辅助IP和vpc路由。版本发布历史如 3. [bug]修复 cce-network-agent 识别操作系统信息错误的问题 4. [bug]修复cce-network-agent pod 被删除后,小概率导致 operator 空指针退出问题 5. [bug]修复创建 eni 无法向 nrs 对象上打印 event 的问题 + ### 2.9.0 [20240102] 1. [optimize] 申请 IP 失败时,支持给出失败的原因.包括: a. 没有可用子网 @@ -55,13 +66,14 @@ v2 版本新架构,支持VPC-ENI 辅助IP和vpc路由。版本发布历史如 11. [optimize] 优化 ENI 命名长度,限制为 64 字符 12. [BUG] 修复VPC-ENI 并发申请和释放IP 时,Pod 可能申请到过期的 IP 地址的问题 - - ### 2.8 (2023/08/07) + #### 2.8.8 [20231227] 1. [BUG] VPC-ENI 并发申请和释放IP 时,Pod 可能申请到过期的 IP 地址 + #### 2.8.7 [20231127] 1. [BUG] 修复 cce-network-v2-config 中 --bce-customer-max-eni 及 --bce-customer-max-ip 参数配置不生效;未限制并发创建 ENI ,并发下最大 ENI 数量可能超发 + #### 2.8.6 [20231110] 1. [BUG] 优化 EndpointManager 在更新 endpoint 对象时不会超时的逻辑,且由于资源过期等问题会出现死循环的问题 2. [optimize] 优化 operator 工作队列,支持自定义 worker 数量,加速事件处理 @@ -76,13 +88,16 @@ v2 版本新架构,支持VPC-ENI 辅助IP和vpc路由。版本发布历史如 1. [优化] 优化了 psts 分配 IP 时失败的回收机制,避免出现 IP 泄露 2. [BUGFIX] 修复 vpc 路由模式下 nrs 标记 deleteTimeStamp 之后,由于 vpc 路由状态处于 released,nrs 的 finallizer 无法回收的问题 3. [优化] 优化创建 cep 的逻辑,当创建 cep 失败时,尝试主动删除并重新创建 cep + #### 2.8.4 [20230914] 1. [BUG] vpc-eni,修复在 centos 8等使用 NetworkManager 的操作系统发行版,当 ENI 网卡被重命名后,DHCP 删除 IP 导致 ENI 无法就绪的问题 + #### 2.8.3 [20230904] 1. [Feature]支持kubelet删除cni配置文件后,重新创建配置文件 2. [Feature]network-agent支持开启pprof,并获取mutex和block数据 1. [优化]去掉network-agent在申请IP时的填充锁 2. [BUG]修复network-agent的默认限流配置 + #### 2.8.2 [20230829] 1. [优化]提升创建ENI的性能,缩短nrs任务管理时间 2. [优化]增加并发预创建eni的逻辑,当单机未达到预加载eni数时,并发创建eni diff --git a/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-0.jpg b/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..34e535e587e5f52cf591dea3666eae586042a98f GIT binary patch literal 405543 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%0DQ ze>2o8$hkpPq}Z?qFvN9x%8@!iE?Z#h>Ic05Z+V-K-*4(<$wGD9>*F4`C}@6|;%U|} z^)GTAENc9B6L39s-9|jsg60smQ9>X?p4#0N*xdn?;=+T@$b|zQkQTt}KxsFi({ON` z@|g%bg551r-1rw3pQJlB0MVVSosfPvK_0$>fLijcX$HsHo2zXLdZb~sMLUKXDkmMk z3QkKkia+r-)rt>7!f9lWE#kz+Q)VzId3qy*LaNl~pc4t(2-B=?MjA~+q`dw&yvd2) zxJNMpy^q04FyYx3klPb-fIz?nz0bgm88l*c3JC<|Pcu|WewYDcDry!^Ya$DdNXAVQ zzT>U|tt%~%2aqoC%Z&oLM_}bsf2vm#H(ZfGF#vl-2S7v`#$lQ;#_G~GZ3Cmzmuq)S zPz+cj?J(gISmh8@&x_0lnI|jAH^}kJJC>cLwGO}iX)p2H zdirBStuzlB_%J7!@E*GJcEaI#gT!j~II0c z(zk-BfbzTHwIO{dfZgJA&D$GWQZp~!K5AOMr_&g%*I0LR(M+Z_E-LSbKqe&P=GVM^ z_a~2FuR~#gjSxl0?uHbF*;x(Qa-?xuXZ9G*8$!e8AZ52Od&VuY^3Osy0|A+Cg2E^l zCC_vK_pf%g?-}_4Z=^~c`9P7CyZeN!!CrGIDChMHWW!yF3gQov0+zJ+HLHG=ez`tV z&nY_#UcjZCsiB~uY|zF5vROYvPCp4ClSQ8E&g69=BLx?VM^MZ^M%C`(k`2Y1pw)8# zk|UEp->ICPjoB(-DR`05PT2iv6LuduP~iNEIvCu|gY;KBXh~B#kPV1D10?}Qtf1_K zyxi|&(}eLj?$i#*0@UXtyTZrOe2)b2{;4@@>O;`!Zy-8@q6?a=L;(2Kfrc^Y_Kyok{;tWqP6Gy-!j2D+VAa0a?~N79+OheSh!5>si7o9# z(KVs8UG4jzo+u`C6LjUYU!2kb%{XM0k&smjk6*Lnx`{;1>#-N6-picOf0Y-r<-p~+ zbJt!v<;d{U#$wtR#cH~hcg{3b&Tm+(5gO?qhaWl=pZ_Dlz0cT)g{e&6o<2rsLVa)_ z;E#lSAQWtoOgTg36EU*FRvt3+T7Rit`L!xyEn_I2 M|Jt6mc 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

$xzo=ncAfddzxnR}d-Dx8Fjqp^n1@!3T5?AY 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 literal 0 HcmV?d00001 diff --git a/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-1.jpg b/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50faa7a99ffaf61ec82389bfff2f31029bffd9bd GIT binary patch literal 274228 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 Lohkoj_rsq6o)zBB literal 0 HcmV?d00001 diff --git a/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-2.jpg b/cce-network-v2/docs/vpc-eni/images/scale-cluster-bbc-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9816aa534a7e2d13f886552df47d5d1170b4c17a GIT binary patch literal 300089 zcmeFYcT`hd*Dt#1T|}CKfKo-8QWXS5qy(fl=^`~?Kzb*Ng3=WP6a++i2|WotN|i1h zLK6^>9$IK2xzYD~e9!ld`^UNWoH5S1cbt{6_Wm*V+H=i0*Iskaxz>g-LHGfj*HQph^4zgn8huS^(Gy0CaSK>i_^y10=7&067s6n*bcd3IL>^NdKWwnSLVs2a_Rx zUnFb-xAa{+d_25eJf2>W62Af5x(CuB|IL9|{$PPW@cal@>|_E^MZWrs=KaRey)eQ8 zgE2@|)mGn7PaX6??T=6l_wAlOeL{L303hx@UWOX?t~@lixI*=dC=Dq<3(x=;?Ciat zs_5%K_|5a5^B?|y57@CkvIE0nzj^&7|M!6l4vyaTMCGlB%^y2>+e3)>3$ZTSKlSnf z0J1_NozLIr>2KUi#4KJ!0g3p>Z|wXpy#E_N{)5S>|FkhSR0RO?%S7|i+u38}1HZcOniTVkLVQI|m}JC1Ozs#N#*qNyOKP?Vkbw13eMndi>w; zbpCI!o!#TV{O#$()|6ungy1&Q%4^JnsI_Mt^ z_5m6HgB`uqjQ+u19)^GWa`IAD`G;=jMMVF>$Jfo|FWtov^p}5-i?P{1ZT!Hdy8mEr zH>1Du1i7gGrF;1r{*~X+UE|(A?VY^T|LP9$CQ|>SUl%aw@4Y@Q#=oC~KYH2Q{ab!V zca?wh^nUnPHV4Q1YX7u%bT|2{?^B<9fBSm+EVB!(oBB#k7Sq==-Fq>kh( zNf*f=$t1}<$tuY%$q6YLDLp9*DHo|AsRZdQQWa8dQe#qUQfE?6(jd~8r17NhNk5a8 zk|IdIlJ<~}lKvoFCp{no$mqz}$gYx!k=-IwCDS9bAaf$~A`2snCQBjvOjbr#Pu58` zO!k9pgY1}`lAM|R3b`1$0=Xu+3AsJFCwUloEO{DvAvuD)jeMAVo*YAtr(mGCOd&!c zPXVHMNa0N3PZ3FxLXl4ar)Z}brC6fCQc_Z~Q3_GYQEF0}Q@TN6kYmNv%$8LG4Z*PMtzsOx;X9 zM7=_NN^_n@fJUB1kH&!}kS2j9m!_U(fM$v2l$MEBh*pu-nAVl{IqiGeGTL_9Y1%zH zIyzpu+jItWV7g~?@98S&zR}Io9n)W+zfP}8Z$R zLp(zfLo359!_hhBbK>Vf=N!&K&!wM3oEti~#Yn>_z^KA#!x+Sv!dT5Xz_@Xq=Dgr} z)$?}e!_KFluRA|}{(y;@=_ZpN6ND+2shFvY>DL9S3xXHaE;w9xc_HUQ>xD&TGG;zz zRc3qU7tFcL?aWIolq^ClAQo4a7#0{yKMRKS0;@EuDQf_0I%^Z_4>mG3K{gQE6Sf4l zDz-7U6Lv0k6?RAVDE2b;A@;+IoEPt2bi5dSvHarb#gj`{E~#B|yY%)F^3n_kImdMl z0}g+VOpbPr4NewL1x|a;XwE85^kve^*Do7h4!)dox%ct`7dMv%*Hf+!T&-Li+-%%; zxm~&6aW`|X@-Xu#@ql^W@t}CtuCQKFx$@-7`zvi%Fjp^M)x7F^HT!D+)l*&}UK8GM z-g4d0*8V^f@XrR1d)Qjgf0q! zgo1=hgr=`CTvNX0dF}JH5n)QaCu@bR)aW?S>;^E>*@hu5{2@8q05}gwG8!|VZ+{nE#b@Reat()OD z>u>H#UYE3&OqU#yVvtgof=bm&?MRDAJ4$CtPslLK=*Ya1X^}aTm6P?7Et6f96*y59@LrB6yfl&>n=DSuX8xXX9f;couj6%}C>SCvwgt$R1_dEJBGJ5-fd4ORVe zpX|QI{pkDsY8TW@)Y8;u)p^yO)l1Yd8ZsKe8ecRiG_^I~YL0`rK=z;_5JpQ*D^#mZ zn_k;UJ577x!Sx4T59)PDb+mMnbf$F$blr84dVn5CFG+7kUr66mzrldgK;IzUVA=4d zVTfU;5wnr4QLz!$_?~fs@s!Cm6JL`SQ$|xO(<0MDv-@U=WYp=vrM*JvXZfiv>LM(vJSBBvf;3KV$)>HXlrL%{fOd`*`wk|I6FPN&vplo zL61K^#@MUcr`oSO+;w>8ufA55yScY{aC-!J3_cZk8u|2xr@UvfC&o+LE8m;M+uFO< zhlLm*_4;1(jqsiKQ}RpqJMuU6uMS`ea1ZDWydD@8xDuoulow1MY#-bbay8_6$b6_u zXm%J$*yFGkC?E7Cbm^JKv%=@J&)uH)h2IEI4BvlY_M-kJ*UM)w7b7$xN+QoidPR=E zQh1g1n*25Rb$^sp)Q2d1v_o`v%#E0o7+kD6ENg+Em}vAMYQ$ul>OP;mrqJnoHVf`n~k> zkC#72eLT!?$Qa7Jn^~5{l@*h9@(KKDB3mOH@mcV5N)Ba?U(Rx_X>MoUt-PXqj{KPX zvjX>mABBd6ZAEfLMa7qk6G})+d`nhKtxCVc?!#)!#LBYE*~()ofC}G=waQ18V^!K! zE!DTG%W4E_KEj#d(FhVmAOeGQLH?*UuN|z@s%xoNsE0R*Hxx8pZA@=sZAxgSZGPEI zKn0-=zC8W1@zwe3VvB9dRO`dmkv4<2{&wy5Zyo9#t(_{JsBenj8oT7X>bh@rBYR|f z5WOk=k`s(*J%eAHTr|YL1k(*4LAGfY;)okD0?!`RBEbVyh5O!ntF6|ZU zOYgTF=pW2tA=uNy=%Y(Vg~zv!J5S6`R!;qKRJiwN!e{k(ZTvI=LLfY~^CHFy#6m`_ zq)x=_Ww(GBCzt~O^LGHCwft9H{ig-Vzj6U0Ci##0oBUt!zhdv-7l<(gkoN@uOr8P2 z@380-u?l@5hO)$}e-{9Z?*gP!QhzJ)>hI$1BqjL=kkAoxB8n6OVUHL$QV9dV**Jl4 ziX;%u@`=Kt0pJVvU-A6!*1W{HOxlJRWspoQ{ti+9bNN@SPo({$<9|I9H782)yZom! z;S0b-O@^d0B_m-1NSR2;m`Dh101r`Tir-!R&iwvWNJz=ZDJZF^X=v$)4dCYiQW7#U zQgSj1ir>i_Nht9;K+Z&Q;p$BlN@fE)Dqc?($>(pgsrl|zeq}Wr#PCZ!_6nz=Wn;g1 zi9Uc#7f3QPI2`n zyT~FBYu3OaM@5VxMA#Rd`1NkObYWXuz8hfJ9 z2*4-qA_4%dvBg^&IO2K;fFMyMJAYg`0T3Xf=V#ge1mLcs5uT_Z3WxaIbDWCTApqSi z+sC$eZK8N&r}wt;&w>ih@Pp7}h+68N1TLK@XvEwu3YSV0jtUzwcLp5jot}Lr_KA*> zShhWuBZ^7)f2{F;tnvSmXQM^amxO=SLoshAcBrIA#7FFd6Vo#*4Y9nw5zA5CMb0Ws zeW~KAl6PX01&8>`gadTXdz8;O30#aHx{0~ieb|i;Tm0Qg{*z&3~#K0M^o>I%g z*vir#Lg<7&!*d%e?i|&lJkqb!*9_Z$&Z`Q{&y_E|Pk_A2drbhG+Rxlm;aHpTiLpbz zuGa;onT;PicXUm@&Y^2*FA{)*f)`bD9-GjKYOB4qfD@%|xRx@iHPo=Y8k;$Q9y(ka zQ1@8__jUX7V0w8Do)UnnZ9Esz5mA*89pmd90${q`E`hrbJw4i2Uf_X_ddzR*`B7&J z0djG7h(3^6x%~*NdS&g)=qGsLYb=+Ft zi}Y-4>{H71_Yx*;kAz;~-r6z~fJuWf)B!U_2YM-#7y1)H)N&)`D|Bt@j&f1Rd7_gx zxx3N`aQcvV_zI{X%+D`7f7I}4x>nE1Pq=jt@P*LAj6e&{svebC#RHM($UodV*K_~q|UEHX0&Bp%v z>&(fFOmz;#yFP5;Ti$^CdiLZ)6=b0$9#Y~ym2pZIYjd*()wT->%0J78Q_Q>);@$+5 z7{-Ag-D;pQqA|T-a56(Lrm6KVPgqNv^98gi)}BV?-oqfHjKro(-nTo~dN|h=b-Tw6 zhPQYdDw~nfYeUOyGc4G>Pl>|%1i*Lj`3k-K4SMRb{gK2(e;sS3)vW6IN|u`eLAOd} z9-6i;2;YO#R`^F~negV(w6EvH=vkZg_eqBg699&SQxWK(2gVFfP8|4M0V(_7R%N*J z#^xf|V=l5*9yXjX%uSB1K5v7=_v4|Cr$By8p3Qnt=^FQ>nPYrXfvs&n=vz42kn9HC z-OHwRr+#7G^5-%HbL2X>s0<&@UTe>MqdvO+*YCcHxyV#TaC)U@X~CcCJznj*j9KfM)cYgU;>QLZVilZ&yF~Td z%B3rFo;m*_00{Pt&aze8DS3~MEDFU>Rkb4yk!vTRQYOf1xM+1>;HNdyv9y_#7S!6- z{*l1bK}mH}mzZaMHl$~tl<07JNft3*l$mfc3#OQ@jI;8xbj||J{frw@!l>r7t!r1q zM`>>-IzN3|T&-zB;tB+Z1Rm+|JgZTT+B<5dQwENbj4KCgY?*CWEwEX3p`k@HAjd}r z9zNg1v#T2Fq848n*M^IUJ=;^7cDn`xBuLom70JQ+j3LPN@~WYWQ8ElY$Sr^v<6CqI95P6*GbhczVtpXYv*<7k?Q zv5yvjB>*J2dl<8}ZN|=pa^NUIcSrA4UVU9vg6Yt#20XYymc|zV+s7TY^cF9=lkkzR zECCyZzoXLI9+!?5zY>6R`*c|>N9%3%axZZT&SItP^8F1^$_zV}!|FVEu9)Z3?tJr= zxAxI8(;J{G5K~9+Z3Q*iQ;8$z#ssY)xT4M`JlTbD zqv7&0Ul9e7Q|*Q(jx7-e8}#ggaKW%dK}FT5T@5PgQs&UIwUj}{2w$Wlh`S#JIp;hdI7yWYn(Z|FzaJ(mr0Z36z}2`m!g>$_kmyC zO#Qlw1EtP*RHK~zxjN6l1qQYuM*8=uZC=x^Df*qCq_KYJmm;N&NyV#iG7&R*8zMoE zB+)aL)9M_;IevaYE(cROX9~Co-?9>&gQ;<3Z)LGtkoYUQX*Y}J54o0|5br?#lZ*pc zEC}qtE0?MxFj9F9S;KtK-I-qlNZ}I^-=F56?Lw(R*H1PS%GdSQPqxh9JIayUzMM1M z18p1vv#|Wx3_1OwO}OPHI33qr^C?%sj;-$7L~StO!xUsb`r(ir*qh?33d^?*ukNnN z$r6~nKkg0z;8e4Q_(E;hPmVd?Mc&YiRDaEr!Nl+Qb)H}H`sGD%S5yZHKjbHUk^Kq2 z8N+jzhgoQI5fiPUxriBPtK)y0%*3pY=}~CND1KeCu-n_)MxieW`qJ;Vn8mqtQ`ay% z0VFu(gDK*NLy$2b;t^HfxnJ5Ga|cS@?cHB1c{AMWUET+0&scj_e_;^jAQ2=xH7I7& z%Y)rHQsZp&3UFSq@>1`OPsj;ywB|ZDreV4h;OO{BRrnJ5qeT{Rxo!&`yPZwxG}p{a z`I$up2Tewn%6rsjYyo)B*u{*e4mtdJmoy~%ryVa5UoU0c)jFA33c&xN)Mlwyr8aq3l)0m~s+;v`zwWp?rby>|6jbg*bP6g#f5_PWfV~_CN`N z3tVKn^=@LSR)ZT7hMXd)HH{-1F!#hM!x1x$;WCcdz{Pi}&=L@Ls-v)esznSemi|z* za=1EJ=+JK*V}TDPT|dD$q}`aFvii{8)W)0+o<;vu74=FFem}~s9f*~>Sa3g4HXwJ3 zw`w&LuAslWG* zrKV*h!$(xxP8yD&txnp3EAhtMSH?dl-5;d8f*&2t36<~MWRLzXLR4YAz|sFu%r>Rm zfgRuEcRDw>D+?yk)*Y$YeB3=}IOMSb*O6Z}-}Kl&E%bP@Wx$o=6uvz3Gt$JIY0;ju z;#_P2ed#q#mjf`)8vX4+4%=gkKZ1bQBEA@@5+g>LB|iyLcWAE+-q4@kDkR2Ef_yexGZ<0 zd+jTEggMjX3+frGSW?y(+zbjz16y?Es|3Jyys*l4qMh7da@IlvB9iNA1@fd-;ZKzP zHgww@hYaNTsb(sWzI5r1fipbgOu2l0gk~x2+WD?Km8};YjQGncKY_XIe@r64v(MCt9n*8hC!l3{l9H69jLlQKm{sxBVr|I~LS9`wMI)SK`1=M=-Jx|g2cR+cneZHd~2Ju*%( z8CnJ#imA7lh2Iv);A=$sMP7>+NHj4MytT+r!&OnJdvQ;9p!zhz2kW+tIym#Yf}i`2 z`X!S~oMzp`DHd-4_JcmPQ5>JO(q0$7AZ)lhy6{o+yJjkrBZjc!WB8UbSAhC2p78{SHJ%9w$+G+yqD>iz zvkG-n=j_>AJ2^zS8HiuAukD^jykMN8ubz}QKSWw|yEdqp$2P;jffZ zUt6ncU|exQ?fM{bolsvGpV*8r#24^o!zGk0B1;um=Kb|LCB$icXOS##FK zWP+Yg`=XHe=Y;i>(`SzDqp**UEgRm8Jr+F=@|1q6ZWVry>g}Nac^bNpZ)kuY$)dA! zD`!ya@Kp~y(e%@f@Pp)E-p+w;etg&YWyhIx`NT4^dkvL_rgUc}`iEoZizZ>)hn7UQ z5-~i5`~AACbbUjgs5u`Ga`cJ5_BJ;Wbdle)Biejs=C#Rvee(~mX(>T~)3eK^rP=Qy zO-Yj9g_q_!oL5&Ne-wU>`lr(~5n@XAC~4CaZ=U>!aYdt|_fYwDA`uLRW#vpYms`nccFQW_h!%x^Lw4r;BnB5HtW15W8@ zJtNAC+o}YhB6se@k(ikc5Hp*!$&;``J>T^cDZ@7{&xk^W56uRbd|=}Yc5$q&{cxoD zDS`v!;3au4*R^+oi)8)o<;%9LVcdO}tIDG`#%`7V9HHxDIH>Oe)7|4wnVz;#!@^K5 zQL%orKKZhH593dh;QNzDm_io-}3!GG1@ssJljf3)`>8R z5sIn75Z_m##dQL>#M4}n&ic~<`Ji$s|(iz8mrS=NL#IHVsxXi zp)zohn9&gkjQhLoCpPj9Pf8GG7Hj+StTX&E1X6rM{q z&XUe{kpYz|jQ*M8p? zov<`O{a&EFP1^dF3dUy zB>G0~(gixa>W#|@d~BvU{aN$dkUG~bc>V7{y@E^e3N3JrBrjR3$dRJUnEaV_c*<3k zrPn4-SDcp&^~kOh(};uPXKO&~Scu*{>Rea8>H-5)?fcSL*;2*e;^={b*zE)D3DGSX z8kqD!LmG3SCqqQu=tjlYrbPrY#n0ff`^bYtqWJTcgwZ-%FIU6#z&s0Ue4JlZNY{zmO=5L>}wzUd4l2Ce&C<2@t2FFL{) z>uVdU5j(b#TjKl2cH#Ij2JZ(c3*Bxp0cD#D4H4pL+JQpwOwRX)ChCba_Q@m~ruHe; znaxBsezp6~gc5-9gI#$GRif5p*SD<)MFnLK(H+T-Pgx&+^)H;9dcYcK5^JOt&-&`R z=GV*dBO9|_XXC_NqNUyvemF&R5GkT?MFm5%PzC)el;(R;n4>=hx}#UJc#}My={5PY z34SlBXwk95Qy9?&cluy)+r)FU?5qH0^T!a!uWA@m*STLOUyQFr6&agF>F$okZ>2u#Levkd<`k&$vIIv-jf2U@gRZ>&F1 z?8;{GL{4su!?6*0sBfX>S>%d*vwGr`MW58g(Gy%aH?87m?;26i(2yw-z?TSS63(6N zYOZ(tJcC2Q!m)GT6HiWh9#l1sz(snjm34Oeq^bxh^_3s->H)b@ zg2Qd{6lW6^730#uQ+sEy7!e(v@W}P1%gL5lcE+;uM12irH3-3s7pRXNG`9(q zljJwv7+c+6ov{vGH(4s4i5L$f0Fen>K}s|J>g~U$5otYV@3^8L%h z`iNdxzc(N9<7b(~`dT~boy?b_FCJFRiN5Wq^V9!w4uz^5+VJpI)Wr_#4*RTY$DFC^ zqiQ3EI$ApV*=rk`ni^^YHa8(+kK#dF;zEhH>>{{9Tn6Uvv`)k<2L(pR(+!3Cd(g4GXDD)MckT{|6*fh&tGQ(tv88mSY{{@42b;3f(%)B5GIN`pYRW); zGc$vao?6>(O-~_4d`>41=qwf1i8&namZGu=5>Xc!xZiX9L=^t86i)rwFR_11N6K&* zesOb)^Z}L~Yvvj`yGG>;X*{qAl|If#$^7`aVu5 za6JiKYub$aiT2(6ECX+QUIvlaEPGP+h^Hxhiw2f!oFb$ZQe{~%=du2&EVD0k`QT~< zz(SH-xQ^z2BhG0$<^vOhhzg9lPZ^9rL?g;l$)o5`lE2oaU1fMF1-fiFt7$lUn~UkE zsUypfhr^uC`E+f+RBQ7RO-D|Mo^E5q$P+P%O>vK!D6#p`CqHVVEi>dT8(g*_kDH%! z<-!w}`(4UDfs#2a1NlCX#0|bjE+VrI&V1?coX13$dGFx&Dmv8?630S?rzI2G7!@dQ zAuecR>)l~H{-(orqrO}kCNF1y%~A}OKZvovgT1~}mbV}VvQJ#p8!i)IOOXv{&^8QT zqiYb+ZYmmH9&J+ahvvl=8~+>>LG|p8@6K6H;5dlE$&+n#tL5mFWz$)9n5b&N(1%!g zjaWF8ah0U2pS|^jq2t4+T=P}Z)l5yEYjGO0bCa#dW(Q|Jx6g=S^#@`e@#^4b!jSDm z4xaYa;;WsM=NKgR&8Qeo<5NH)c3ACuGk+J@tzbQf5*92!1;!i|PP9Ypie1d4x+qH1 z8Z0c=ER0Z7aD}FOy#oUI-npgIK1kh!VdUJZo0NsG^@tCzswdct$>c^ZRe3 z5C;D&^An@`FyuT;&Uok5CJk{}T>14@^W<61yvLF8ti+BS);0<1>j5SJ%uV7aoSbur ziB?DpnODtU!Qs*pH#MyJvQU~mGuOtV7(h#}JN<$ep?0|JnvM*z}i zRfyY-jH+i=KO|0W5rE@@6q z;20Z{9lFCmDv);4vtc%DwK|7-+dPa_hPNXwEn1vzq_Tfltm>5&xz!D=*t_05wSL=n z9CMsC8hDcxJV*fCTRNmgf`iWIy*`^ZtHw5Wn0@3Qi0^wkK`*G(%p=l^f@7r5QY(7H z3M@$u;)A4D%+8XSgiZ4{O0@DA!}QR>5^kLLJT=5@vR8PjP#8VNW8P*V;%a^l47w)t zBMmQka-X$;?(zJt1EK5RZPuzmj-CmnJhA+?<2t>MkaEMkjQ>Xt_j%<}K zGFwpM$+k(B9}#g5G`T+F+9D%tl4rzok5$;DKziOVLle~UG|}^&Wo(jeXL9C9kp_Ri z>rj!r3E0s*PRj(`tzV>@dn4JA_T36w9!(2KOEShVHW8ekq@;Ql72*ipF>0L^*pY}U ztqHa5;N~2I?3X~<7=-ky$0SO{H+XCHHifDX$nbT8=L-S-6*~;NTQqD6JyA0@An1dg zhVWYG%i33BnW`^N;^>D-%$P0dIFhdq=DjY6`Y3cq8IcScj(FHDmp9M_>KHMRH;lW6 zx1HC$en&->C*A4&gL8bhzdp8$jCZYgCu}cbP@*LxVPgz7ig@tA%F5=QLD#);w;$*dF>`MO`G>2elc>t9tag^*2w(YcM2`>Vb( zBkVht*6pP+8ZXaMtbgz*sIqXVKR;D)8?$Bi4ZS`DeSuM3WTXOTap7n%3FbrMhmG>| zXk#0Z@4nY0eB#upn`Z*K&f+UL2*9S4xeYk+{+nA=RXC2}h2}x18|tEMh~+qZ-Ay9= z5dA7Lgt(JNx7VtqINI?xC%}Dov7Na39tDSz1-P`@`xdw&I39$tHV;Wd#NAwc=}Lmr zb=2{kp%zyd-(1Cbv^io9W7xVGV>5459~I62I{YD$)+1D3Q)^iw3woxX3-OyP0Qk^m)aqX5=6pW+6f6Mt16?BK8(O4EsL4TB+&S1UK-&erHS*!`1fZ%}a2r zIhrxCaf3b*gqP_{0%> zK@A4SePzs7sD7mJCTBoQ8xnGs0RAkP=)tU?5^ODR4)gH&wJ}YG!X&&#x!GPkvTV&1 ztvaGOy$w#W5ge563+isQ+);l01Ri6lXwlEPt_fGTh7T)m-SUc@Jk`Lv(@Yi)PmB+#=spPrM+Sug?&?{ zSvF?l1cLdKnm3znd(ZBsARq#R0)q7iextZl@nY+Qmo=y(6J@p^4GqF;&^_yirLxL#To4urAx9B80bR7nuRnrNW4BRK6^m~CQi3LbE9XzW5{E( zbgNOM^e9VxC^8|~=T9&vcRe2iCa>$jOn%dG0&1{B3mICHx zceY>5F-RLN>QRFSL-@x+WEKXnC4}YN#qZ(z3UGZGagED#)N6Uxp z`l>mr_0yRbhe~?_p8G2D5I1yjI$pE9gP%qhin?}Dp)w*~C4GpbQ6-lt)4Ggl-zkmm z`=&)+HADU(m8S04Ndyd0ZsN3Qznn!2ezU64c35v4zO@fy>T)Ly1Rq8rRf{BmIp9|<-lVA!Pwykl0Jxha0 z$@oF{?T%fcMTo)C;G&zv5h(8{qSys(hCLVI_ycBhu%C}g9I!xTc25}(9cVusV;OLU z`fC}_Nq=k_?YX$x1DlgHtAP!2C``H@eB1{qG=ml0hlXbTCtw786$1kV(XCcULu0OSASfZ^N-dUYq{N*#W@a>piOA%W+Y8CterK^V@- z^4%lEI^VJ_Y0cIp+OcP2=ADJ1Mf*s7l4Y~6n+$ufrKQW}R_)ve>u#(+N+8X4el6Yd zc*-v@FmkF*z8}W>ONsO-iZMO}Qs|17i<22h;JJ)rgXP)I9F;Guw5*4kX(Epd3V&HB zq9PlmAc_Mom#at1r3;0IuQm*e!#rd-@28Z19y3^q*1i6bbCqUtub>1evlzAm3a%b^ zX=s{851XswS0J!Zq?y%h&O+Sn5N!qb_FFYNoB=W`=gpb`Et@uoaa$D`tDQX&5Bkw7Od&^x0j+3FaKC zbR3$0@KU~s&)>hS5542z+;CcUTtWOKvw`2hNfZIuLvwKz(8zco<`)2;WDRqZ?n3{Exh@suGboF zv~}^bQa9o7$jL2QON)n{>8TNAbc$8esPty6WqL<#T`H21#wh3Fz(V8CA@9WK43N&f zy&{9tXe~CQpH_&1i$?k{Lt_~@-re|KV7BO8Zqlw*Gb&@#WHFeVXVEO^m%|aAXi)sC zGbxo(H=fHVM_U#lKd{P+`oy@?i0e2i%j6#RXy#kpIZ9m&@z1<-RP+Oh5SlOOuw1wH z*T-zmMC@~j*DYjYzO7-KFAN>T@fgIkq-0Uy)X2xi{8x*AVbKUdjkRdMga(Ey_-UUe ztQEQ|Kivy42DX;E1P*m9v30VT$_Xja!!*FF%nrk%FjQ!`?W>^8rOtSXW|{!#{T)s# zi`IEPl7v_CX0N0*eH17fD(kZr$6L}B;+*D;$J`}w8btwdA<8acbAj9$+(lQbn%K>I zKob5LaBQC63;|f3o++)-+IBmwXF~+lPOdsa6oRqM^>a(G^gE~@tS`NwEexe=%`&67 zoAWd5Sc|jxyk_D8MTwYTkM)Q<^8!DIwES!Z#MWG!0_6hfNX~PWw{C_B%uO656@hmZ zU(D!s3C>uIp>*tn#hOo#Ej=|{8YaC|^-zAHMGJEmTr)Y~@VX%!!@>X;$n)uUZO3`x z#;XjYiPV$rqk}uIqH#hlV;kMP24G)b&t=IJB$NBw+e3PR*G~47&jclLP|je)OugKy z<1qn9o$;~2G}l2FxTH>14KZ1-8!vfdKRm*B?A&kWNHyABWJ+$Bo7ZCs$kIB!vo!gb z2Mq;N3g20Gc^>!CbwE9Dxh!=d38!=#KAvNPI8*)+=59+Hu&3RC*y(J`pq*MMYgpv+ zk~n%iB|CQT4M!GO5};~WdscV=t+GI*&r8q+*gv4`?K)S&no^fPZPoA8HmvxrQoW&b zUhl11*rB_d_M!F6-1PqGbakkil>7b|Vt8%?Wv!=nwDcp3ac!0iGnU1=oV7z`{v9s} zp5+{sDZd253_sk7Jn}4Y!NTin}f2JhldgQ=IllQA_Wy0KM zm9ynP2ED8g-3Zd@gJl*7ly=$q215jv@|vd9?_ZdTlz2w#>{%;EPN8hZ)~C%vVRLxO`Z+vL@}%~tE9L*9&l~@5yLDXW z*;!>gi;z}6Qv~mi5dfiMn$11i4@H{JoC{I(%oBZL=)CIoLiGv|Lp*PTj{Ht`ExfK4 zkr?11B#v-lQ&%^ec$4}z#yBV!K5UydcpTC&dw4Glh1o9NJ3i~Am`70V9)*^+Nl05; z8mDNVsRx%(_xg>=zS=DD{|qjBi3fr-NV)Rdaf=BVmUORQEVZixLq)VTtK{& zu!&n2!Or3KaU+%4&4_Ab4e69_own^}gG(G7|EbbS72+@RUUv6ua1MXD%-e@T^@xg5 zhkAH4bD%)8vvJT|aZ>|0Z+tR!;Y`BAccb|%ORd~zYaFug-rcWM?4F}X-|kexIlTsT z8>(3c^&-Li)zemAiU+3^+o~ofzoy5$^K+T6M85T^ybe>f*$ZV&$JdOabb=Y&cx%Kl*MHn&&jwx64oIl^@E}-7%vWTooGJEQXiZp)HXI~5+ z_rtQaw+}aG2m7yRrE;s)d4!%q%FvL!?dB=L@?HszDcklbb-0bic7EPepNL+AEv42l z_=cJrL;4r7ClTM@Sg9#xCNAvGg7T{65d}Hz3?FBWa#K^2^n|j={bh?3yxq4B#%4pg zPZZOp^4cK?3-?qU)hKMvKd($EVsi`TD`FYt&b@YXe$#OSW{3qZeQ;631WE_7Dqd|?foeozuo(gfn82`05G<~JX>>br=$mxirX#eXwZtk0cF!ub z=y84Wqgv2>g#KAV~3^G$P4SRxYLZToUaNTV@1LFFTW7z?htC46b z+omY{cuJM%C71ccmR3z>>#ZH7N`br)GuffGH#Sr0U4n=@BcIQgHh*{x`UKmK9U|UF zPvQ0(3_-GE>t!>es5I+(nD5}kw^NyqK|go2bLO-eat+noc&7s1Sj}3tizQ6q`y9q@ z&`5|oCH672AyeuhsadX9EjO16{QXw&$VKqr;12*?nj{Dtx@4eI>uKiL#s= z(^UY=h50h1B;^kfAN_-^EX7=Z`ObGqmS_#~7;h=_dUO}-Fd9EEtB}$EE;W-l$l7x= z>wB9gar<*k+fL1V4~wNKO!>#}nMdz}%Eg!AqmN|`ZJ^*l4_>sC zb=OmI@iB#f_U3GCq1m<9TS%R95p&m!O-OlO&B3X~tr%^Oh8g5a*tkc2Q?Sh~wDa>v zBl``C_rTH<0;5-le8E2wT7zzB3bA30(gXC8>%=~M(p-5S1{&#Gz9!XI>jgp&Xv5}? z@tN_;4hE*9e!uXp}WMKq=R`W7H~hp@AO*Z-btg_FdH3rh%27R&;7w;YMTzAlJXEBBzd5ME zXEm(Lqg?Ma5GUjlpUB(AS^BpAm4-Tfe5qtN%K*&CHB@hZ3bR5WfcxLsVTf@t`J zTgy2Pcb;W?+Eeu9QlxDSIcdL!d`+8NKCgY#IC|wfcBWs@A@*f$P1MGM zW8^ZGF{B}Pb9n>lhhs0F@f2uiaQi%tn2|YN{n>A+J1ns{w9z-(+vUkt5Vr2?oU@MS z4U9{84LhJ@W@(xPUmEfGNykhD&b~0Mjs9gNV-fA;uN?rSMEPtkE2zL$*GGQrN0)gK(dbk+Bp1!a(P z$IT~a%V(OucMA7-I9eSGkC5}&m~yqtvzMD{$G6qsL+rx`JZxZD1Uom!aJk7Bc-@q} zP?SuA0%yuQP@#(-^7~|;?8j7D;~E{<{;^QQ=|;WF!R(N=*+IG9-mz^qPkgELsiM&r zG~xq8Q$dXKyx5?0sJP|BACY_d-^=n07WxDsg?;{cmPdwDhNC~>iNf=Ux*<`I>{Hgh z>}QRdtp4c{6e8A%XxXy(%(}{ZC(Cj0wZC3f5j8+Mse|gz2x5 z(Y4cY9yzyxM4KPzWxKA6PI2g6m#wH}lt`%s-+>EYg{LRbH#GB+Hie9@?DZtamMu?v z;uKL;%Og*E(S9f>cXpAg{U@{v*aIT-Gb*=JAY3Ep$-=|fyk@oOUuGUUK|aG~Wj(NQ z>&7yPUE+SA$SR(5ea1X<3ga{)-sB%Fvq-a5lXafo!lA3QFBl?L5bvv~V8cEx-@7eP zZN8r7gEdVH5#fJK0lxpb`1Ot=%)P`~IaQ~zF?GO%{S2CQFuWWpVE#;>Dide*`^)(q z1$@_bobmzZNOAT~C(Z!Zh7F5_7eQOP@#xf3KKyy;kw){}r(+(p?bO_q?PQqs3QmCA zV-oc}Y;FbWOx&;}3aPP+pFGRnJ7y8Lbn6R4z#EYaAu!)Lca+jxg6dbLt2pNGO5#UZ z^Gm7i+Qu(B_`c#rMtl`*ztm^+rF^)<%{ksEOo4b^;pWGFCh{ubzb8#sBR)cpXOG%Va6?k0^Q_W zg~NH&!v(Ct>gTm?MmNpmuq2Kb80|Is$+3R0_Y91euc^E^?I+kQ#r`@iIwM3^u(ZSs zCc~1`XIQ#)+bg$RaLBAOtaOR^p9*>^RY#tA>!=Gg<(JE5EXEf!UQ57~JkD2y3bi3l zoMTESppMlRg%{QjZ*Cqt(#JKiU$?XkEZl7uIXSeFt22@zEDjM&Hll-f0DtYGKd&!g}$&QLQwOj&iV zo)|27Im5ZIGyiBP*v;Cay8|EGknX0VHF~u^Qm}c1>3&vyb6sq1+_~v`|I?aD^jzw+ z;ocr@z^(A2KGln`;kjLvW`3dy98&~9_XJ|C9IupXd?fHI@J<-xk;!7hdHhiQN=J;& zxs`_)j~LSy<@0X4?dK{n1M_`Zt#h<)VVt8l_R?u+L4f4wR+%q~xpRSs5s`JL`8r2w zlwxL)VKiD{L);T zN0e=iQtCXJB;mDQ9;0GG2{2me3ok*X!_ILxIb=pwPLR6G91xML6jYST($vh-)YPq% z^MvF8B#4${W=^1xs1TT%A_9)2h~Kl{Z=H44S?7Ddb-uIS;k@7JFBgvr5BL4tzx#Jz zzw5ewFa)pD#JDerHD$O^X7JJB>P4W0Ovbs&^rX_QPB|HX@&yX+73Z zbW4f-Tw>G^$9|P_sPEYQxEkzcDcplDbI29!4n~V%W{tqKCeL;Rh!XfuvhlwqhB zDle3r6AKelB=ew$);sFV2~xi%&7)$!M>5xZe~NFGxci*MXN<a4TAgmk(~f*t<_Z@2{DP z($DC>en0sB`O7|_(A=$6gN>HylweLuLaU1bz@HLRaiAwp4L==MI2_=hCXKd>^HycB z+wbqV<(qi@lJcRG3d))EzcXK54PvpUxuYxLJB(^Q6NjAYJdxbW;ShIM_{{#*XhcmGC) zY0pawiad<5cbDfk<>%?Uo6xMCf3A#=nLOb)Y`{H*lNR_ITDEF|f2&bUW(;|sw~foX?w_>ey8;FF0_Oz8woC#3zF zt1qBJGfWk(OhW^+Tu)Hy6ZJyyykS!!xC5K9jM&mMbWWwEuCjZbB)@=jb&k3{^K1)L(4Iq<6aYa&z*%SE9>0M(PL+&EFSVP z;7?IJC#e3J2rj4JUF3Z_KQm>}m#}C&midFig&vBNh#t-xs4iTPjCLgiHPaPrZyl<4+cLJ)Tnd2e}HBO0Ow=bemxj!1he>F4viY^u3+9 zIG-HqlmKnfE1($P|ITtP5EYc`;vE-ZG;Lx3G({(u#uzLMp3#c;RFiCcT&G@vPPk6( z)9wSNbnz-H#B%fCx9&Ejr(z9G6sy?tMhl7=fU1`#7*Y++6?ukrDJKF z?GoKQzJ^}YiO%3Vu$pvth@8G7d69B1m8MQv#F^j^liHJEmLc|}T3^N@qMU$0U=6a8 zFRi$(-M3BQ+ZuN5)_<}!DQhH*DsC;1=rubQ2XGEbOp?Us_{e7K*x5-2jPB1>0@bKH zn%X=XY?SX&yKkn=&K5Hb^C=S7rzx(IjRRwp-nV^-8pXlv)8qoe(JK3{6RCtSe-43^ zrEf?r>p1Pt5f)#xUzqcdElyPSK&01NJ+-945~Zu+oYWD~Z{R|9A@y6*V^;X8LafD^ zB6a|0M<%Pqg^hhYEi(-(3o!GG!T5#v*6*wnpA#hia0!P|s(b6K3no~Foar06qnf@2 z_yTj|JchGRD49Zy^nCCtIrOq+ULGq_v$Pu?SXc3q&s0@g8IQBc<{2@+F)BHiqUd>_KqGq#nZWoM+dlMMmCX^XJR)?0`wf*1G~ zLenLdZyBfp!_RkLg~n$-gA7pbS~@d`^ZgUPe#YF?3)4eE3_5Eja$eZ~2SWmSEHXk4 z>>4p7mSFy1+!sbPa}*^lbT^DaPg^6PQQu*XIOT4o+>h}p8b(h9{RMYCY9mpNw=mXw zYP@%(x}G9#@;B?0p-faO3?lpu{d^9Wwa+fI`Y+7PhwG-1X#I?M)WTf~vzxbgzq&eL zRl!ufp8a(O7mrh#M#>zto`xuGls;N+;i$6emC;w1=v?=eYzseFA)z_ZtI=~4ox61N@~rRH@&E## zP1k9I5=(;}J-4x{T!Rc6bVU)7Ste11kWEhtir=5iXsJ7R?=%_qtkHOOODBAv3;swX z=O!uM{O-@=tuLpXM}0@*0ovs(wT*=@kcCRh1haxA*XaA*YJ=$R=5&)P0XuL9I%=G! z)oA$}t9ii{V*U|R4ZVTB*Q#cP!%yrqeCtyJ;kr$v2L~LqW4*<0+g+WS3AGF>oMJG_ zJ#qvd&%ByN_bal7_3yTLE-$(|fX%BNc&sNFcf>aW`>ODOB(ghBc1~h$p9WTvMz;X$ zBBJoxIdLmd+`-eWzR4Spz9hK|Cr9au<*5U8!VvcPOp-K>;tC=8la-Zti+tm z*+HLbKY`2&m7>aCcMEH1ZE@E9?Jk6-f85{tubZZ4I!Y^V>{dwDfAK>bAOl)$St+(q zs?bf6AY0ROR3+eNy8RfHmDB>b=U$17vz~6D#k15sT}bXR_SnpAp`5SRr=~+`%(^qh zld3MNJwvu7UtxEk`H+kHBFC~;(~DgT!DEPe@+jjrzLPGB*wY8GAD|iB7y1kkh!vC^ zTq%sp7#7C~(brk7E)1Y+(qg%E!k-r=}NitXTN zNNvSlO=eML{8#U#)>yvfNq$@Nf?P*p`!T_;?!*Xkv+PWNkZ|gNWMlL-@Dt1*7J3zE zXtP=9OTN;&VF)4GLa)Gt$WJ`E^g*z-G+@-Us6K@P1G|0JZ-5qNIYx92xaa%6av>)& z$=`d6A`R%BK@=q7M#Gid*)72tkB$}W>U7JrYOB3pWG;aR`X82l{fWEFkfAzSHV^#pc{?6igLiY9iO%&^yp8%r@5rbtKz zXPcU?Oj+LI!V8H!yzrawBsRj^k zd@()hI7jBhuu_*2u1indnVvlBvt%#6pYdqa9Bs!(=1oqiqgpYt-JkT87j z1J)ZH-%x{U#jY3Rm`P{9b#IVvXpMw8>V3@I7RwWOtti_LzX#;(%;VMneiYymWezcaG{D$p*uDodtoJ%Jn45(6MfWBsbaW4PRUx}kbi zp&CoJ<^v?j{SEI{s)b%Ns|pJQy%nu?!W;-74LyCkQWOU$cd>n-Ddf-!cp1KOQw}Jp zPzCczfEl3a@kP2-0EfJRCb4XsIOax1PV#Fnhy7 zy%i^Zf^BpF5DhAD+ePy2qB-j+tkluE3d_@3RY+3w5-JHnQ`S;|V)*Hrkn6n+lD&^i z?^;!`n=KPnc?lb)zL+kx0JmDbEULiXT0t(KTHzD=<@kBg0%6hXrjSOEHAi@Yhi9}j zGFKIDuS7L0Xe}fycum)rt(46p39AYla3nyF4S37|cnNxe(=h<}!QlM5MYDes!(;!B z0%)8cEv&!COL6kg&xxxF#kfasX~zU`;MO4l-|jXEQ2e?K@KzkY#mbU1Vpm@M(Xiq& zs>cbD9$4P{5|Hz-W`hCc;cAW&{LYA_0C;Jm9GP&kg7s{sZGwoR}+qmyU%h9^Gx`odoNt=x)NZ$ z0m#Wk1Nt7d62HcXEW$piuKjIlf8Z3}5F{)jkyEb3d{)|xzS=dp_2T#xJCGZY<=)$m zn9>d#Ae3L#hGFr?hx~Ztg!h}>!Mo`NFa2IV_UGxl;K52RDo1UV%;rx%V-xq@EQz>r z$@10lKRWuFZH(x7PhSYkApBb8T-vGk? z)?RFyx#)R-O3JwEHYIFgLp?66i_t$Fe5YZ&0&(u?``8mLK6760KHvG8ZsTk@`A7KQ z&v(+i!n#$i?!0($``BZ>J@psw)+NoAJr0)dlcJh3Tz7c8vfh(VcGQ!>+iFjQJ=q8` zE;)SjP`lb`=goaXB^^7Q8)*Tj%GO`owP8Zb2?LTw!o?kpCQ&M+zM}rRgoLt=grDD@ zv$`qNJ2S5l-pR=7KTV3C>(x?TM%`l6Uk9;o*IqvvxZYxm{>6r4<5xp`RJOtTQ0d2Z zrEA}}ztDv&OQGdSacUBepi|wFy`zB|$2JlRY?T}oFP00I&5RCptiM_}0v>cm1%({4 zd8`?CHIV>Hu3NioEbndDWPovaD>ISKf3rN*1mx+77>AJ)@N$d}&ZWVjdxu9kJuc|G z1ii6?{D`^r<1K|wj{q%@*cb3aXL%1?S+zvo?V zy(TEe2!GIe$B@p~J`r0Zd~9q<(IGX*J}`F4aiH!?c=Bg*-XBEE&gBlHr>!-9*Ap+0@!;~wG`43+u0Sm+ zL#30JAAY}cA!Ag5Bi5P<8BtmrbCvkl?T?a4DzIRzHbG=qJ(F<|c&FyWvyY{T739Se{?f$uJX9+NG^Cr%W~k|EWp)ruB6T+ML(Wmh?M0an2zU zJ_3%dN!nV_9g8`FEs}aSEo2_ZnaelzdcwyMz5P`$CGTOFe)QTmAR;(Ik^A7w!#hEGat zgTFOA>HOBY&01+r zOo_xH_3JQ)d#v_#@>M0y3)vy~$;?6u8xwvV#l}4~&JqU9u_t=F?;0R(X3l-wY}{NS z(E1yLI}UALBLvWwHGgWZMTpHIy`#)SVq6>Unbo%55bQ1~wI#19+$u|a%86bx?AoSz zm}%j|S7A)oLB-H!&H=asZo7b8Fm-&=Z0yZK$sL2gQVkA}A8obF4IL}kWR#~)b~4F* z<^0n(tv#?_P!)!#$tMOe6Nb4RWx$!0*vVDI=umoOem)}&I{@Zeoa7LcUUz~I+T>q0 zqV5%A7Wq7>0A1}Y&Z#62%e9n%1Y)N{7InE{9r}J4yM~<%Y4SVj-V(@i1)1He3%h=3 z9Xn-o90jun#a|uFn4%$lJ|7lDknah0()6-F1^QUlXO>;ngE?*m)_Q1))|5f3$p|s z6;VQXe|bvOwhEw#z#$kw%t*qGTn?1K2tu$=yGg(eC`&-rX!?aPSzb~Sg)2j+F&XTBq{;q68Zu;f4 z6(iwW$Our0q!PAC;f)L!n*p=hDY;xd;-Qh=stHPR(>yHM;;El>g9)m$*Q5Y2=`_(* zLXN8dIWFMMz`-TvRjKjoo{pyu*5eOP;eFuRwSo023s@IJ=iB&Pgox!a*but7+&%}6 z_EZqd>+5wHH7hV;hGBHJVCG{quRu$HIdcQQxR^tLNTFfaT?g4HKkwk?io@!Q$EPTsb zIn_wXdC6Fn})eerZcF>J{+VT4BAIdsEan%Uv}E)$+rqC<36!O!=4 zQa8>7Yl)Whut2g_dbCT}Mv0&E0|w>T(&w zVmr>abf|NNkyPR`dttCWN^`NOPx*Z1tuVb|+{wcS2cPFS3|UQ$XLF)XJWR}JAAS;P zFb4RBVAHYhH^J8Y1Tw%as*-4V3^;}>HI-PeDx6gLiCszo61kqsUaJbx!Pc@**hp-1 z9~uyDo(%&01Dir^0Ts^g*rxTezg89Artrw}tujD>^W}-wqDU?nFp9@czqprCPySq8QcX-t$f?ZqCfT5^d7H%kJMpw*|jv+KJw^k ze~&yzgcbG+``wq;y8bjzWXcS9xLq2?b+$a=E-y3VnaNDs1!lT;Db{BauKZ3nuPEWG z-fq;yI>y=bG@jwkBR+|@H%}jSEE^z33JYYfGr{+9rT;i0_~qD&)p?IkqHpqgL;QC& znr(b2JCQ%YmUgs~AkM9EV)l&3xep7T2YQdmE+F&JHR#Z#u<3FiiDOK)b(T>KOm~|z zobAYUE_+2v6oKNqScx)VtiPupW!UCApx_1-1biJs7RkL?eE9cufTaIhbWhSb7J)JQ zMoAa>5Vv`KPFayV`jlf&&5HLWaaStJj|T(Q9opg|-zM3*m|$UiuMk)hAXL6X-Xz8L z>AZTjCkslyUDe<6jg(4wJuvt?fei+Sk+M)x3xZTp#6$s(m#3FznLLOvK|!H@6{`xt zZ;LR8#Jf|s<)mdgFAq(C`C)TH=oJYl-Z*JHBtY{-ygqh2+)0|?#w2bHt~-Z+-xLi%tXeV_{CECC)dpY23U2VSH* zn=%rNcozo3wM$N&XL*v%*)IV_;(v~r-v9Vx z*QA$Y+w!d!lzUbc=H`nSP#NyK9=S8hx8T4lf{{QUC;|%8db@7!`WR{6TL<3K|{E!*c zPhER8&equ~qp8ZHdqaKPsNtsLT#39vLEb=k|732fp{>2zUX-nIXQQyT_D>%wHFL@3 z=DyN>krk*M<2w`k9+@OwZ`pG#V^zW4Ds5&MNWk5oOMR8_{IBj7C(mrqArqICPdOZ8 zd`=s4$vMCC@{GwuA@U^OqY;{B_e=yz`TZ%Pp_@Fvg4$^x1qi#P@~(e=SNVvr6pfh74mVi9qvd1Kxp8_ zjrSVWj-=^TpLc)e(@E`s9rj1!t|jtwn0CI8ocpvRJ{~*SrpH!#ed9%eLxW&iR{yO^ z4jk>fMLP%2GY}3IPM2m+Wmxs2!XmKf=YJ&+-gixBsj5059>Khw_I*;e*X>cP%@xWh z?${>FM;&ra(JVlPbfi9?Vg?$XRLt2mzn52)PgL1+hjU6FHNb8p*;m65n7+{_?%ENY=~ zOnl)rGcy0lX`ByX9`HMi)MULEO0ix=KVJ??QFhg-h1H}8d!L<(aRwEl{cuvMh8lE4 zr7@E7eWSNVKR5f{F1}rXt#iPQ?j;8>c;>4M6wseqtty;QZ3;g~vcYXinyLnn>P5@9 zF=G)C3270o20@FeyC8%1mVHkbPuA>6d-dW=cgCUsD-aEKHC4$QI#^46m>jL%LOAL@ z;~OQJe)t6SgDy#>r3kF@@5s_@lWMeq8)q&oRJ$04%69XnpC8 zvS6uhGOBWfStbP4)y?FQYcD+i1|5uR-d*+iY3btbc9oB7SNw;T6NtdrddP3VtUH!| z&>WR7Bs%YK-l2T(-~$zB8>IZ-9X*rMqc^;iV*>$_mz^Z4A?QoJ4`Niu8 z{qJ_o{h0@5HBlp9v>KZ-0~`YZTxV4Qignp#4@??hFTjWTn>iNn8pt&>(_hHd8JOkt zmFc`wL!PE-vqoO^nKqGBleX;2b$_lm)$PxkA_>XgcYB2ZK2s~i0$S$hwBDcgMl&~R z0=m52eGe4)=N-GM@^V(cZD(uZsg4t16L*cVMH%qD=d}J=xg}e>GJF7|hL%1X9)98V ztu*(XUTxdio!H8~TeEJoO8uujH|7|WjedHqEb<8ge0SeK2}l8BX=Y8KyP`;_{I~_( z6j=39`(47@>JBf`1?9M{BQ~$M)B3K~ncKcJf|;AZhq1<-BiCLEIX{Sw zPc$$6STdPbMj0D}*Jh+K7gT)1##3i_@sD=cB|52;7z5JQC|rLFd>$L&l|H{%wUU}$ zkArW`F0F!o*y|18WUn`b-lMqZ*x%N9o!m9mb{e1*Yz|sV)c1$v|KnWhJGo?%N^t8a z6qrR;6nN{~9)fx_Rv+wKmtah5hSvKV;{DFQuc1E7tF3we+&m%dR;E!1Z1DQVoj?vG zQ>I@CO$$$+T&RYE3(=28yjUT~?f!JgXL>8e1dpFoOXXz1k5-as47`WY@Wl^?S;cG* zH(L#gl^^+XDckybd4n?r&R0pF#|mWfS^7~MpGhsr;#+|Aa22_7QlQN2X3U?BAb9m= zzKT&^V|%^Q@!F-n`|aBPd3}LFQ=Zoq>R>3O4%O~qRly58_cMF674Ds7cyd{|(Zz>6 zCT>kR-w#gM(QYXH7+TdcH*5mW>fM&@Pi8;v&0tSosAMpQ_HUPU>4Iinb1$QMBSotU z#7Hk6Dd3DR#|)8uKi%!@kgi==C%jRgtwlpae)mK^_#DJm}fN1j{?kOZQQ0P zwJN=Z*hdy8DmZ^-C3-by>Spn`BCli&=ckQ3F^%ESvgN@H9~3G%j2k3;`8e*X5oTsW zw=M-1rN!d3e5OWDik9RQb&A#_%wAqXT!qY-r?mwgKFQar?!Nc-hnh=Dux0&R`ln2| zqC=Pt;D4e$lO2ydA6f89<+9`S;53X^l9+SOb{s{>(F3u-An!9CPtLhV~ov&f=O*V8n+^g4l3WNsjs0&Ao@Z*9a zIJ5!RQH;m0DuDYqV$~;JA318(z0*P8`a-tTZ%k$;5^I=Oyu2KUYUuL9x_XmRk_7s3 zw9PtgY^`Zpl0N>3IRKM-MW)I{b#RgSQNVEx>{OX>jUk#FV4sh^QcbDM;2o_hcfDi3 zE^(KcUBbcq$8XW!uY5iB>}0utv2Z0!?uFLGdh?XjCzvkbk>qCI-AohGLMTsmD9`+< z!A-k^TgL9VKMpSTJ#;MWk51QP0-Dmm6h8z>Vu7CLAun#*Aj|Pe0R6JbKK(qR`Z^JR0S4@$(|E;}r>J=saM(1601? zq3aK>tlxU=B7L5!n5_?C1R6&gO4gw=gG&66*qbOd%BMrM)zs(kupYaoJfFlqvoZyD zSO07x#Cm_glBH|LXK4~IsR_FWu7-SyasLLuLs?$EOl3L|k8K1vAf*%S$U8y8yy`w< zMg4>_G+wlaGuWcsu}_s6#CNW}o(j8YZ&aY2GhvdlVr?~yROV+n=Cdd10776^hR}ET z$MHnn(xj1bg?XEl<=Oxc+aJw3r+sFc>z@wMFV1rp<=0fj>pS>S!XS!nBjkiYyV7cG z6A~m?nmqMDg8C?0BOQ0GWy&19@i#b}A8eP0XbLJpc9SPC9|oNV5=bwHM@vmjnbRqL zQA|h-v3O{(Gh%)7ySh~c%6-N7g3~%aaL;AEpCzF}0(9VRM|{xqGSr5d;F+8RAkJF} zMkK9GbvuguJr9b%yM%YPzT|fXDQ%a=7&x@l8}t*19yXk&vkYZVfmvE@2!80ReUbE@ zeINkjiczpQOh+Z zSU@Rh=v^NJ{(Zt88J?-uhJ^1+JK-v`0>pT z{VIW4_QWIc(Do|_%-^B@LhLIr7;eo?`|m!Wm6hJSVSPtth%k!2G3mNQZ^3J!ZVZ-f zXk97v{M>XKbm+py)S#)v@A*+M^ZcO?#`;s#ZG-h=!~eLutbG>ETYSuu+?JnCaZCe{ zm#g}1@KS+VP8r@LZXL51a$^wf$#S99Rf-GnQzt+RPslOCn)e}_)Z7VaWA3*@EBu0> z1ECPFPBts5j^jdn@|h4tTU@3_X(f(kAjMf9TwFYl)8|%p%yp`_Wk$U%l%}6$7(044 zHL*?v^m;axdAOm&*-MtY&h>}*^cg~(`{x|3!F}_mGf#`Ar~P8~Dvza*DEREuei91> zhlR2VKYk`MY$-oP^$ET!pvhb4vTwFcI5jAIchr8eB&4N~RtTKaLQQZx<=U|D+e`PC z?$8T}9_p7Q7YdVVNBqRsseo&cT|KFW*?j`yHFRNt`ZdTfdkmgve?hyve46&jQ-B`=0xH-t?oI*5ZP5}3G%)*+4y%N0=8dDLObYr|rpmVmcLX6ldD5O&}R>rjDlOxN9i{ zCEJGW8_mv$GtRjE3fO?MII=_1uu^F$QcY05FQK{qKro3u(qc-%#Z4I*!Hp_2EI_$% z79}Q@=M)n3jNHi^wswdZdryfVXH-;dA$IG#n}ZBM0)!knU1?ECV^e2Y)6~$PIe> zo*hjl>>9SAhcU5tcQfGqr=)pB+|s zvu!svCTAmssov}wzD_m#Co2$ho-*9uxzbg(p`g%xcw#A>9k9#8vNI>u&%1as^u>?e zN!uR33t%^DoNgc)TN7DEc=!N>Ie~1#6Z?h#tjP#?1h*5Lj!@->T_a0fdFmco&1`LJ zaSERNjQB%l&xiJo;M6Lpr2#Joez0$ylD@>8HC~4A$d_X6db?76iU(nd`2#?;(KrhV zzMt~<^u)w;nSn|&HS!u$D1gti?u@#?Jy89_vgw?51Nn+=hnVnzZp$mPn@dR0&}!UT z(D6p8NlDB$7|ZDv-) z3#15z4PTyeh>zf|D*P6@{E9WYn5kynn6$y4+U$f*cd@PSoi~ncskf4do+E_w8f&j4 zIroaD5Osch%TO-3?p@!lm>Kn~J;Je5XB6#3-&@|@OBu#z(oZ~33Yi`Z;Yz-qcEv?P zBf>ws$jOw^rYh-1F{yRzNK9P07X;c~(GKSc@3m=KNVKAlL7Ul8mH7g1uvoW&k8t>y zi9u0ZpBi_9K3Dbp6+d{npD=~k$!1Qv-B?JOPGc9hSRP}R9-$iLRQ(`^-uy(hqLP@@ z9{@S97x^RU=6So`Ly5cp7;!#-{z~!F=z!d#JZI-WH^2F7&BJHcmCXP~nH&HjzZK&u zZJ*&#8Ym(=Kt)8H&S}}1H7hh)& zLw&my`_a^eJyx{7JNipFjoGkQTqE%~RD}M zWi@=ihOl`oRQ+dxO<>R@wY7d`WQ1Jfk{qx37fK0l|_|?V- z=oGkIm`K*69JB>kd23O>bXR%-ak7;>R}J{+xcs=&;ARBR-$j`57^jmIOt( z`N2Ct0oa2$Bd1Qe%!a&e`+A>rNZLTGV4&)|r`%2aD!3;kjNr*O>hMbAA{A6;A%x2x zE_unf83G1X7i@vcA4Q>F=rA^GW$j#pY@Y|vXCOWaMO}{OOx25LiG-z%6IkgVz>#EB zMN@=Y+(;XUzY zK8vy0cx;cg1HiFp0iwm>;>C!D_mjm-&5mI z$p)I(i)Te97!`&x=drP#N`1u8*V(PUeT4gAqAdlrC-)|#bn(NAIt@i4dBcvoZ4n?S z*6ZQKf{ROdNC7Pd2SeQShVA?a_w!ljLQ3m-(|8Vv{LOaDiTyn9L>1*b%HGN`?*9@p zR{P{wEH4@B4_?v(vd<^hfF;|{VuA=MO|;qzRK{Shyk9xKl4zmAM?TToVl3Jw zU3NgMb)!k9OYl(lmBO~`v-dlal+d27>bM$z7A3V5NnsEU&fLxpQf=o*j@@mX0cwZR z?&hCbyMt}kp5Vz)Eb;xjTMRejk!9Y;>)vOEJ%@STr{$O`#gqyicGzGf(S<*MULVT) z16PlBe=#;>+9dt5EhHLk;e2C!M?#Uq;7y6e*RXF@lgCTCAD2NgX%|(z+m9TnfW9yO zY>0(o11B10#a!vud#ehqf|*luWfk4iFa9rAe<=RC^xwzl*GT*yPs*>6_%#y0;>5oM z&0kC5*HZYk6n-s*UpbRP>#tb&D;EBWg}-9quUPmi7XF{G!hf}0;pJ-oM|oGmGOlP< z0sdy~vKeSP3b#`6KxyhnL+uiFfudktohu)o0Ca>u6n_2Tui^RiQ2bg7zaqu2`1osA z__b;NN)`T>5~Nio0iaU;D=Hrshrz=Y!YLmr@%hs9=}}^JGGCio8$Z~we4ey0#fRmQ zXT4`RMm`B&lD_EEPzr}>)uxD|1gdvbnjm!HEIN@5p}tNY#goH2g6L&7)ygvieQ$1k zh_~J^>)7(~jPC*GGrqq2*DKsk1}m&-*L!f3(E@8c(# zKCZWYQs^SluK2&GD;Qao77pme9W-X zA*s6geD%+Z^6OZ0;Wf$w^_#_R0ckJ2CeoRiiO-g034@BSkp{Xm`RLm-6$Tu;+cTrj z?Qq_t_8mRE)F%p%V|kB0Xx+6Y{9HLQKucLB0NocK6j|jJ4Q_;IE$auXv=H-SB8m_0 z80Hu`_yvsFE9RSEdhCTmi3I4}p`~M|1*Z)TMvSyVvQs=|( z0ImOVO@MlL!{Zj8-uSs|@D^>lsN{xDPL^+w?-7`{M(0rF`9&7vj9;MomrUozL+{I| z2-bHpgOIx`6cBnX4)%L~m#aoM-Q?!;cw2U&8n=4Z%3=`RTV+@%5R$A$@2bWx}LL*IFm@Zq_{ zoljz$(ib4UHAS7zRtj4eo%-&V{gGeO#&-9sa~pfE_wa44JA@sKqz3&-Qw&vz%SjJts|ee0o-v>Z_8C_az{1r8~^sZQw2r%p_f2Hl>DFWDYhUFy%;$ zQ*5h?X{Gd0(>2MB@ZE_KekHGKXs#bkNRm2|soE(hggxoGtFBM|)TH9QLzC&5w70&m zT5dll-5ogmhtMY!PcY?5i~)h&PhcOucqktap=HNFXfX<$9}G^qXV`(d4e zyah5U++(n?Yh_x#-L;FWciR;_6`|D$S5_t{*ll z?-zwQsGlBzySzA@s^(Q?G5MJ@hwzCEVvS|N9l}wxloupaeUgksB+q()F0l4Z zeEYS-RXcl*+$_BPH)1Rq5K%g?bpQvQR0eH&enuvYG%KFlIk}W?wYe&d>mqT+&!UUk zMl0qb)m>P5%PL9F=6`Gnf5o)YlI(JCLn^%>=^rQB#P7YTixy_zI#$bVg4;$I@JS6g zpl65aBrCUpORE>v%@Wq4v%KEd_8IC)Y?CVHM);?~^uA*B_|S#-4C3$Qgk!$X2oL<+ zM*8r&@o>8Y_ND`=+VO|eE(DZZfKGWb`zx~MWtl(SQsqZ1k>Unw(YJa5?iTCx%|Er0 zu{GmQjd2^~1J)bo;RLqE+*6Q|);{(R&Ap5|C0-=_iuf^Vz_AHyV2-eQvydt)=V#!qNkB)crZH5r^_UGJas(m+FcT5l%|u6J~nm6H&T(P5Tj;MrOeE@2@6G@BchxdYjKFJ1;j77ACQfVXYSJj-OFv zoAeGIHUX<&;bpy2iZRfAD8V+H#Wd(8`1E2fY;1BfBJ2S~8kdPIZS5=qb5}qqsA9S5%62AlKdVl-~ zBjChyynAPT$JCepd{Kls9Mx-ZNJS@9Ha9mUX$Ji3Jf!$~O_`-w^_hIC=tCx8Uh%{eUUK`&5{8{P zJPYeQVgq;WZr`|R0CSRP4ZCbVHCWAn$Fm1?NsB>!=};gc5>z zG}{)A8QnIffd>s(*sB$Rx!h}h;q;n3j2wBe1TD*l%m`-c- z`ovmsf8TEUjUPuJCFIi+8cgfvQISU1O9ym-{vpgIBQx^2YdG{WAn|BVBP`W^)2h;J zcmqMVeE?(=lK!;RH*DGEzND{((z0y{$bB$=nV@XFqoLB^PPRLq0Ag>vp1`wC5E5UW z=AuUQBifjR5WG^T;|21k`lj$1U}6jdSHea}pNA@+LMBWLmIoOy!+Ni<37q%}a&tfq zXeHOeKJUlUPGCupq)z1lq)<(*6&%KvueH4{JiF-e{lv}e#TJTlKWjmd)nV!X!|K%X z*7~e(4o^jMsxb@l`OkpMwh@1@>YhEP{8r7_7zVGG8z330t3kQgmB!D)0)$lw$RZES zd=+VsnjnFKnKb|o0X{E|s+M%O9uKerNt9wSh>?sM>_Fbt_dWQi1DHefIk;EsT^L0n zd8VyR?fM~)Y-5CnFQul+={hCVCi5Xp+TVr}ui#)16}AX@+W-M_;SSg-bcX?R!FA6QW{e37mw zQ2e1otgEKP>5^;78Jiup^!{-hJ-nw3=#*{Mw0`eIIP&TGS6n->>FKn z9!<+R71m&KFOiMhi zA}vI)a?}CO{+m>GmsA47blevXI?M=AzTX#v7D;S>M|`jGm@q1{%)w`ToG|dCJ6d)< zP^Pzs)H~f!SuS%e@cN35^{8haO0i?go*oPAD%$YGFSs}%VvkFE&P->J=I|lYbQIaq zvbpG_qp0k%z|nnz$t-UTwKQ zfILPpuDnaKnPl_robnl)pBMx^KcwT7edw4?kN?$px!)u}pf*SLMt=13O^*M`ol6=c zS(>&|EIAFl#B28NX6jj?2J1W{Uh`KvSMW{KclIO=H%dQ@zWbxg<=_-U-k|q0vPBWj z%R}zUPYknX0ZPtn}TXibXJ&bVEbKWB4s($g8DPP6ztM z#k24HB6kvr)PTjP2x9e&fCE^>u7ExwgYdE>P+!z;a0#?M49f#llzw1}Ii8i0Hq`l3zCv_Du85jYAj)6u03 z!iPP6EOH%Ddg=6qNHZGcKJcDnLsN4ECAg<5NurYUWR9@O?W?DK(WIxwI|sKK_*Re; zC0^e#h`BU)jxJ;_&_6+1O5HsaGGhvKnlnAVECiofKXsKI8nnoUa25v27TLoFQF%lX zU;=?z(AfCsFFMBPzVx7csK0@X$9P2s8#Ma2-H==|BUV)E%*~ubglW9m8Uo*Z&i}=w z7hj=+UpMW&;%IBkq3HjySS4gEiQYpcsGp0g3J+%{??MPo2&A(#ioK8J6B2`Kb_FEf zB=+cVdQ1?=;sPIz540^C9)tnx-0NhzP^3+E(;*?urqAei7hb-kwEkX&?-Jmn?(Bmw zwWnfJo)@bjBIOS9t?3T=3-pQ1Oxz>m!Q~axmXve6vZNib>c(!KL>Ip?##mopW5$0X z>@L?I2IRs@!#BK!e&ig8+|m>XDtPdt3}{QhE-Sk$@MBI+%=g0ld?Ir)7hV#&$zW*+ zLhe_g6h)JC3tF+D&u@UtDBZfouaT=cX{~{A=5rrutpS8;yaQg*+#MKmKIrMLq;38b z+vjzTfLX5C_3?f4j;;2@ywb@kwCV*%&jmSq`t!%!NJF{)?}i0^J0m!hYf05kCD*{P z-FaofPGiV|^ix&6^GxQAua`j??^|RBro^H0nmG^FWwP~oD>d{AuzZXuvgeoz)H-KCUdwKsH?5wJ`{o z6C-%<>*4~sw2Ebu3UZ0G1Xa*K4}QVHn;>R&HItA6J>9&|Z^5aui;H8=Qs_%jp^JX$ znND?n2;`T+i9#l+9vfQJfVs1hV+F$eC1r$SPVjMYxNUX2cAn$-#}Xc zszSIU-gx=Qa7e|fHhM{FK4_1w-xZcM6!2X5Nkb@xf@a?32x6v zmcv0ifi?-!CvRk?(uX_;0bo)5Zi3B;D@KzWAq#N?*wXVSDluhNi6z6*dsk!l*qRAS z!IbJt*n-+;yy+4BGuUH8r*r2Z=Jv)F=@&iYn~&bLw+>s3PuGXc)#pU4j7I`ixL+D} z$Sz38t*Z(+*#2wL_EyJW-n;lHP| ziDVEQMWVp4w2qQKYai>l@S>Jgg$Lo~IDj4RPqPN&c-*843wweaI{K`O)Tzv4qJB#< z<719`U3w$=NdAP`7yeQs)W9x)g*xuPyEy%m*in1J<YgqPwALL7AJhVf+VVL zphTEJvV5(y5~>wlxp&2m=I!&llqH-yT*}WOz=FzD@KtRW*!Lvutj` zv}YVjgxK6Fe)Gih+})O6fYg61Cbx05_g{xt|2Kl({%d}fHLLVrv-6*#eENTaivvQ> zQNjOI<@oD__n$dDt9`$w=ilLU{ci-~?PJ+1W^TZvxF@S#?#V-GP-e5y}E8)sXB4HKg=98vAX-cU$Bb6XIlH!oL%$y)GK`>_#Fhx?J?|$AtzCC>3v5);8 z?_TddzWpu#QV)mwzJ~KUuk-v38%rzCSBWm7D}r^R+%)c-a zTleN8?PBc4q982AWWbeH+ef2&aVDZ9)3i}hOd=x%^G!U!q%22?VoEv*4)Pb6fN@)^ ze$xkqt1*5KJ~g!x4l5t&)c+xVl8cBzllIT*vGawPE%M9Vp*k=2lW~RJ#a$fr-jF*fU#XXXR z01Z&vv#}ywD1GND_q$qT1;>d~gQ0VrBfi?bV5s`nB45Z#Ut+A4yVk(q@qBENvOAWs zcEQmQe|Xex042isxF=ERJCZJ0FUl(Au9TGwwrnC5fbDw$a$z`;u zW@lhI+F~Z+1ee2l9Bh#)!~{8E7A}z_kL0n-U;=l~fN0#=0)nbpgN=Y^jdPtW=}T~= ze=4rwH6O}%bsV-tj}Tp`U+6X@z+Ys)d73pcmDQ{*2Lu5(`%M-}BNPT7s{^IiqqmXD z(4NfAI8urpn;se#Vc?cMEzv5qO-wG#pLw9rpJBJd?fZ0*IIfFL=_f7v#JS zpQxSlYHG3=>hN8Dou*vy6ZJJL_u&S_uRJ;9{N&}*K82}(naJiacZbkTlRd2f?X+d2 zetWSzh6R9?wqgmkH-xrsu)PR;xOd&)3GtLP{T`xqPB-DF`k@0-gU0$|y&r~yFO0^R zRex^k0UH`1EqHUWC&VZemR@@7PhN_hjN#RX==GoF)pL6a(lOGcx3ZPyosy|kdJpI= zb2|Z1GR>)FZnJTq20akIGg;1w35sZ^*D&kuUCYPq3876_n{|bVOijSQT4cV@gXbKs z;<~sxuXSV0n>lcw*`fORZ=_SsN9Oqs-(pwqEZGqyR1tZMsxu382_45oPOU4g0C0`q zIyMLdjdnn)G_~C-i@}=nS=LCl2@q&nz2nJuD9)E4ddjE6Vr$w`vR`&>e3NTY^~-=^ z-^V9WlP*%XMmRLDCfXV|@_uC{;JWkD9NN*cG$K0(LGX`FvxUm;a;7A;G?k&`a?u!K zjDgnOBb1A{@Uq1XBuwdy693Qoh)a8q--RACNY*!m%uD zdNlT~>YzM=;4T5R!cy!GF#64U5LX+eprmEuRy_22Q@jde$PUHl$0LFiD2gaR^+~}Mo3cAgrd1uXlc`RJj$>L(~D;xrZ&IlGal8FtqD;JEKq1ic} zaYu(RV-5Hpp-;)`J$KG9ant?H5Bz7l=#UdO?3clCNBcEO^$MJ}%3YlBA1HKe{*{)+E~kkD zM*K{Q{tFwkf!9CbqO zJuk?IPr1?>hH&DoMu|T%6AqC)l8}r%xAcn$Fmyh3m>fB=V1jToW_+k_8WE$v#&8jo zdAr-9pf&DHjA3_kJ#WPmKrz&{;2RpnfpeN7EunV~E99gxJ2Xe&|zn1B+biuGse zoq1KfE$N8YupM2R)ro|QbXxC8@r|!$`8n;p!Eccn8u3+sggkQevGaOAT1P37&DYOJ zu_d`-92=aM7hP1tRmIvgD`BJo*>ib3cVF(5;+Te6s%p1pQYZcZF~y=KPcY7fhF8E{ zy2omN#>+kxJ3u9~hMh?`YZM&H*PqV$xhWI*+KXY=sOlB0`H3RxVw%yED)FB%Yx=i{ ziAi~VY!AOXa1`DdFs{Xgxk#eY8$kku2+|5I!n1rN+wPY1MoFz%Y~&7{20xi0v+_(W zn+9$1d_U(o)M$O9e707nUPiuC6;z+_1X?z00XHF&XT&j9Y1SPs3sN`Y`iT`>RMry4 zznkYb@i8P4<>NCVGQq~Y9wo)oGx2w?dwjFfauLRY%WxO70;!{78@mD+^^I&aT<-D_XysdpgTi-*wxhunQ#e8w?_{O}7xz%-&+S0NM zD{j*}gMSa^SpB-Q`;ZTU&*NU){dmZ(4a2`)`!Zv-wewm`o<10u6-JVKLxJ zFvPs_3*MfSY`Sb+Uic=(aVf@6&$_bmPJ{=zun#`M7q560cmXQP@zLL8j9G@5`eEj> zlJiz{5T?NryfLADvBIV-!L);N?>FxoXkn0?n0*UIu}J94OxVZe9+%lpm!^JqH_WN_ z8!rb7j?_F&_%kQaj!`pQ8rb}Z%eA1ZVn2q>g!DQ?%sFBrgq90=OWgWt)^7I@Zcl^= z;4ZLaTt!rraY<8>EQ?)Q?u|s{xQ?_CK6+QD8Dt_ZVt01wSG_HNaBEX{{xBsINQOrZ zR7&g!;_EhU>4-|dyHK+WD)&ona)f1hBlC~Uql^O!jk@1t?G znI%4a0KY{^Yt(!6N(K=@KNV?&5|tzk#BglM`9GNfcXN-1uk1*9M7JD{Jo@q9jA{N$ z)BHy;)aCWi(wx%By*A~m^U#TEn&`rZfKkn%ta&XA4s%>ue{3xKO0nTbfcWn7VSxru!Nq>af(;_3cP>>d9*OaZrm(&znbTjC>&?y6o7`7 z^Z2^*$&NYq*FzC!mY|d!bZZ~Xk%WT-)pQT6XkSK1XbN~hn#RDIHBKRg3L67C*N6F+>x zu3DyU3g&Hj^O_w(i~RvVk0BTaq5$)(h@(qKY41+x7?FZT#d~P|zRio!905IlsbRE% z?=K}Z1P^fhOsiKA5q^|KT2tn^zHx~xsd)MtA0U`SUnZeSkC3UeInnI8GL0*4pxRk3 zeJR6l)+((JW(C+CjGBFz->rS$J4Dp9d(GH113~mlaeB{fh~i6`Q!53|#fq&15or() z$$d;Ntz}TF`P-l_D|M=XC6-*P5)$)RY zON`8}n%YY#pU;OBMG{_3)`!4H>HJ?CMcqVWf49QyoX88l%g%*4w0u%8W zd4TXZG0*u<)ch*y<5~AvS?#C}^G{|q_0~t5s4?Yfq3&Hpz?eaB%$y&nbHBD!_ueu( z-vqcuSjV1jduRb`h+pw5n5fv%@d;()$B~5xTX_{3I9&ko>ea85i zkD9M2zpLf&?);%7*;wAe3a>Oe3wsUZ9aDai$sB=!wFhU}S2)!`Nev>n}5El<71?2jlNT6Vsme>joKwbgTdL}@^J4&^UQ)YrduG;V;a zX1~Bk8D#nlCW4Qs9Bo+5t7UFJbf?ieRtN*(*E;H{i3F_rBbo@>X6di{hq9j2$`#+o z(5UlFO}smc`(wewTw*d#qpql*mS*kNG<4M~iktr@N*3w6#yy!5IuXf?5JyFhvU6q% zgE`y$sz+*?Yo{r%U)MH?hj^b$JosdGU0@R_YZXFDmR5fU2<cG;s2m=}eidf|=e_OVxRB3zNC%*cSsw@&hUX$Wk zV_r{XO{Ya5Nph+?N?Fvf2I>Y}+S9uYn>iE2+t{f!5VqSxWbmzu+AFdW$BYcXjOK{A zFk=KVDRW|u{#{MPUp%|U9A>aAttL7ubsrM$+$crjy9McOn(nPKFFS2NT~BFLD^`B- zrsP9uncru;(^T7Qgmb_cOlvqe`h?w2Nf&V`lWVYnrBQ#}U}0Pzm;I1oS+11>ihL-x0?2=+ga z({XB6FOsn}_dLC+FMK@K=O8VG$DZoG2lJucKIu#E6B$NB1>)bg_UNf_V2v@VDQfJO>TwYclRRg|5R@3KjmY%PVUR6C&!y=LW ze`wCltzKK#6iama`$zvCfxmnN)`i(ROr86e*_rmbBuhX`U=DB+pT5jMm|oft@%Arb zHfsH~u)+W5PF$zo%#(K#X9iYP`)>bX_f?t49LwX=l0=ZPBB^fc<}s$a++-FdTGEUX z?ysqw36)nsp_O?C7yLF3uvGh{c4WhX{1`Nt-0^}KQ0n6xM%|ciyOhnS3|_ZnsyJ&Y z0~kq#ch>-(u)s%p2yitxrXuFsS~K5l0CKh?aX^n~=^8PxS797?nE18$-<(3U_0LnNZ*L-2P*)egT_ow&b9XD(3?1Ktf|gvZ0Y$sL~zryX|4Ak z|9sTcXJMD3dy{%0YN*Eu5BJ^5)2{ zDWBTB={F<}uZ#_n59xam_}diLCnyWOhiV%3)ij(wd7}NPEx&U8bo9A@XC3n|8q0qV z?D~JI-U?z_imtnva;dVXiH28%NwRzLUhUB59#{Gy$*RMbE_s}*sCa(740Kd2{11AV zrz^UGvjAM^W>!9H>0HeEu4bfIUQz!1a(&tQZP?}4-_v>lDn@p?~iD_G33j@ceUGE zYnJKp%vDOGxzF8yEs1sCy4G$ITP_|@KD9f9BB?clRDS}F$AKcq+C_Z#81a$HT)wLX zw4u>d0KRp>S-(|}_jGK!+U5=g-E1XUs1)>U!|KUXS$dx2iXh%Tsf|o0y55f!a3`$% zmZ|;m{rxtJ($2U%Lqfp(@BY6`pT_b}XWPgY5Jucy)VJRt|1I~!x_o9fkg*zY? zNPfIs*}+^hQ&=hszpLF~jv!<>dD7Z>IkOeA3ET)9CnX}aXyW)bpv`Hr zyxi_&@P6Zlj~S+0@YNVyw>OgLV8*IvhN;OCdM^**ZG!(b%h8>3x%ii7qP>p2kIkw% zC)}c|DQI?A*YVXBm;5VE7>Db1fLNbbeXIdPsbwz*j5~kaB{fQd1R>LiHGwVSPo=T~ zWw8xX0)ooXu>b`~n{c8*NdknM)Th5H5E61H0D}V<#JbRN2Fo^54aFj7NfymQB1&q9Xv-}Dv6dR6RMlR5psUp3p&5VU zG^hT0@7e{{c(?M1*p(A;_^HO#m{e_zagSM3mOjzG48T^LpJWp zO0tpSupW{{-8&7(-leTIS?C*U?STo?G`#xF4HFmH@8=wEe2%RNcQJbc&lrMn&FA|$ z4o63u{ObI~w=bc?V+`L9R-84`ElN5V5+%N!@wE=5&~{gCSvL;()L<`RXG*$d9&$kc z)E>XmQJXot9iQ};v`KXg-#G?kN!w;tUX9H0PRj?zVF{{j(y8Q#7|kthpV2$;w^+?O zY@3VKBivK!eJ99P?R7~n^m^(%63$S-$Lw7~xRXoO8Im;7p9Joaq{O6M`Y!TV#uOm^WO#}D`CP({9# zxjLaMbt5a8o)dlW#Wl;1bvF;`y(%5GZh$%(hmq@}nyb0Ex^a~{5Jfn|)-EaguFy1z z|I$*SZPdwsos?#`Z5ZF%!rW)GckO_}SGti*!OO-THMM{-d-1sBd$QenzM;#id~^M__b#Wl$h(wZURvNS5GSsU_JWZ6dAWdoJw z$guL=f=FqgNL}!z@uFm{B^rP`r>7vb%+rYNr|Rna>0r>gdv0$AmJO^Htb1z?7H64B zDnjq!Za(|QO-XQUaF5JfIb?0ZV1CQ8Qe>cqYm2(?oV{@T>##|nJ`<{-IV4gQI*@TO zYY$z#QPoeBzyM4Xay*b_u&af4SUxZYO5oFB^XXE3A4z&*+)wyw4$EI#8u?fDO7+rkRj&WW0r9W~ZMu?_D-1u&F>*?&29C-Q4QpwC#FDyG0f^Ec6S^p@9?7 zT@LYn5|MB0=+kxicgztHZhznrI>LVB1ILmXq9r$hweW{`$yy|&dD3%vR4Zw>IYpxd zz`LdqgYp{qX-xGv%`Qo&$TRn-JakN{;VT~*Pfu#zAT@flJcFwwZkK1ambU+l&IQGj zU$QY~qgQx-X<*kD7mASwLz{bD+0~sFcl(<**NF{#d}OuKB&_7P+3fLXr>;6)Xu9bl z`OkjL!q8AQr@^P6e}tsc^76IZhC*u9hszzW z6CS6ZYO-Q*Wta=DF01G>BO=4w$Gz!x-fvV5=R! zh$qU_JZ|Lp>sh4Wo0YHRyvoxsnuH}x_OgfA{m7##oy+%TS3x#AKL8= z-fzG$*YI;_D}R3d=iP?8jUv7sKWo2OeysC~gCE$KGC$x3`}GU?;r(BQRpO(Dhre)) z8K)ytqS6DJ`kiOfr&}ApzWj@NsDA~-Wt-_W`GYZvamT7ID-GIa;b~_swa6?(M{i6B zpSoyADV+%`_Ua9=?x+p-ZS^DTX#CDu96|8>?1S0tsMD9!P=FMk&F%_0-%lP|B2Rq3 z*3rDTKSGH`exx+clmef*d8M?A1FvvD=s!(4QPNVaqcTRPT9;!yxrG?~n7SOgQp5{xg!xDLi-Qtc%P#8ppFVEEZwv-sXz8u`42-zdq#4abDFbj{M>R3@L#>+zdZS_ zhO-Rn9j1#}2%aUJ%lX>RTX_$kdJ8ikoKR9AV^8$}>I-m1;B$LrfiIhs%w|xKL_Rm( ztI&?q8qsKlH+=?DbGx&=()&xjiG74n`9HZ@$>JL?N#1m6%SXyaQb`cJ_MVcet~gA* z$>bEAA}2dFSux*aP9%ap0sq!Nq>@Y>vi&WvtK03K%;Uh?Ak~W32MLQte>zO_8#W{$EhQq@YY{2osa+jZTPGx3hr=fxjLte)qV|;@9B)W6sk_xF3zmr+XDz*u zpDef->fI_@9j++cwj>QD)Q>4MD7wVo>*doq=MKxxlO5m2`EpObK#BQkK}B;%p57?0 zyo~fpWuv)YD@SsnW1$F416Sz|xy>A-7VMNnX04r_X40VqjKp$&MA|DmDfMOQLjti6 zN!v}_v@|T!h_6hSY>8u?DU<`AY=z?(Grd5ko+-a01a5P;t*P5*y>#IgrNuM#bX5|T zJi2(px2cHSb^ISyb;mDQFY?C`#bctwyi|}m(0ogWV&o3+PWO92cI7j zp$I-eB{Nr9SOpm?MA8`@;t_{&dON9=$<+l!Sew+xU@cmoGTXekl%zJT;W^++L*sA4 zi#z!`xLz%M-en29RA>ueXw#66c~_2TiOGtu)_<~%Ba^vPe10=mLJbt3EWfGky-zH>QGlH=C+7>DT6&j(k8Zq z0_$&U*V+XXljhalyG7Nx1CJCk+f6m(#FpH{^`v%)rXoPz6)T`7!0n6XAO_e*vjQ7= z3kdLz2rd#XNqdtfB%;n_yTkHRBi9B=&LiqRi!o=cI1iDhS9}lL z33?InJkNi{*2l-1y!TV@F6Z!LT^D&7j{d0keee&QOh;;Bu3L_m_%v|hf?le#kZDLA zl@8&A9I|^NNMJ0I4*u)-Z>o(!3vm{TQ|v=Ev0;9*UQZ4kb1KfVG(=s1<~^P1zToEU z>FO08=9;jS>Ur67@r{Xd=#+cE)sbjJBb*hVWwNjO*v9VQ0CceVs2Exh?2sE zxp7#!>HxvMK^`s{SLyV&Q6Iz|?_bg~a28UN+yU^+x{Z&scQ}6Z%`)6LQe1b`sN2Zg z(yeG}MftA7rvJ-B_X!%s(ztDe+l;@AGDV3!YMxv_d*_!rq-o>lx?6V~=kRM){f)}} zkhl%R76;;30ca~uoE%M!2O?_x1A?1;?;4g6C%2ZS3!4~iFnxuFoU*2eM+=P}s&*@M zYowkBXNuj(AT4cSY3jNvLJ3FVVm*^5o`LOdA+nW@e622^G^zYxx3_RE3qa$O=Tgs z79PnNHvnChl(u#g4Q4I4rYzBVPss!)#RgUyB$d~=)3Uog25n9f(FF!>tBt}Z6;8OL zmmSw$-7lWn^M)Q};ulZ>4G{Yj&ec^&F=dy{X1mPAXD=V$=E(cZhz=6Z;JVk20lT(N za^!_%K((2B#8(vx>yPFXFXwC{kBC^yYl9&tHsOGN zhQqhPlfN|nGn<@T#r{wwntYPmERKm?cq`#&Wr^Kx{CBk$=Kf~LOo4$MHD8q{q-AG63W>Otm>TppadW7O>ic0n16W6n4^nepm-cz#8L-!8W68MT9w zzN^vI=PMyV{6XCx5F=xYQIbO`2aB(;6`q2L2JZBd(9MF76=VxFoVj3UHDX zr>LUtF)9}=^h$prA*MH;7Bs~mr7JCv6yZoKmtDA$U9Xk8RNi>`Q2(;aDY9D_)!yZ# zQ&{n{nGdH3uVgg8qaCx(hDSvWaJ%35r^;Q9r+Mj&#FroK_&sBVWa!}*(qn{o9u|f3 zh-36ovC-2z$$im zjl|A767$I+db2iw0@YsKZl0jcI)Cu>h;1_(ht6j(#bDBoW6- zUJ>?71Cz+?n}8q0b7^2IKz45rAvoU%WA4Nu_3?|{ZB`{OhLTHib1ZA1Sr?xUvO0Rs z2R$pRLrdPejFiC(-@JNm5|c#L>PuVN9jOJtbaG>NU@%XN$5v0T4Mnq}wH_J_1KrfL=HUOs?n5%#_+ggTzG)yU$TgOI8 zIRf=mj#F)(5Y$$u-&TVU?o@40yU5DzxQh~&wR1~djI}&X&fFHeEaJG6amsucFD5f> zDQ#$Uz=ic}gz8-G}_sqdP!~`@O)P1-Zkk%yQ8nxY+mF4 z*rl1Dlt^3awOU>Pmkt(rKg!$vY2Di4X>KXYLE78G--|akL6Z*B_})g>SlNSH(sc*X*5B1MgLMmVnIoTjOFH^Aos7?E(f;iXAql^V??PLcz3-O594^ioBGiuq41gVfS7`hRwtQFGW^p;X==c-VV8)YyNS9;b zX7s4FGYfF3{Y&&3K2^8 zw%mi>vn$ldrxin9H4}O1ubD=s4d@0&;ZVbHZGL}kea?J2!$L@+F>IHcvWl1?+4Ou7 z`YQ;Gf=C=l!>3!Kk4hM=AicO@=DNnc+AAELxD9gSHN1+`yisD8bEG^rv?3T?EO!|h z*Zhz~W|al!8Gdqn@dS; z=|2*V6SvMp>R}3ANE!sxRNSc7+x?r_3*-Ted6Pq%6)dTU6Jp~mBkgb~YpFyceLvrR zwc7&{us7^HKQH@aYL>XTn4T>{W6U_0yk3<|iS#D3vnNBGEIb=;E@Te8j$*Io5@{>* z8f(W@9B%3TMh|JJAlHxJB#*Ui>ST!@HgA;bK8Vv*1WQgM{LH^x!`~7zS))jnzvaM^ zAoTEJ?L+RLtH#~oq1LELp*vW6Zm2l-e$z1OsN=Eos~uj*cv0wwG^^~eN0lh!4rsq? zFp>lHw}pugtsRqF%3cgK>c7B`3ynIN8|B#6s9Z|BDoKJEhoz}BsgLHDWAtOK=776P zGGTq$w90HSA{4##F{L?E7`yY1+nQ_q!t5+pCRD#ZTaTgdu~_}E#g{37T~fxFu=35V zk4$E$e9rJ@x!fM5GTI>OEoiuM%xJ!5EJ&rV>V<5v*(=Fu;iF@nR<262vkb@|8mgQg z?Cwmq@u2(}VlLK|XD^%?hw^vIOWoP#b3>0#MwNZoVbSB1S!oiS5fW6_AK2KJ80b2FY(9lw z>*U$Vu@a7R-uZzggY$OV6{Z{;DCCdkJ+tuV1DQSzQlt9>NQt}zw8PgQtrt>vBiwp- z|DKdVVUde}k5S}U(O$<xn@424Q5DZmX%*G*_$j&=@mC1(qJElj^tb zR2au@8)-voP4+c9ZfMq%;@DInLe~%GJy>nhTg9GySeNnphN|+VI8CgmVki^;nk3ChI>1oX1 z^iLPZAfj|3>7E@}-ecOnhi-b;O>sW&tq7o+i`%sBc@+e8`Xfp&WBQ(>CP@YnEdrQ67XVKO@X_&{;Vi*D57*YL}yg0V=k>Bb>bDk%=!S zLDYThV(fA%td!We$V@^?oO*|$0}ZxYzZh!)rjDB_yDRS% zys%7HtP_F~S64=C&Pu65-8O5DKp=`nwQ+NXLlk!P`=6WI{kamA6ZsEi+N^i(>-#fw zSau{V``J&*;bY)ili5J6v$$gW!y>aOJdaa;mf}&65eB%IMD>B}9oImr4^bo&%H{;Yb#BGX|OiEDmmDMXwDjVYfo zum*>S(FKQY0n3-Ee3&^#Y-RqzJ0SO0osx)K!0zo_PAf@o4lb)KCft&2D;+25qx~UL z&EETVrg**a7L~Q+O%rFiJDoNn5BKTE;50N48V3FbmyJF6*jC4(s$Hm+oRgDhDRm2Bfx zF%8)a%*FnXUQj71nP7}6o(AotD$bV8<+h!~M=rKPnLB-Y1!=ZsF%-8L(Q)oxlcI(` z@k*;~;)Ff?_#hXWdpgQ-v_l3j4XA9Q&*!(2^>Db{kiI9L6qGUYHoY%m;KRIZgEGrb zyIpE3`&~}ejynj`R&@jIm!A|Mxo44&IyAk7zjKM7qz8MjtIZ-ksDDoHpEYuXAYg!+ zFhm{x)z6-HtVj6GRTR+eW(BV*IyOTQei>>&8^x|hHlj0ZK4vrS2kcpO;xJV|)GJr) zGON1(A~}-(0mJn_o*4i4_51H?|Jw`rf2qa8f6q4i_a>eHM`8rkC3S0F#IE7EL6GeC z?`lB#VEI>kHVqJUAacI08-Ep{(5d+WuExq!{>EJH1L$#w5R{z4AFO`)i$+8Jul14m zkFJ3*EB+&ev7l?&*bnI*gU&Z{`M3-n!P7);tGF7e2^}XG-7wOABOu&xnO#a5?k$CX z@}~5x`EF{VUoRM)QtM3@M%_bRk=|w)s)}yi8pD`i(CyGlJi2Yw7rX~-l&MlY7ftz5Y`pNsobHz$C)h1+tlD|F(I);~_*Q-wM4jmA;ox2pKF4{WpZ zOM*E)rFgIQTv08lbk5Q;JKDM9Ma+w!`n0U%!4TToyA$J$Z{ks382@nC@%F^JnG@8DuYEb7*)};D!Rzg0HZj!OCLDR@D*5@S7ah&7y?Qn*|oxpFW?v^l@|l=@Z@= zw(p{@ZS1I&8ns%1=Vn&Z*wo#hLr31vXeHSAM5p9ja|qWG&qp9qV<(SY>)QULYyITj zp(76VYT&pd_=8_Ba%>#8F&Ore4=?sV6&iO_tUX_0wk<$0CIG}Jn9KV#!t+L4EJed_ zmq!u9u?7j*S>ej^JK@_%n(b}p_IQB|;4MPTQeLe)ox>d<5hw|46(}e!MfUxr`WOY zYJ2?~A4D?JFrh!TXWtD)g`!T))Mpyb>z;z&avpTd0&IS_s_kS+VQ;?zX1A^wm(}S4d9Ku;7!rhI0Q&bDSYYUuT_=7^S(gCn zOiy^-`{PTg=2F;aSPVv&B!KPVc21${d#Fit!;F}8E&C0|rRW@Xw9AVV{f9lhI6sek zZgjJvs6|GvJR%$u>0V5*tm+zoQ1~+)rS3RtJq03AZ<42uAs~9Gsn$sOC6h)!!5UFJw$hc6 zD2CMn>0cb?yg8PJg!>{!eONNcsYxC_@UH(^*JtZ*0{W3E?S7gv#&fU{`ux*OfzC57 zK3g^C^)hjQwQ`gFer-PYPq9^JiC-Qd$<%;AbfiQwn6I8*>%0^4VO{ET1x%v#fNlV} z+}G$@;yVYgPE#^(6#lvR39YN=cIHvcU2^btF256;XNKhJguTtF5A%h0#bP!@MfRTA zpv&Lg_+!Z^IypvCFV_{JWrb^(hECuR?lq^qKezw>iuSm;dta*scmvNjS+t>h-@857G}xAP zmFM<{hD)(;EpjY%a`f7{FtknW*XI)PgI_nBne@FYyh2M#Iagh^zGERR?cJZndTKzP z=PHja57J9d5lC-8#t0|}muVuH|4=DL!|_!!qOXKb>5qy_{W@?%bb}i0Aropw- zcaE=fXeI4!-h~%VvO8OeI~04XXT3O9`IPL;TS72+Ij<|?4)rDz?QF2H%e{_~X13Pw zF60F*2HkY(WW{igUET@Hkab?(@+nF9d8VE}MGARaTc0wwYueJ&p>5TM0l!@t9MZoq z;uEh?cepR%naoQkr)IVig$;}@4-7xyb*fY2k4P)6hl&HjyuCQsR(T~W?%=>rgS#B_ zTtCu#PJ6ye@`#{62GmEF)+$r+%j%C*?fpGp5Q)%k&mE7$*Zsc9OuP^8?vd`4(dol@ zG6&~T1Qe^yH|uGQdB~49h8E284?oejPieo8Y_N(nnqd zNFlLSd-zFo;%<~@E<2wLr3Ax?PYv5vL-O@bJa#PC@GkYUi;;KRrQgEeV6xfzMa2kz zW%k%l8&;Frh#OGNJMVRd;}hSHx2(Wp*%Y+U032dYu^4x$={MI5i+K~+f1qjSMP(kD z*QG&K5Iz^fV;Om6qj0W^8nD!NH0}tm(p+cbaARAw>@lAe5Pf@qH`|XvdrFpe4L2z~ zrT&I&%8S`Z*=@Fu^StLwOwj93pGSN@tfPhsW4H8PXZDHBH`Fu_L}*LRr$!DIu={Pd ze)flVmmMjxah>_v832Al;)b^1POSQdd-s&tW44ihSy9vOO(UFX*6XwJ1J6r>><;9$ zMy@&!eRx`d_zZ(QGWRpOB>D3+D%0mk>8&G073kc6O3HFRa8jb=cX zNGrUey@*vl3&w>C^}nlaIsZFvuk>oPjp|^(m`dwb9tUlAEG)SP9;z-X3D!BaX;~*- zVJFHf-{qFHaKWg9dy)r6`;&dyrJS%?=vLi>8s0}1zcl*%R3Gnc0^U%| z*?io7UqM2%l%Sn zV$C2$G?jf+*od%xH&Ig{>?y5&-`I=bbSvF`4G9)46;2KzzGx3g{bI- z2RivCr2)j>+3_Q5o_kHI1Z!8{)n<&+D;%P1pX}<23PeSf`tmMr(962_S?xCzbGu?k z)%nuV8ULewbPZ>BICh)2AKc%;obsWuR;wJSLL0u{wP|pFyPnY43zr?y+FA{a_hz!< zlEk9McBmv@NZE<#21i|&^t3-=NVxvh4MoA3)Af(?4e|x?!aL80z@@J}0$S4s^Z*s6mn1VK6G?RUJ)wpkS@I7)gIDSnx0GDF5ZYPBUSW!p3q_e; zA8ERSRlq#Y?l*A0)yC$U^X`>0Q>r5J(zlX7lw*b9El~T|8yA86?^F4KDf?eGEo-Iv z|IkmR`&oTg^KM|&^<91;Td^~c8V}#8zi#0^Yf%e-0=J&wl9k2qwA7x`nW!iROSGem zVfy5`dN4>QZvV{wokNYG6nqB%yo8-(@d91pFOTna{}@8S(%u->H4M0qX=yeB)+Ya7?DF);Y(S6HIbfM_{j|v5zXjqwLf`O?DEd9L>E_C*5CRX1nq1 za*rOMk8Dp6t=GHal5aF0m<8VLiF0sPiB;S-2uoJre{kp>-ZNiUHg?xED ztL^9DXwRHVO^s_gS84|7F?pC@M&SO$+s%0;!=u5MMZbyX7kJtM1zGewwrU^*4>^?zFSbeFD=xsO;a7+>U^P_hPppGh?vT``=iWbcMsM zTV+jUr#pA{rh=W`W34{*h7iOjuHH`h_1sqEPQoP#e_GK(F)-l2i&q!j7Wn`I|vM)2X9x*&WkJsnC|AEgr-|y#q&-wo5aE@b~$K!s# zE!XSinkID3`)S$*ZX5aQa*aUtyz(ZWud8|@DObf+Y^0pfJg$|uN+dsNs@z#^Q>kT) z6__vxYX+JX)6F!oq#jRDZqsOO=xz^BiqfvAnOHPEWtG2&aSutRL`+dSiMW zT1Q_IgMaWTTPBPjU41h3@#uRa=I>7OYx=n9s*hYpbNJ`81OPj)s=eym*g0X=Ih?RF z-Ra?+ywmBe=brHbuLPEi)EFXNv8^hA{#)NQ#Y2&uT%oceE_HE_Bq*uQ*x9TL(TYd?1=4qq zdp{fYR%n=uWQB~au_}>d#TQZ(YHMl}z+kd3`w%EAdYk*Iy zy$X*Cs8XFF*1qaR%I7-Y+Z4N(9Kw4H7sE2@&h=ZLEes2Gxr+G5nc>?%A;{DL=%q-Y+%@AbbVu2)o}xgw<=mDcx8EmUJ)!~ zIoKpOK7Sq8dh#fn5yP`88uojfS~zufbk}0d#W-|O)u=bjPNK5@6GgOdqn~IOVY`ms zLQvDwca-HnC1Yzp=AK`gt~i#flZlq3^W@(gzQcFTj&i;Q*A^M+ znf=dfrqbTrlfCHBj(>-_?~(6ZIm@Y4$@T-RN?c)uQH*>(=sC(ph-5WN@%8RnsMDnl zEEJ0qXt)#Dj%w&=Y-{Q)*y;8|gb6mcVJ=MxKEDR#16q1v>P_tgW^-E^a{QUjP}!_A zA}q+IA6X%fE!}o>7W<<9BlX&Wa-G8Uy1mHi@aLDmrhS&?;|SktVcC2xkWO{WahQ-8 z16)ca4%?R_D3p!2uO_w2T69E3bNxt*lm1Vvol^O3oQ;&etDC&|FEGc z0oi6P!Jz_ELe19!-f}6dbad+ z#kEAsZ+My8VCmfCdYgJl&iF*Vnef!@;Sf88{PU54 zUY0!YL{cFKA~(9c+8O!Dw9IF}Iq##dUP6BHYI|oXp{T}3LNR#b@c zPnaDwO~FOaF;q;&ZfR~4m-N>F+MH?XIPLnSddMdy0+Urjo%V&p+Dd)1W>yO>jNs+U zG$XW2%cv`TeKA6)M5EEd2M3TnPaJ6;S>1XrfqswUO5KqbTZOE})2a5mFT&ZrS^IYw z#Hykr|NOz3Iqx>y_B;;5IfikrLIe%$zv*_F$GsBFeXgw%Ee}ZAjGSYMBqsT(?Ihy@ z_gyy4xi(=Icl3+iUdawj-O zDbaEO^mdL-?1OO(m0>)tqHh@XL?!fEYR)*$KoD)@QRd(TpBjc$hryEitpt~YT+h6` z${woo(>r@|;{{#Xkl3tRfv8`#bSmwg_M6bxp+k9Qz_$OX_<1thtkmcCHvH4q0^2pa zI&TdwxdggVg6DO<_WxVjZ^&9Cxg{F%B*ZHcEyR2Z_?lVLdqw5Txrs@jrlnM+9fa!x zv6gKVEyT;^cF`O}*>YAg4*P7?d=z5UI@)A~4$Tj~Ti>|N%VNpY1YrU51R3;Xjf;txrz`3S6o;yD70q^4 zq%E%BIM8D|jH|H?m~)JPURiM<;oE;4j^w%P-_f2$Zpa)VAnJZDZalB#PDH4*lSy+t zoEC~zqi)}8YqoF05eh^Q!qaqF9`+rYzO+<-vaVwCmNNmzD|23#9c`9T^rBp!d&=D8%GOhh(Y5O+Q~!WoF;iD0{xU=_b3#yJ^nDCFMkM`TH$O-wbhqnxDRI9zYM|B$J8c9;>g4 z`H|4I6kYq25SRPro+@{^QTj@mU+PKP?|w2) z&7-xSZ#w>F<-}cRBYesvMN7Wkz7Gxs8LB2DT0@sNLjL18g}ma+@;Oo_yzozJE|8>@ ze%G38nt2+UdY_PWn=(4S;zbTP(Uqx^r<<-MQkv~$Rh;s*;i|SoO}%cZso?vE#c>51 zN<~(qR@Lv^<=#G&PyS4P)9^qpy+^Oq_{WV5#HPiJvWpl*tZXuT|bs zA(;wGsd>P;(PK=pcMn;wiqdj3WSZ(*v(2A|C~bw?y*RLp9wsQZGlvS2S>D|f!Kevh z*wGoLdz8^yAgTitgg6ehB&gMWY{5;e?_PY^n$_Em$<`Xrp}$?&jVA1L+IsJGc&X)L~|RLjH5Fn4qPTw_$6N>9r?_HN`$gToi=uMPaU#qs-P+D|L0 zAF1oYZQ_<`<>n6-lfDKvb(VNlR_d`S=j%~zmU%t0VlW7>eY1n56UPpvbvoT4uj7>0 z%~_!rpM1;qC~=u}^wrtN)qe1UYIg7-*3`Zb##Gm_@?g_nKPdnHm9xGVTb3lw55qX- zJ$A$hOD9z)6eWCQlCis0z`z5fJ9q@Q#`qbDBvo_oye>-2HyNok^w7QApXADo0CFl z2QG~|uO9I`1)TA*@`X$9rteM^B{4VCm~CUa(Y1GyFbG|DB_&h@pzkVZ-M29L=#mS~bsDmTHL$MEA! zgx?E29j}8h*Lj^jkAIc7IP-~;S7p=fF{GwdNR;kc-&$h_C%=1M#T(jId#P<>6 zX3aSTx-1{)tOP~RMYE{P&obKNP^_H!h-RUXZ}5*>F-3!=K6*d(viq{CeK8S-WjT@M z4sN%KSL*nNex3$(%R^tsBO;!Y^g}MAI49_&adP(~mhKN%r}+4*G}d>L2UDZh zhD1}xecGR_(oMU=#ZZ5hucW1DxR=YPP}TeHvA##Ivsqkq#+P7Uu^KI21xe|o_Y5a4 z6_@~od%O2h(3yre^}9Y+)lzEmeWeb4hOISu=5K~9K=HnKv>@6c(>{+DK!PU52-2DC z`rg_v^A-}IraHOU_^ATYmLhp`EP#|q9i0tVl~^5cswl!Z#az)0^EW>`KMSQ@bw5Ya zS0y^@sPt$QoV>bW`py$7F5%Bz-BS}S%g>S-ustVH_1gcCjFzr7QVF@y4(!4@n|Ht8S^o8R8AIGN}t95@yNz?LTuXU+Z&AdbaD7YKE?joi5;z;qZZ4dTR zm9G|}%>xsZ$mJa%EtQuHL~4h7>Ns6?^bgMQAQa!#U$Je7u_M~>23~a{D_dhWBBESbSVjDqTk6+f!#% zv{SkDr=Ag_mxI26Qfs0~x^GqbRlSUSr7X)|aX&mRFwFVs&Uc&@`GdT97lA5G8HHxZ zh2f|82|**?a_#^@y9`bd*OH4$*$8r|=P9yH<*6ByAS0Jb^i3vzsyBVe$br=#yOVGD z+5TCxj9%9m-t6O~BcQf_U^W_>r)((d;^Io;OjO&{jEcNP!TRkct1PsFPrbta1*D6~ z0D_&SBj62Cr_3ceUoDE^??#zm(J2X%Dt!~!e9NL_(){Z90K&TH(t>6MLtTt54}YsW z5wCB~1G(w16-X(PH7A5+xg)%6S&jt9grckmM;gv2q$Y%_gY{f{p_NG)N_EYad8KPhgH)wZsjF=Yj1!xE!?6(onb_zvD~6&l z$w{;Y0r|KQ2n%P#@HwGoSDXgDIzJ@{@R(KhV+Re^UmHhy{^jtlc8#99LcQuo!`O}{ z{>{QDo~dO9Ul{Ct<~{Q8ZL?E~d+We8r)ddu>g_~N@bBbS@mh@08Q zcT0-6?zk$`BE*MaQgy^|5t*|!%TeS* zrDA((G>AxRjYv0{D)kNdWv%6WsY*9R*N!H;uHp)Tyc{f07LOxZmHu3ql?%LUlu7ao zZxQ?jNJVoKY!XQBt0q<1+<#^qtOu9PgG!b!nNf=DPrm`(h=G%7YEUU=>k~6>%6{H| z92s+5YiPkpmW-?znC5Yc431*8WKW|s%*NT_NRQ|*JZ}TeG41l|W}Pk|gOQ6rhWwkG z*F$}>hJjz zWfN~~TTyA7t!tuLMq9F@A4wL!p$xmfBlrE%RIbrN``g}vyeAhsRuJF~0~HJ!H z@6y9yiO>c^YU`SvOMJSLmU=_=B{FF~D5#(ObMKw41XLu~=tSHb<-w^{`f%0%(f0lS zo<)Q5|7KL#|5LSG^iy!%7#353b_Me3NC2%`++8^d<|jRB>0pk>R+*r$MzIi0m+M?y zzVJVufNDi)BdaN0tp)v-Og~Cj;%=UHLC-f+4(``Wkp$i&O7pW^*%#;=p-39mrV=`HO@}{eY@vn6L^H(AQ{Y5V)}*8@BJ?LOX=-Of}chZM#MoW135B-F^HAnHah> z_r=|rum=JB%k2x9pdd$Zm7u|*3@1j5qoq@O$B#~Z$7`_UXtGl~`Evb)%S{tn9mnVv ziIDyH!j}}Sd^t0+-`cLE<5Ssn=8sIL=pb9}<)d&s6B%CX7CeE*tZ z-2N;Ui!c-zUA1pw$-F2>Msu;Ei)hkTb$wir;VM4&7 zhb&1z*f7KGrYt1XO>`JBKXi57U$*x&O0kCu@BNj${hhog-+NzyzZ$OcQ0nnHeglH;X^Ulx?Ulq^$x5Bw~dpOv-#QnS%I7*fAvr;IMCO}^^F+; zLMU>-%q4)2HZ0-BQ{+J#^fNSJ9%iQO=AY!bR>(1lvLd~w9@W)w&9}GbGOQX?*BVU? z-iHa+bABNRGShK&DQxK&?Ls=LKNXLhH|2vP2%3ofTvgoRY<_7wbrNtjQzrVX3geqU z)s}2cIuCe<^R;Vg9@|x1mt@jofV%`m)JFpW+G3i*7+!lF*1*_%y+|ZL8ptX~!|oqY zH_m?#pf`_V#cOxt5CPatHC!jsuDv~*SpSA&ne9PKZVGtWC8+7h)?kvm_Y+gTWzq6Z zMN7b;lbcYyMHPY|KJv5c<>IXo(#(Gxdj(i2?Y&K8uvF5Z%)%A?gJbiM{UMMc`T6o- z9q^Q{>g31*gSa>GPQkQ%;hU{EAhZNX{XBaqm6(Hx@K5$GhyTYBFby2SotIfECOw`18SMswVm?_+Ls@prhF{%~}5ipuE& zRjGbEEW2=6Y`mnQZNrkn3M`FZzIqkd;2nNBoQCsO%x0T-UUp|!dkz$^Q> znWETE4mrq9we^Fz6pX5B2MVYIH<(*ag`z<8aBn>-J^(FS`(t%~2vfu;Os@L~w+Ido zj*O}RQn*5gt{n6nspDTCIk{XJb{u^)#zyiN(btWLg{RmM+Tf73562ZX{0SOV2;^+F z(4^tRcbQ(DwT`HiIbwl2j^)=RbB>G_MtD^Ew*SYGx}gt^Vs7`s&qx@hXx52Oq9o>Q zo5zaB{3h;>g+`iY3c{(VYWVnbSQQx9X7q7cmKz)t2?AxCcbKyc42vX*QQz^qlpT2- z%R2>aALLFO<|Pz{BizH0m1gCpAEqX$F7O+w0~SbwCa=*2%8%pARE|G#wj=uCmFNd7 zPf}uHw3;53?FJx2i-B6%q%b|jUMIu95JwQeKAb+Ir7s!Ef|zDHHn+Yu6zdLG$e-u) z`u5wQs~s1)(AC^2${|L$_!?xR)ctDCrHkk6r9l7QgJ);F3YLo9>;L-H$+!JYFoKF6 z2pn;pCCOtuj;Z`RQjF#BT>ed+873LN7+KM4Nv``Kdm1->dR0~!eVoB#br!hFa!^Sv zMS0wB9P@a4Db#c#8~ex(RdVYB^V4%rVAFmiQ~6de*)YTm+%pq1wfR}Tr3oD#<*vSV@V*>y#2|o>ka^es+m(q)YfsY~tMjPyArBk_e;|tI(#~dG*>q*y z7(+Lp+B?&j=$k-q`BJX!Y*qVs&O^|Joa5#ut;ZL0pcf?6{ZTWAg?Mc|zsZTdz`8TD z20tn|R?Xs}jqMw)&F;xu966e!9a@lr=Dz4;>Vq{i)MoSv-cgFG)s@B$RqH5W-w!yo zA|by~#F>h|C7M`)LYm}mU3<6dBx&rwD|=#vgO6#E7$U;j{G9LgC1h2AAGP~oqCe5I z)lJY5rO|uD-^fav<5dZn6V#3^bH1$~NiT%>wy!~CE;f(scG_6tT=*TJ_&X)DU#?3- zQrVCyR36=erX~7qCk{{r$51dDmHqDSjEa z!}y|EpsRaa8;dw|WtEFjruY8$GX5wiroOqg!p{P@cczf5xl&pF7aZ zGcjhJ z_%XITy*i3Nh7;LtfZeE6nN?(|y=lgTHN(z~>C5sLRsz+1I{RA9AKSIFu}IjN7V;xY z0Zo8+ACt|pmc%g^s=dRjQ4{ONh#J)K?gFt^QIDJ7+1IN2%T0sn2L|_C5zVLCGpfs$ zwaF_IHwX!4rY1oVujZcnBOz9@q^hOalYlU|smk@7 zOY|#~c>K|msa^Qd3~Lru2ESn8UWU5q0;-aUjpyORD*H`)gB7@%r^dnDj+2@@$974b zL_AU}rvjcuNRn&uA~#|A2S$GTT3z~ZN?)TG1+z%ni!fC-W)jGjOru1+AcM<)t@b?2 zp0pWHt!<%WQNCf@4x0AV@|4}p)7?QqwaeR%KY&UAKB7BZYzlaWVrQC_F!e;I4)FuQr}pblc{L0cAt3QfBDwx5AX`~0LVLarbq z5+`YxK>lYNF7Ua%2asID90pE{>-d>=(~GG=(_i@oa#0VxwL)urf*tTRqsG(jfuyo-h@OQ$yv znSbF)(~t9nzk~vk51zc-~`q{3O|4fMPr1AVuEkh$rIIKAdngO=F&JX?14rO*6oZ6AaC*x&tLY6x&S$0;sCQ0iNm|5W^i;8PNos6U*73gm;kelzvUO9= zeej;6uR;ZubKv}$DxxR1IFZkJt5?@0k06QDp+l~cswU9XIo^aoR7fB*q&j|=1&iOUqbNemE zLP2-KN`z-;XT`~9l^=oK7%xBX9xe|o;Qs(f73iHi{lnQ8Zq_*pX0=*3Fukzh#X#gd zt{@YVTH0CY3K3o^P#&wpK>WzQ(Q2ZvF3gJ(#PSRb?MG!!g?hx40mZZ?bs)^Tl?AH~ z0+A5}U3LufSx00BC8nDXyDlK+-tY2fVIE`$j{@VdsDr<8AkejSwX6Xk^3_-;_HAeasTdbI1QRm+DuF z2djQ!o|X28cG)D&gG~XH>jG2;*w{MBiXjR>(3VWTwPp#=^(w?`U)aU5BU!|EY)wWx z{0@yc21*!$Ln}VbK992fh~hpV$q2Kg^Y=Pq7#BuhDyS0BY)yCV468v7X@}$afa-kYAUN}R`Rh?87WAny#p zkhoVDk2k-jF4A>AJLSzNZH)54_ZANNj}*w7W~P05X2bD*`nPkabMbibfHL5u+1b_o z--(*DF zxKXJWWG=J1z~kw}3blqHN6~ko&Eck|hnWXNezc-z`7(7qBl{z=E~s*dzfQEF;+FT- zPuth^u+hqMf@~4yQKb}hI5|`c$gDxd+-e@HW9!tdNWj|4eZd;Yq__vtell;~jmLG$ zxv#wjlZ70Ihz3rs_ytKBM+SJvw{j-1p_w3L%Z#R+?$*k(wbR7)7Q%J))@>&M|*B+|5x0&&_K=yi7t_ynSkE*n0{%) zUifZbHA9-G@5qofRNJVNf)1i>O8OkccyA_9H9ze3w^G;>Ee_SrO zRG;X`%4n%w&CULgBaJb^=p4m6!f+yO%x759%HrwNKYrGqkHdKaL^rGW z;8Bx|jUUezc@O!WOUPtY7xFkEs0x{|q7uZ>g&eCEGLB0nzH^E5TQnytEXj#D%SZNa z8oKN9*Ct)Dcm0{F_DVqd+?41by)bz~0vA{3&Dj?zd(81mF=&HX(Z-CTVMqVQK1g1x z({#+p3!Hc;4I!Rv9o>~pqiXK=;dByeL2PoD-HYAflVG35M~iambCbbM?W(Fp4vU~x zyUXF9{7!D$Hnem$wTy;D1)DPWvZ_enM=pQ4wtMNbUSR@x2|Isy(g1otvtRN4VX;=3 z8>fSBF08>Xa?kB0LA!mlX;7dAHP`1V`O@hR!^`m(E1`(Tyi$wu)9)`Q`11&1whl|6 zciK&$pU!u@hnfV|Krsn2+J!_3h5V0Grt<-imH#-z@Um(x^QPkQ*o5+Glr2_^qd=G< zG&x5KC|R}Jr%q2hFEJ}lxcq;z_%;CX0x9!kf()q^_|_+TTviZRT7skDbxG@5@ng7m zn<{8X=@i`cPfH7PFsu?`T7qa-tF8m8823+i7caYB7{eQQe+<7ad+$5v!f@Y|=4xJ- zDEn#~VVv#2Vh7fb=G>du)c2t8jj%Q7h0g_1-D!e~X?Uezh0c;y@EQ9pBNvCTlSoV| zyk9>_y796VVOlv?7I}~=H?e*y$0ZBthN6g4M{X>Pg%5W?wZ(OU*=nW zu@e-o*`E2nF`i@7i@Aq`^lT;v_m{4aS3%jm)4ZFIGvYK*gqM>ptE5>>CDKG@I=?lZ zpSFN~$dze8BE$K!c*pH;J!okhQ-xxonyZNKf8}FdwU{$8ZDmwb(M%BaFUkYZS&i(Q zWH7sawXmt{^H6~0D1*g2q_qao!+jakw+JfswGRse+cVrBW9y-HKkVnLn-YM;Q9uU# zWB&(K->U5dt}uCjXnM(O*2UV8dnKA<3`DNYEI-SnL+g9JsF_LbU4HhCxDf{K^>`n( zPxaCMpQ0v?O)Y7)00!&SB9GpQ%05K6aDs7;Mnc2=vn&@y4YyMzppa z66=Bc_u4)18v_FyXZbqbX&(Cby0H{eOH4m3>A`f>j^;S?~4s6$mgY$ex$lZU+#tr&^bXcKBDY zd7A+#_STqQr*SQhD3O`M=>QMPbyzDP!YCBzX4Y=@>+JU@p9jH*Vyi-kTxhbs5q&P{ zNSR5zEa`WCqUZOgm7F65mK6n!Rd5qrk&wzVQ%ea&GUPk4xTaZ4PO|9l)2>e4VMtAT z?G{T?kY5l_%|F9`FMYbDX?;ogf_2{jtNnFNnwZcj&0|{s%O2@EcmN2H;C206yD>aK zW?xIed6obeB4EAbD#ZMj7%5*9apEYJF*jAVyDPwTt88{DHw9=UZXvw}{^PKA@t?EA zOqhP(IaG=i{yQ`D@Epx9@(m3oBX*yx`sL!UKQZaEqKxe)!_s%PJTrS4Te3n+Mgn;j z!ioK~0-Nmq6YCZ3r7tQI)1B?E38ut*>Q=eM1Ibxlx*4hk%FuosS7yefFueNBR z>T)URCo(~wZC#!;pHY6eS~IAQQJ%7^Z3x8bq0sWY`b%5J!@XUPRIfZ9L5CjLbTgn_ zPUthA+7BzBP{s&Mj_t(yYh|xFT8+%WP2ln+=?Wi14$lwXC9_Y|`nH>w);Pj}=0zjc z1xQ4BmPjw>L8fGbC2B3TXW*T-pG`W_nn_6KVzdUwJ^dY5?R2S5sg+k0niyCun2Jp! zdbdg`K>1?p>@St44f} zuRud2epoq@S4%}zst}Ug`{=UfvX`y;drCUx4V#ar(#d0$1qP0+7NQ1WV?lQdVe()(ErY%^(WjJjx`+R`GQXD(SuN7 zb*Z}BaSc-e$tX5ds0Rf$a$H93;vkk z4Y;Q49>Fkcu{Agr$l=VKS5c5V_vZ2$G_>z(PqYpy2NS)?<@9pdzWS>Br)2yqoA>jj zujUPE9XDrfbN0Lh3X%3BM`zigfZ%~N`vibw{?U(w_6|>m(KbnkPqzZZmuC5znX)89 zN%VzgAmHFCkhf07W>f@dx%{<2rZ1+2OpEPIrDN)7g#ySc`ocHd7n!un?+riW!x#`5 zmRgKTg8m&w;_1MIlW(3n`v=fknySNGbvFPOkB3$CZG3M2{!89#U~i^cEP8?vQ#@~g-bGmz6I z__ES8IoRY(Hdmy+`SIhaZz|2t7}PcU6bcbE*a%R4VcHLl(9lq3uAwk$aZibLo_&`& zL2~gXo%R7!NA$rp#kmqxe{aw&TBgA26(EDre~RvC^9$CFr&aq4ZZ20{Fe2}A0F z32w0@Q)wI3A%usz*n2I0n)NLCP4zk6&e_tWQ@-O%LU*z)VTCU)v|HGeB^q>;O)-N z&Re>y<0syGI&vmWC7eX5sChHW(;TUTl@Fh)ox_@=zVR`8R~#Z<(b1~tJi*j#_!~RL zYyF;+m9!x1$^y&5RSzG_BE@W`tFrRf#sbdnN3>MpL;Z8iE+y1TD}GMC)LP76iN(2U zTS<^5Sq@#KQFLwh#O;J-6BFe4D}QMI`;~*hQ5Y!c8lO`#o{92GZv{Z}mkVXBlMn8VKLXFteVct8E@b%t{gd=essMh1Osh zuHT|7Z;HABd3zg*4Ai{3h{lfx0D^gB758_{)zb|F2JSlb)~>2AtUk-_l{mKwqIXe7=%+y zE9SEK$P(YMb<_B=&3%<$m`D84xdJzZq8HUK|Gn}xR-MHuZGRb?xwn~l#Qi;SeUQ1= zPlBCbBGDEswRq;}Fzt7$7Bo3beRU!tqkRNA%;Aio113;XpwN>-`Y9`OG)(v?q@v6CG0^-5o z60N>Yz2B;L0r(*+Wh&gO)jDwyDL3#*>7A%?!e`I7U+eU$rRqdpCZ6^dFQ>2yY~udo zNa}cP{q<=YmUZ$P=Rc!gf+ha!-eV|D5LHBlu;@mv6q@`k!_uBi#i4B}x;P;qsn3Ji zmSJPIAoj%O%T~>Cv@~1&qgO0jVr-5s*|mjVt2^TWDf7n<7qo)UfS1oat7+fVFM4UN{k zDe|$2B8zSQ;f3?QN)9#rF2o-(d7f|i)&C7d`DQm{%GbpXls+fUs}smP`9B?`HdV3a zViNyx*b(m!Y^k&e(7Mt9w;E9DCp~bv4Q7^FzOD}7{cm))`5TJ&iholSJmro-8VSFx z@sbr!+C$Zp>-2f*?uW`;$!sa&Zv>}(>0uVFKwD&Z3Hx@|gAx1>rd=tiP?%yYQ}&CM6sH-YJ3wk%?2T@`R@9=;-D2AKHAD@K}jHKcR1Npo}&cS7iE; z+1FU(c`@b7u5wI~!rmqJ<@v)HT7QbZQIYSy=X!s?bQn?F>IU6Ko|e-aari@VN&0g8!hmB zu-e?wU)5Y3<964!=tXTqTadI#HZUysnl^)erlHQ=mP?i$5FScjMGl@~uCQ z{M1OsmUSF`XWx3hrKSU(oS+LvkZvFqc8Zzrb_NwAd05z|S>-ueS2(}y;ck(#^s6mf zjNTv)ySGX{`tj}J#d+ns1Reo+twIUHNML_XY=N!V_fA&S9T2S*PHuf&m^k$fvWmZK zEk-d_B#!l%qp#Y_KF2DZ(YQZlRk(N85gs6J0*VxuM~b)}?Dsoj(XNMYU`jqYqI0LT zEKUNeeKKMoUR@=o=UO;(;~H`du$@<~|DUan1djBB|_0RiizYMOh1HD(ws8jvIQ%r){ll7v&%Pjka!KCHDTu z!2x(5N!0Cy0~0~T;53UD49)@^OMN23GVmw(GLmVBuyB1hwL(^6|3pi2DSmICzgWIM zo~>J5V=QUn=asFwu*}+wUwO>FQMeGp;>O>a&qaOH8m6gTpucy$Cj82qT$kQYztn#* zK`!M}?LI$ux>w`N(Z$;079bI@kEciHCglxOPPXu z1~Ajj?BE_7Iz~8;l2FyL)a!~zJ5?MF#595k)^!oO_~@RgfDm9ak8kj?zeGz8Da-7A z*1nkvQyG$9z%dVc5bpah3zcduW?nCW`(H-dAljBb%m)(4>86k_BV`-m9&{ufxF{WX zh~B)I&OHYoY*aduV%`t@5tN==wdAoiD-zT_=7?r)Wt=R~xI#BJLE*DhA6$lAMtw8{ zoMk2HEg(av^E~+h7=7a{D?E5`(RF!0YOzm%s%*(jPwYMcz9+wG=6`24prBag`Cxl5eN%>W)Iypd?{~}s8Qqc1R8%N_v;nQH9=9( zD3+}U-pLs{;fee~3fA>ogAL&s{AC{RZVB3J3|)Co8ctDHO0duCg?_;p<|J>6{l`(m zb`tY`XD3mhNR&Cz-^K6yz1Th(pc9;o;xIh=|87Izx@+2;BD^R6dT{C2pJ>mQmNd3= zwU>BE!@KJ42*pf%5G^3VO+-JS1dyaF_G~6C_Qf=9_8uDbyEP1xhEKsW(}F>SmMyrq zIy8XEp@k^tC^h&yq3$2MOMT`*GHkrk#Pmw%`O&#=>^J=o~Z%|bol9$ zZ>3M7Wip@&Lk+rf;EFLocW8?po7DJ3+o~XNFZ{5Ye$v!Um)Vv4bii#@fBXGiLxqox z-_8(k37Se0Qz1&GZZ2zJ8y2}_7fq!Y2$kn}? zPrp2%Iaf=APN}3pmScjfk^`@5#N@uLL$`?=2FS~@>}9zLSr2UKqg_2oXoL5U)LYdg zX={suye31RcooNeO$wr%WKRY0d&FVnjvGlPcH4iAZc8e*;If~je~2_n3)s{*JKzUm zZo)WY9m$&@p&yzcDgi5!eG$!F$mi$-?@9@I#^NunHPPBkf}LbO!K?H+AHI~ofCf3x1JbCHTHwo=wX|_G}N*99$UeHnz&iTLmY(J}~U@>g}BD%59Q-gKqW1Q&E9^swIIQi58#|FwTF(vbZZ zX+|%K<49g&_UU!0ITIQ+#md=Q`gU&iB1!uN=S5f#)NPsD<@OK>d`kivLPHVG9eoj? zxxJ+3&%UA!1=D9^D@_qhfqBIYdm{YzbG8Q^t3$GH56B2v_vqcr&94~WS--hzfhX@o zDnoguaLqW{z0>G!;xNO)gIAv&ZJz*{X-fRg7d_UYx&D^Vc`FR(KfqL}kKfI@nXYJ^-%8 zHU`u(t~?=ME_!QMlxIbHSUHh6UNKR57t3p)Ix3y%r+dpetKn=mzmJ%r&`cLbK_sx%bJ2C8l8M2)bgVKruxNC z5Ohh1Hy#r4pxs3Y17iwG!}}g z!4{8guB+(J)l+BPN=!q8{Jgy0QoA+l&YT1xQn%zbIEsU4pidi!ITAFj6Ih18#Hap_ znS_Xnk`X7bAuBc?rtkcbi}cP=ZuMw&plVxai6rTM;P-cnljT}Sr)SYJd$(R+vOC^A zD|5GdCxlF)BwGGEFmV0c3yzADg-J)!Xc#O0ht822Q!y586XnN&Yr(T*6Ew~IH+Zb% z22_h$$JW$JQ;w;;zIQHt{IXof`j4OA|5^sKX^(xN8kaA*o@6E@yQ>wK`)*D797&8c z4H|3tonKHMQnq>hf3f$TQBAgMw`hD7Q4kR6-9oP-O(6n;bmqEktU%=kzQZA zQbhU^sh@dUrKr)E>T&xtQtIQ2LUN_TT{vN z8#a5t)MzN))Wg8$NC}v)!pv-*e6IlD)6V=%dd}0ucVC6lLSgHU; z!{${495XkPEwKozs++tE(?3`H^N(w`oC)FGCm$REx3zw795Z!`8bM@U>FzPp(FlIX!&*q4qY*enlJ0(0bHNp6l42**zZv;S~$3&5b-hbVc@pe&8-0LZeN}i7x6p-=7Zmf0)sK z*<0!snYGO!zwN7H@fF0g=IL+Idr~{Y*J`e?=&Mex&ZMzd zY)y3mIrzp7Tg3ui<$`Vh(+ZVQ4Z-Hi!GU}e#}+{x=hh6;zGEc2R6uqO6Y}>}foU$| z+R1}<9zL@@^RDUb?QJQqsz&-952$xw=)xE0_*<>*Gtwf3;TlBObXcovy58da+c$q9 ze@BH$RM`G#0{GbjDeV!!SVpN+0NXWH=_7@;8@(eVV%R>l(h{k=?d^H!eeMxvr%OrHS0(Q0Fs0wz)iuItWj4?$kKBUkdSm}`4;knM zDZq+lYDBQvN?5Ppo1a5NE~wupW|@hPTahI58ZSa->q;!VMW_-O&gh4v2`Y99rmNL$ zXE-#a+?{2xoT({h9&jnpzoE`pKQ@Ll#x~-Bo3dT$>GiImz8)K+5^85);GWF`vxC92 zogns407AS4yz zT$-Y*Yqq`J_TOo{Cfp`mmF-E&Q0LBeyh2;8IX-8gS zj>BUvM4ld$_^4G35iU^l^l#l;?X5{{dZjv`+9e*+&q44V_mD@mU$>Ykk?=Cy&a)JU z7hKMTe9o)cVyv6b$Gv>yl@_! z1zy5AK77Rt9)Hh+pIwS>HT@Ph9URVaJx!g5ti>xkcty6<^kF=!>BB@`Umfmf-Bqn~ zrp)RVTIVd)fi3kNopMuJ8LTaQETi%Z;7Q|3eD7er^sr&UUR*chdbUVUt)7b>he)iJ zbL9J%qM8IX`UU8VT%qQe1Bq}RV{ChE?NsrU=S|#pWz9-9{%C4yQfihS7P=6;9=R*( z@~|br#^sGNurW4>#2#=3aMW*nZSS1RDLxvV*B(lJ;|MWK?iuk$ zncT#?reD0a;62l*t+>Az)l?EPpr~Y?4SK4|Kq~0Wt9C!6a!>4FYgmkf{E2+Z3kLO( z<8}a>?_Af}G9y)-aJR;-y}qfru_bo}oW9V_YSe#AOPzu1z~Sbai$U_89?Mj-*VbC0 z*18V1v#UFhqrZ7{jy#pF8}kjFZjK&*?EmwQ(aD73Kw5xI2i|uvCvik6Fxc7Z-JDfW z8E4$E;*P;wX`0RU>n>k1@chluv~O!|v+&`==X8yUg$G=5l^(2rO;CbXgh&US*PLi5 zHenJ9Cn5Qr+zzyl9R4!YkoXaJcXwHbz1J9;VcHb=%lac?>b+h|;Ki5HKwjk%3+Y{2 zm@J`+sdq^7CSLczL3;dAa@7E`V(Tv^=`m3gjkfC6N)P&L<;gw@1{*!`(ynfx55C3j z&E}gqO3DsNzqyw(yozBgBVH^c=pRkS6+=+uQmez9qT+ZpOoj%Fe4pHCKujoGH(TL7 z>0*fB`|56sh~5R$u58sXY{-oVA%7@Ea7|WY!&P^8?cVP0ZXayr=}AfL?(WJ+e{X9` z5Gq^`#k?&GN)L8;Dd(liaB`!zHFAHD;M5x`(QX&FyHi~SW*{Vn=7qLAwQ+8XgkwLg z#Sq0z1ykG?KBqji()s#sf>z6GrR&oCqT>=C?((3yecD?-6tfkF&1218U+w+8?=GiL zy!t3tzTQAF`{Q~~a9QEPa4wYZqr_$*h$P+q6z|=>vP=)l_4jBF*{GexGRQz0sQ6B3 z?oUg0d~{WxM7e!q-#V=E0k|-rxZrgo$ax`*lXBnH-zV6OR79vkE|!w*@r!nFmYxaw z1$*@^Ne=d({m9DN@hcVeT9Tgi+MCr$oPWLJ^Cb<&%*&TA^d3n7;P#y@?wg7^Pt0{@ zP#lShC?q%4?0F!x|7HF3o007#L zq1s^RDKzkgv8r_n0&oE9L1W zKjJ{Ei^X{=D9%;}96PAa;)iio;o9MjhWbmhm)^(l%qtk=6=)k|YGv)YcUu>0&~#SF$Q#^Q@P?1fuCc@J^UGmUo-4x+O>=S(`- zm#pxvOXorm^l4J8%!<4AQ*M zV{GjDy)6b3u;)UK^>7NJj>{&v9hvLt>ph3M)z?!j()aY+o4Rn`_IGYWD}B7AysH2y zH_Jk=psUT*avBK%dWyf{Y@-kJ#u0mlE0G~Y#k#S|8{dQHv$BloyW0?Rbb582g!L&p^N5T_>WGj0h&g_?$en6x`3|sEe z8R$_2)dGxYrgE)pMYjSoXu#Ath5AdEUZt<8&5cs98U2IKSnJ(pxsUI+P?@Kg=>Y!4NVAm%?MtKVCw0jfKn03hvk z@70M&FkwSM?^kPp3ef&raX&ojAA0k3Gl<)>sov(RlaaTGoc;Kf^pW+>-#!G;V~PuD z-sp`v;ddl6R_}U__EVsb zB=bDtE)|o1zMRP897Olk{_B!`u=yY7Rp{=X^zJjjsmU48!6m0ag-DKuZVQSA0e~-AKnfHMuYByn!}r*s17c6UT?Lgd3Z(tijA} zFAd!QLC0n2VcTeOZ1jnWt1D6DLq;EaHh|W6c~uXggL_RftRs1>SErXK_C_-NzeHeZ z;P+lsmt9bM=?`W8SI7s&?jed5M3&3`!K^mvi>ug zT`*lwFnny={k-DBM=E_oo_Y(Glp5nz9VX^-`j(LRCO}GBrHFB((>ivWo395SkIj?2 zx{t^Auf%7piRui0Wc&cA)_$qX2TJMmbnONYYL+Cq-KMws>rhKkT#N6p&Bfrnrw!Q>OeukPDkDVB zS-RYQ^JwH;bpJp#dzJX#eFReQ05qo9@tP&n^t0@9lxPJGoR7OcB!?}@U*mcKyF}P> zW|iaRyW*-8-S_1A-Ot+G7UZk>LK>YCk|UT|r08&g{u@bo6#IsHPb-CuWc1UlAB^N4kcJadcMqd;9bpVablq|>dH5kfezmKKTp;&UH6#5o$z31x0h(UP3lw`oi$XM>9XlqY<*ba zc$Vl0itGjucjw@TW8aO)bCKX%tA?sSjdFC`>&R{~2R_X6i%>ZaTgN)Z9Di7)jpGB= z0_mFKA8nOQW+irdX!j$BpAYAh!%J&}-x^{4gp>?8LA(P!W-v-((-P))KsPj2bUHG zt*t+07-*=Ndj^urBsujipF2^)2~9=_P~>`+E>F3&hAtGS^|Ud*eFuQCx)t@C9Cgs_ zrjgKcBUYZo<6^1B-mssacMX)P3Va#E``(nV!e1+o%5^v>dn8M0!&3WJHz$kPzurC& zipN+^_Ts#bm04Rwakd(CVxoUqA$l3#tb?8X>@m{A&wrM0PM737CQ-x#VR}O1GbOPo z8c4^^-Yj|?;$t&r_%&t==(_V3YOXCiQfRw_aysSm^jpc{i*wNj28lIn_U_N|58fLS z#n$SA1JMC+Pst}!rPH}<2M5?DJMEX6oET(ZhmJqJZMZ~eA&a_wDv`@d$%C2B63}=4h)0V~H3?{?XQhdF>cU%r_F4JRF z<^mRkYSM!P*0e@AB;`dXQ~2im11!Zo@=^u$c2x!7jJti%9LLL~tg$!XTS24~=o~-q z83%%$cza6_I|UmpCQ&c zd6Pt@O5EqVzxxnA&fg)(D|-vt^%V^s3KFvgl7mHCe;3%MC%cO2<$!@CQ7z|9*L$8n zBEB8C)&Kq-p??1A54$p1DXVqd@%r8fy zoxT_`mXf{%kLOxI_bI-lhe^#i-<;<9IZjDU#_*MJ!!J$3b@WfjBvx? z>JLqpxM&$KuH(B;B(&KrJ;Z)1fA3k2aTIBoIrzt;Jph#fV|mO)SjW zWd6_`s}a&K(i=Jo@AB;)NVl`>-3rQABc97vOz|kNq5yMpAJI4P$7H71Oy69Uqi>dN zL;i;_H>Rgt(UJfZtCO+nz6vwzTNQJ;^O;>ZTZTEd7tl1+#G`DZRvjC8rRX?4EIrUh z+5E}kw>|Q2T$&&mcNYO>pbRp5jZU!wbgJAZYBLH3uJzN6G8ZTuJyh1NY0oLw76XH= zQF{H_mi8u_NjGb0p>(0Q5cOBn3O)toB2HfgI!;GHVy6)CiZnZ>CZ^Xdco&*G(L0;T zr(_$=`$GJKvZaIK%BFeJvz4!Q4eo9IiP^n%R88eI?c>zq*5=pA>T@sW*W(FDSN!Uot}RqB{`s?? zpPs_c^n8?eu)cj=(yh-6*e6w-n5D^tDKYo#gxR{?@&To_gye7Ew$nRG_rz3$f|W4d zJ0E;77ZOB2C-b@|%l)a()H0XMV^_PQaW80p_2JUIR~+WIrACTbdO{{rh-T!%f`@E- zj-)xoN**a8Au-fh^~p3TIR8mT#(Gw|{ZxIbd{sNvtbKOU;Jtv^?ROV5HAZe&N>>b5 zTd-UDA!qE$tz?TIGjhJqD8Lx)jwyvzmCYP@IgVE4*^l9&K`@7e;Z2B@Ug=JS(VX6H zMtxVP#CFUZ=R1|_qMtW_AaPc6@xJXOa^;()TQ45EsV~^1UA|B*|7ZQ(#?O)YRqg%$hJW6()K&d!4~ zkQ)=Z1`t{5#KVmL1f3#o^Q&+ecf)QAy}NR+Yjy-MLKHXyX(+VKhg`o^c21oJ^j{G& zh&Y@3SN6>cdn>Z@^O&xeENuMRjEYCpKXu|Dl6%AMVTN^Y8i%4 z*k-cTjto!?;TCyBQdGKq*lSi{dcun7hS%)lW;$ZC6>?ne#DLb+2tSrdkH(L5QQKmG z>d&8a{tQ&e01_Y__)vu1P6f>mSpkt#E9Kl7X#Cx_;DqE3Tq(8$(JiO8akHovMHL$V$V7$9gAl!wT`O;k4$_Rjm;)!| z_Z3>;`Q`@w`(6M0UjO?E`S)u4_a5^9*M5BtILB!;fqmSq)~}0XD)YrA3ie@`3XHUa zbhV_*xjs=|N1N@&XMbGU>RJ&-RV7f2OIfRR*IeJ4EDYr_JfYNZxJDXjkwGK zvqi*)$r091OBx+~>}I@Ou(*Ro)w#ngO0VE=CAm+QmKF@yr5sCA-I z^I&?}h1Hv5S()ogzBSOy&vU!LV}SlXOlNxgNlWm}NIoDDf%r;od zztfhcua^LY-zY(R63_=Ma=lWi>HX^57XOM*@#MesQ=HU#3-CsabAH?O%>P zO0rc;Rb@K^b?YJA$K$A%XHa!@GdHGA999tc&;eBu(o%^c5JkKQZA*gKj^()e3#Amp z{9Rt8@r1SqSABzjJ^xK6sf!P6Oa_c;^`<*a3C2t(_a`o2;}MgT4CuN-vBjBA9R#0f5xqifI&c93jPCK3un_5#w!%jA7n>mr zjN9=o>v(v%&BEBZwVbO4Uf@}rs7=*+`ejQ8LCb6r_iv_xo|B)>i=A72rY&$s){Gvc z`Co*s{EzS&r-n07YPc1(Lu%GL{v1JRLSD!PL17{_i3lPws~9tyrRzJ;y;wrC(;o!%lj{!8fj1S)dspwZTN+% z)Yg`NoqU9EJE+lAe$0M(d;JV#CvzHrIK4Ex<@)1g%A5ZhIE(e%+0?)E8cfPLVdJ5i z0oyeV02noFomL}$Ax<8=V~YN74}hA1+EZ7Oz=)WTm_le0)W^>%#>C|<4 zDQ-JM#o17f85v0@i>aw!$Z+rU-80Y%i~^mVV+mIZ&pBc5QtKSt96Zqga@1+`&OpWA z5AOpG33X(NGf;k(ip=-a#p~@8MoXhf^#^n>-{ptCC|Jx#XmpQz4Y@fnQWuM0C%Q=8)T$59SXMB%TxM zR?(NB6P7`$<#S5-8OT`%xLsQju<4kP!4qZ~s?k4uHV%C{#m_)9$)xu$)hZZxsOm>n zr>{K`2ifAFDd3U(*UON={ja=t++SY#KG+qHXh^9cZl%UH7qWEJ0=6H3sP#mhRFg{O zgVPgpUV)(mi4RUaovKZfVLB^5@s51Jy!YN%266cc+^tVpy6W^Te?ti@ge?VKvJCxA zcl;8p7z|7zRrB3UMrG^0y&j_iXGZ$1-H2pO;k;?~C51EXRx(I;(o|#O!ar&ayUrArcG5Bli$N*yuiFa{n_J*hV6?-3RoVfoV;f8QK&Vc@W=r7jEamC4Io&ZS{01kk&!qAX)G}&W1dDYz9lVD#JMc%{ z-Rt0<9bjyDzgfsXT`iyNG3=#c)@b*lrJ=|t@7kiBX0W%Nv+LRfF^z`FZ!O`#~QsI&d>fi*<4Qqx3uPqztvqx+Lu@^9wRwBw4PBz~;4t z7ea)pvpo&V@jDXOEWDN1yioXp_mGqwuFmoAo_}--=j*0b`QbnHNQ*^Dfle2J!7UOH zKd0JF|h%^CC#}@RKc`u?^|hAdl$?Sp{}<-NxZjyWFb1p48HGf zQY0f~W1fo=l8HvkXO-r({W6=&`tjK%u2?y}gnJqZQCdfF^^f|U==JMw3~ar%TWo7f zo>StpU+-4uQD%y5>|$AN$|9U9Zb7E4jAwp98X8hR=R?~rW`$P6l%nr+wcrB6m5)u^ z!-x_FHfwh+gt16{B-GC=s=_$C^v{#(KVlk|;`(?fop0t^by{MGbwbt+eejH6F^8ze z{z7i)^z}BF*pLIgZn~t^?!=>l*K=qZ&RzD)Ia&4`O8u;DugxLFHZ* zQ9SvcyLGA&48nL;@B3O%FP(uBoHA)nO56!6=akq!20(_Kky}{C6g? z$t+GYCt?-w-d;Q+4m--!*UUYGiVaLWW#2Q2HO1l-7HSxPWB|R+!bPdZ%UDB*ik?s? zeKDJ;9#V))egRXcrzS!S)Hd_9HbG*Ni_CEY}?3-!e#5)0W@ zJ8t0tzh*WlQaxLtm=K`+=)%b?Q5L#$z2lMa98;l>4j$3E##gHs#NX|P}{v81d4;0zfSWQFCqxF0kVaFQ9!g5 zIV0&;zZpe{DoQoyu>1j>_~)sYSIYi#oqSqgdv z0&0+&_49*IfZOrx0`^?2($PF(tKlvYyH5qc4Hofk%$@_Ju^`)j*jlMv?B^yuUf{ z+Gv-M{fi;ZT1BwqNiBB6&2r7@yZ{E=#g~B8us~`@yBdpE*(if6#IzuZz6B!3 zt(g7+(^Q#M-iMp60a=n3AKC^8w||KbyhlTdu%FT99>}kG8Ayq{(_Qj>`Sx~ZH3W*h z+O^H)3*bOs(zE5hW~vayl`Pw#SsW%#2_&38v~l>cq)1ka?oe6HbBbPeHKwOj?DaIw zajSPs1**Vz8h>CZHGvCr?REuPSwi}akJ_*8|FR9NwH}8VpN~SS+Gw)aMoT_=u4n-p zj@GmcA(-_MpMyWd8-Vy zY;<5f%HiLEZo-`@-jT75H2$)~pD4o}#!B|2^5briX!d%yes_JKXks>V-Ci^G%FsK( zy%%~4CCF0kS}1r)4|`LN)tqfz3IpASkIZUo`&5)`%&6>}VACEV?1|szZW22?T<_la zIkV@zr$$j|afL16V%w-EYvS1!xtZ05Q1ST)?s|-4v@$1NzqIh?-MI2WF3z!rz8OnC zTZ_dteQa(*Oau1GpJ27~Cu(E~cLC-OEhOL(HWS>mYS?@5P4H zL2Eaw8jVe$&bz=8c3Ttv<~NY?&syx_b^1BWKmQlpRSxOCBdVNJHIYVC_0<}gRUhK zGUNN^jU~!RzSxRk_`A4=S)({4OM5omB=`QZ{e&tX#e7xgWd{wEltJj9Eg}mX4pUl5 z%QmeuU^(Zo!{saEed8A>5re&j{LOIc(EGT6qN|UNwA)=d4rgPz?POwo0!p(fi${S_ zXVGVOJ;gr~cxA&Lr1n_&mt4NPLT!yja?58xQ0|ryW2IKu()2?))SlRLT;a_ox~7}f z9s1bLy8 zz+pKho?}4m>IiA?5hbk(?gW`?9M!<-PWi~l1SCIMCCsKlSKwG5mW|wj>?PjT8JS;v z9-+xb`^$Q|o*i!{F}!V2Bu;VbPDE8@PtDAYB(%nJw2;xY%o3$26z?6BElX^D=9+l& zm5$m>1v?l$PYQ{pw#t`$7VoM@=1q!T?xeqqepF3+ed*?08 ziX|nGoXRgEhRrg?2QNYT3?gISuSFI6Am@GEeT@!YSmr zKWQXq7Z6l<42@uWOb%Zuh(1T&sYZ#B0tw?Tu~0<8BYCnsn&|sQ`3o4r9~@3=hP>Cy z^k&KrrI8;j{y-=$hF>->OH4n@<923&=n#60;3zL{Q5@Ab1sIxX@>puHBw-$V^y^nH z4keKtyLWJxkLG{qOFiYMWgvxn5`X zoAPxO@q2+Bg=Qtu38-HoGq)wS1Z)ay#JD$kw_ymq} z^g*)ca*;c`h4`eF>Ef6|Iq4XY^I-XcN1jfzw=L?2Idkart;HD&xcAMIe8tSrPK9?c zMwKq&DW>1EYpWdZdU}>WDYg~ow2^U1M5k88nxjV&s>UIi_D@MWGtmMe^569u*|_|4 z*3{y;QwIQJwphb1a?vZ=9yMm;VtA@fL!uGI2^l7wV?m;}yP@I!_S#eO1?PKbbCWxc z98qX@GmpptgSS=4Trr=r7VWgKM^DKI;yu<$GSlL1me77pnk)2Z?Me?*%yEl3k_)Zj zU;2F{h#lv(8PejuvWd%gC#p6ZTA=RvKXFYoK^vK(jc^5eEsdfB?^6{r^vbJ{^VAV5 zCr+^Mu}PSG9$~I~{4#aAi@jzKFnm)X1rgn%Zc?HNGI26Uikvx?I!dOEdOD4DZ^Rg~ z^fs}0G)?NEZKW!YgpgYR^WZ8<4cE^SADu>v+-qcAlfoxi-0U<>aJp50HTiCNkGQO1 zBU`eTo=R~Rke#0u15G$P6tOAF7u;JG}lQAbynHwl{|tXsnB%S0ii6DX4&Knf-{$_y-oxEXY%8+8h2v8m~ zLTCATus;KQqL*k%1JQ)8Ylxm}IEtD+nF*seG*R{~Q}WNXM$X0iPv%sTadXY`d9d7l z-=CX;6+>FXRx|U{S-F#_5snJnq=bs~p-+MU)4}pJM7QV}=$UUg(jm->ERd=y9D!36 zrnownQMgC?xTQM)+ysFiAtIu{rZw(opvKMuQekLZ2R#7P>i;$%Edvo?1cM@uN_Jbz z@m?3S^(K&P6v~`cXQ}W_Uw%3-1M(ZKRkJ9bH>TIpj|Xy{B(xesuHy1?E$;<%Zu%L` z-18Z^u)S1MVrAs$Ph!V!83X=lJzex);nKgmgNhizuwy2QPG5+5m?}kc<-YQ`9hqro z5@;8yC^}41zgRMxw1iW+g~ng(MfgA^ej3@C z_Vu55NWs`Ev)V8YKOgbO`|Y<3CxmDrM({G*i}(5VLbs~65!RM#eBbv0^piIA3^epA zVzlTGZYw}V`y^!EALoE=lXlj zkD8^{Z!$4@H>A^c^aP$?Q^qFp>Tg|p`E25xI(y|#=bS`;*T_69+`rxXkBO<2o5Iq%dl}4gF9l-y$Pf={oJrS^wv`PH6d;1?*d*-J4jRW(c-?rt8pWgn~2#sj}=+660VomtBCOI^uK-vi^#AX&T2440s)VIfk2fK*;X!Ew33n*~w zZCh%52alZEGn~VQ8=Zl4<^r}RhC*E{Hs;dpJQS5a88G|9!hIyIE`7@?6khyUjx=Kv zvq0-tWQzCrMOzyXlU5~+?obb&=0dmU0tStqqc2&MO{2$T3-0L$c%I&PmYQK%V-U(HCZa{a#tkli0 zzg5{U5ycTKBqUK)>b_lCx?T}sd9h74 zQ+5KQ{N=0SEhgn(9)@kx;uB{1rE3aoX0r2LvF~>d>v}DFhMAEuHQHFHd6XFn!e$#~ zC28L(BsFFGVHwe|?z#PqBl~m9l+h=@9;9QtGsVJ4o z4}kPFufQ}3f8H=s@k*%DPT5e;n zy}QD1R|ZucPxuv6J1IY;zy~?-u-ZUDUzA7ny0`^mJY;;Je+{xK&O+Fmtqghfcui~< zI{PiuIrIZ6Ah{_0Yx(#L)0C=mtubTjVv?)Kw~n}cf{}EfU@nyF~JV} zE=;X=kGr4HNRBGxnc?VVLTXkEH$6TBse9dA?aPU{`r5=Mh!;OZ45eRWcnV_L;jwuq z;&*2@l|IfQD@fZTF|q;>69g<;JpdN$@Rqj4A%!^G^i)!d*8EFF?O}y?k07DZA01 zFn(=bRB~{KTe{sX5V>5u&htqq?pw`B^c4sXYhLec^wSc3uO9m$jSqKcOJwvjkzC!s z5?eBi-4@6*5wv^`=l>Is*ep?;1$|)X?bI&a(%yOfR3$)p;^xBhNYk?j%bSbqJq=(f;v0yW^rY+3FCKKDOUToCJ zeM_!-HnZ>>DQ3Gds3QeQ#z;_=Qk9st&5!TS|De*4c5_y$?0Nz934+X{O=))~6w$-a zKtNl2qC`Z*7xI#sB2R1x<6I|36JR-FxP%_1t`Z*Fx6B0QsB+gKYM~OO0`iKOzVy3z zv89iANgU3|hy(^}4eZK#X_e(B!WP>yGu9gBUht1`L!TBYWI*o;=lV6a=0d}qlB>h5 zY}gSuyGXz*p1k4M1{id==wblO&87@lbY4azsAcxggs4pb6!=P^+CC<>cklQzd>K$y z{B%TfZ3&PbcKuPV0fvU=J1H1out=#V0YojA_xW>v!|9L#0Fy*xs0Y6iIW?noFAkrgQnv(Sk}HL_)>5`a zJO8kV4rwG?7TYK@C+o+H&0*lCrXJ)h-G-t5F;*C%7Ka8aUb}0Xk|{cdyf;^6WPzS4 zHnLd8dKcVN;=Y}upd+{=botiS-lY@S)ShK5e>^%&ni5Lj+BV*4Tuo*gj1Za$Q+cYD1tk0W}OzNcL_0dd<>J5~9Zn z70iFbr>REalCxAyDT!0>-pmkMzZ5;-@586V^XqNXK}MFc#p;FKVl&kxMa&+LW#J9w z8Fm?FHtyg8U0kyEyH2$EOlHOnJR;>u8w;Yu1$IRlu`19SqDrd}xc7 zu13;6>-dCd<*-Q<8)rGn8`T9rn(NN7Z65Tw1a!*` zC-j5@H9}x)50D26Gn>Tn7$B|z{p|xZkigO$`)g}qoLLf9K7!huRh67x<>%E&RjB@ytvs51V>%+Hz zHbx9PepT@mndz{3ChP%O$%M?_?hLzFg{D~bQNeXnKn11(ldvX|)>43qwa6SNeWHUX zckpPwY7aff7|&33JXwkbSi;78B_VZ@O=trR-4c~#jlSbv!JKCUoD zL6UBbD`#d_%%P0073gE}-+`<-MuqolZ#0=wbCJBlmvQ}8HD*X52r|qYrxTA1`n0$_5n-b__$m2LF1 zEah~R!EL|55~E5PMmTl9zzFWAt^XZXOcD5~dW|g8Uvu@uZZeF8*sd01wR|Z;Xp`Hk zsP|Nk&_B8t6=t`x$3{`7m|IaoQS;XQ1co5$wLO+FxtTCMf}Qmvz*a5!7hdmfs{#H8 zDz;~$Dg~1Q^c=jrwVXrRcXge4%dL86i?_v{Z(KU2D_rVi3@n$nKwVZK`|HqUk=dZm zV$LFtnRT)Q&Mr!Eg+khm+|!s)YogHDX787&zJUT8m6l-%l$dG*sTh1E%$eZx8o>}I zK#nE4y*hCqHFt-cHZH%GuZiO%JwvUS68Q}J>fjNZ!$(sRKFpLsi#+wYlYr|9zk3hy zkYuQ9C9n|jiM|r(SJrNhF-}pvtK>H!6iEe3NwBQb_Rro>8;FG~m-dV=8=rVfW{J2y z>lDVIDEFak#PXN_gT42TYI^PR#qoG7fQZsNM*-;|(hP2_q}V)+~4~j%Z22d z=ewW%>}T&!A@$zzou`pjs1?MtYJJdlF?6aoHz&hwbu#WI%7@& zb-F-#;PIUxt`3RF>!TY8UjU}g2r_M@Tcl(s)vp2*qX-k42_}NVWf{X31-UkjE&QrHYdBwQ$T{N(cY)(}7Iicaajjf~UP+K zyx1El9JR}jVwXAlBkAMG0q_ZM{sT)XZrKdVnuSe9*}%G{th+*ehO4KrioLvz<@pSp zFy#D~Y-gHOmtc;2EUmS>fQtr=TcQmob_g*aO|WE`=A>OWLF?u^diYeN^(PH{yrK9+ zNUqryQymZsKAS?BG2!ARsCrhdJG5+Jac&KLafF7fDNlAIK(=3!%XD%AP)-hb^-Sp(!PMqI&w*q?<{dQ#SM4C4OsNVa2*Sqn@+l1#4apWv4{bQsW zW2P9cXCT^(5Z`*xWfgk5508R%c8HoSC#$&^UKr5vHIZtcnrW>8{9ie`l0r+=!|mLu9l4 zc}yTW{3X*JMnfM5cc!YmQV4f_t&8oA^6r|Iwb*%jDFkGoG~RsKnsY4eC%*>USd?u3 zV0-x4tGD{8Bxj_g7iFr=fYSUxdQ7E3r)sKU?%Zq`){iTx>!;bZeMO1hoa?W;>K+n! z_>IP0Uf&JiDX*}%vA!M9pl4n8&~aR2Yx`k~55`)4paS1N2XtcNDL#>XRR2?q2+L0) zqSIFe5|?Ima^$&YHlCWh5=0}@KDYU0JeIuDsQ&Vg{zHulyUd%$118@bQd>YvvpUTtpzJ!SPExWCUtfg>ID*8{E&g*CG?Px!% z;xzl&vX8|xERVKe{CDxntVmxM6^;0hFeSUs>%;Mh7H#N z4JN~j3;?m&S-{Om!7&ho7P~AFi#!qaX-Ffwd{&6OZA{!&;U=qAt5iWhzAJFP{PnHH z^tp>agAepu1z&V~$dhokIVJfwz7$$&FIMQ_E*51aD0dvXLwcI{W`D2+ba}JR(T=9x z@lL6KzjG0_8k&?cdw;uC&+DqG>+XWsgx$J*3eI4A)DSw8{cUbzL^4rd=FMsj|IxSn zT;m|N#+?%F(7+R4Rh6lTH1h#0h|#UZi_kHWstTW@j*Ik78;jy@PqBje$Q|WipTJHB$%Gv zxt+v1M`}AkSEuYXfZ~~VDO`RREUd1RHb-^c$LGm|K|WW^e*W~CJTvwAv-2#(>ckAX zdyiX+O;2S0Lc6qVm{N59hlui z7ficABEVh3mM++frC&88bqL|(OXvj&3%dFSMGQyVx;CP|Jq{iU9t*ZG1=F??l-s)@ zB;0s*2rOJ=z{bKt(yD}1x!q25c-&rH?7LsPzQ1xQf2&;t0bFB`Z+~I|=4n*5?MY5n zO4A--bloZ0>CcL^gMbEAnLmM;ZwW@yBB@<-&7B@ z=B7D;c?seK{yW_oI0`lJw9|irx%XRD+d%)`!+;8gffZs3KJNt4?EQacN-+TW2#O?N zAss2t2m$QO@+*Ajcu|BA1~8b(i3mcH69jt{_y{kvz7nS$|L{Y&Hq0z zTM>cW4hmAu>c<`BSMs=>dGcmdHMyMsUUEEqU)ulOiy1)c)ZQfFiTYo1fp)#*8Nje` z8;gnG%^h^!^;2>4w|BWoIEpum+v@LfIZXats?mJVq<02Y-=wB0+N1)EP;kQ=*nUji zbF86#w3EDvij^N9hQDLSMR=^jGF!GjCtjbV1KqU1nrDXX^mODXk1zVcOaS%J%5@3x z38jbi`TjS?{OjFd=B>8;q&Q5e+Xol|Gi#h)OC8#oz3G*J{Gf3|?8?{}#wG=vQEmc^ z+5<5*JfqfmGCoG;#mPYK&Dn})@2b3Dlb2Cwqn0P#V3VA=Abki{S^Z{pT{WQ7HNPZI zG5wj{xsueFCm)aLKj}VOT71Cj*csFq2{F@fL7tb%7%YPLWI_Y&^SMH%b%+9XH0dR(eMR1FMU7{bm3y=yM} zwwRRcvTJGW60+tw#^+Qpx<&FUSR1EK`74iajbNTRIjoJKv-n#YuP7vJlD%aUyk4}K zIC&K2bG~$b`G^Eb* z+SdEO`dHK=PpRQQO2>dh4BDMX(EU|w<1s!%!3%Xk))f{FN8j2v?&|IOL|FOQT;9`I za6r#A%bN|U>^28Ma$#gwhwfJz@oXo5yxOTT$k$=b+Kg$3zN z)W_iy8+V7E`Az3Ve*VHggjT4}Gbr&qWpfU9N?8m$HqI~TV&;qbp3P1yD=C!wU=)a! zZm`bN$dEM00kamVGkH5>opsZaJ$*rbtTV3p*DCGBO_?znwD4i^S8YAJXvj_nH?jW z=(Y6J--{h8VV3y{nHSOK-CACjZX1%*U2-OwsCU4`7LSmu z^P8pvJNiG^6hq0VkJI=E5si-VsUMT?_gOKL4IxH`?6!Lw%Zs%`$=NODZ%wWiTuBu?s??%D0vJp?{57br=+5AQu5*_;OSz>eZG z7mrZ7A6xeqIGdDcVIY#Y={Ka$P!az5hljxFMx1ir{~k6s<39Ke@m(3eFZ=DQzAc3D z{HO*!>w}+?ynhHWDL_m!=Tz^FSfErl0=9>oq>YAh`?=3bf|yp zwn%LLHUqJ@y>p1vc&Y!#7o^T?^6TD!=9@!T)RTArI7nV<8BZ}BKG^;qwTLP_$C`oh zsTl)bt-dK!EC9el#mtE|{^B|hzpP6Ce9q%0&8DwwKK%ExncO56B4A*Z1v)&I&5|+n z&#hxY_xz?>S$Vm-GmN_((Qb=zOHKHueg7Y9O{NE%{$a@z`e8?P=ow(Kp!W~0(hs+r z!lv~rUSNK(Iqu!uf4I{a^{sdF^8(*0WSBPUz5Je4roi zf%3900=L}h4t07K_7bkjSKjMxsf$Lh91izjcT>^4v2L_JO49soQ^P25w*=SKkJkHL z8XCAo^z*Cq`{gN{j>3N);`6tjBK=d2=BWc&M5jJt(UCiTZjXnKXEiOg|0)3t75+P; z)cVhdPkgfQ5Q5M79SivDPAhqpMOk`%;;Tp`jROrW|d*cgEMB3!c(u zUB+m2L%tUQ%J4{#*A8 zku`QQa;C@4)yh`J_lVv02RKMQl>{!&Q*cboEBk6}`jTXeOnIeiT_DKK@jJnTp92ea zYI!B3kkOvuO<3onSXX;l4Bajvr?{8_w6aJ{)co8r_$m7&DqkQ|V?;ypTvPY?17X{4(xEwO~Aq zS1*HA5g&$w2nMfo(Z6)x9_K+Ttdm>X66pl@+GHPhY4~u zK{c^MplK}Ri2Hq300Vi0R*I&e67=FaSeNN1GfPUVRgh=vU}%#G3F)@9>01#IoI)i{ zsaN5!{uGhS9p9Z)esC$bxQTj&IVV3PlYOMuvZC7_V@1~UEh?>sxV~MCu}#6Ln%G}3 zS4RuFmEC#NAm%exEo&Ka*O6N&LL>;KdNp3XK?kE6R=+j1;@H8NZhw^1TZn-Rvm{Vl za7FWR=Cw)&o+1z%RLs2TGR!}1;Yn3J#*9q2O7G4IP4K6XG>&vTBpW`87>}bo%3Pdg zKRjyE(j+NeEwCCsqfLrR72#JH>i8+6Fsc3Cf(utp;GvU48dm7Yf&M%Ol9X;?TAkmW2z$r{I#- z3?Hg8_xlM+38vZ`_|?A4OfA|R0e>2X;@ZN6j9GCyGwnw8ovYj<9TDP6hJs@nND0_^ zl|qcvn)Y@G2C_D?+9;7Kb?Rw>EO%ViS)7vkpxh{d>cErA6YDBgY_Fi!+tpxpx1+(A z7n6wH?$qt4C4ZM9nDQs3aF7-fFgExxg)!Xqb8PNHV-sG00 zBtSiT?Hp{R-e_xfxu@uBH#znBrGzL%XxQB#5B2ZgV`^SKIO%4C7I6Y}KnEmk@)h{7 zpYqQ_bb~OPKE@{C(!75A&*lB7+Kg&JiHD#09+7I`leQJMf+lb!X;~Yb^LPr3)QdgT zOSMTV-M)_v98IzE5$e$xMs=K71D$KLr%%tjHO1-k7f1ThHzdzR3&3HN?Jksv_e%z* z3xDn#z!mr2Gvs1Gsghh~p`|_53~2ZM(f~oEG*+eXPENt=GS|t0hzE7;UCb|QmKa5> ztjbN3JpVn96W%>x#@@(>my>myFBT8VTRN=ExAt2N;@7VyGYb8yA(7^c1Q@lN-KT(! zZqSt4=HgEU{p8)^xF>*|$Aya{ZX=y&S?NXWTXD!zxc_aJ6cGYgP+M|puQ31;_BG7? z?lsd-_*xcDzeDio4O6ltQX6n>0P(>P9LlP}$~=b_bghaicxF8+33a-`z?_hkgNSc9 zQdzLBdpi=mY1?~p*s@8ae3Z$F*rnVtoYeprfBiNi!XLZl@uFLHT-T;-VXlUl}OpMo}aJ3UU+Ad z`QR)^hopMX-8$JA>MTzWKy}fH#EZvcUld~+7u5T6$}?@c;1hvRQdM1mIl&u~r#Tb)@6KlL;uDu^=b-60$A2$RmDZY|tw zXdS1JG!dNFFQJv=2Jh1wgf4e`tS{?U(sUmDU{gasiBx{p?LpT$1FZ|8J0)9sm~y|? zW0=|4izG`yZWk)p1^C(6s~T~X?glFdcRx^2KU1{%Ar{^m% z{KcMycOk#k$x-w96*9{`3!Wqn7I%hiiXqvRp0h423potckmRgWp^h&ey+WRgu86I02(Y8;ZgObUhVNcZ z80)irPoce=^Ar<5;Sh@p>Vk6D&piQt{D993MFb^hqSC;;_^GC}Dk^~5=2t!i%}}o!XO3dB_s+-3#YV-K>9I zRNx#vLcu|A*Vc|*Y>k`R|J+Se`h?QLgFyvW%71G)jK96s#XsW`BZ~etp@n1rT>(G; zwMCNtoud1{dD}0_%XC~{4d**6IlfU`7Q!XyM0{vR2*db7B6lWzAyH$$rHk!N zoCoYw&w~_8UN(mDYhJKPF&o7S+g5-BF**h^>^<*>cs8iSBsX#5nvNyE2gmPenZU?~ zB2%7muen%eMuTWoXCbV4A}66}EzP@0;-a~H?u~kqQoT#c4Zod>Bu!59!*#_=-$33KbN}+SFW_^LUrL!!2jW$3nfE03~i(-1LU&9=27*wsnx>z5_&@w`2Tbs$U- ztfr`PW5qW8u9+Tk=2_4)X=#qhSv6@6Q7Ql$%rU;c*oNODRKhpV7O4@O;bK8HTOroD zIrQ=gOf>CS?ZF}{rtUslI#~WA5tbm;*qDN8ZAIN_l|FZ)T63`bRjM4RB$IT__rw26 zKcjKX?{Ov>ys6l@k=C)QvGau7}K71)KGgv!$HOP;>xiL;jP}ciCoBFX&Y8-F!?!~e#|CFRSOWurb+(f+E z+Jv=UB0G+5eP|8!QN+tkZb08lXeSXAseQDq$^!m$^XE{v-+EGXcd<;!qOju!qD ze55zjBiV!Ck^KassM8-e1pTZSii0DaN0}GqQCuB`2>rSHw7F+Y^^&QcpB;D%dQW0< zKb@*(EYJT50gj74P2=vidLeO=q%TELP1FnNsGAp^35?R}2Sm}TjDXXe@ ziZqfy?kJ$ot>k=LO7|~b&$8(WEt#El8Q@Dj_T_&^*6E)eb^q3X_?wGi{Ik*P54IcI z3|Sj#nlpP-j%_kssLfk=sSXUI?kyYs*%XjSP_TN>|tPv9cV)m2q zZ~M`e|54}qe{Z(-_s-VeJ6nJ6Z2i5n^?$vyWx1Dqcfs3Gf?-ZzDbPaWSmKd4=r`sX zCF9$U&sU#i1R2xZu_Z9bT<2U&Bbhtie>X*IoM_ub&wjB>!FuQL6nsmxt{r2~J?X3Xc_vTF5NIoHHH`777Q<-v($3`SP|gcQs$&&V~oK*X@y{Y|@tY z=f7YSRL?u06-a72;m|35#fb`skYb(d!igaYCV?46E5vN+<&9=syUZxcZVQRxgeOXH@l+ubGX~VQGRI#cJ!m~NE|PxzI|jt3G8;Pu8CS- z8<(>k!5ST(CnZd3^BFHbJvTj@sbW^aZ*2)z2=RQ;Z4#?AbT-pxP+LiM9BabAW``5* zx5jLPGo9=6ui9bZt$n>6B5?oijtg|h-r;x%vJwnR}|UoE=|m{3I`$k zn2d^1+-W1bOm5%uJJ?3_V1LQ?<|^vO%c%scWxkTYMV+bsi~u|AP;0jg7_Dmw#xw+f zmb19j9G3HVu`%asxYv6m+qDP^^!eM>IZl1`1vk!aC{LShVKmYG7rJVkUxUkbe1RzO z8<)hCUx?&+cZhieEy;qYFMg?pTA|A4GodGQ#Hx`fJ~J^f;(h&=`^P#Cw5jUtPU~)4 zTynLQP6Z^{*PPvir?lD2!94f)X^$f`d?Kc--=2?{(Vl%_5ZSHiC_I>mH9`Z;yw~^S zNdc)BONy;$HPWqnhU_!s^=y_CpC{e6^htO#$uvlbJ{@WBmVubZ*;+8-Y3d7xtyGY( zZ-Hc83;lk*hQ9!7M62Koz7Ai&eU$*bZD2YahYTkVCG9ESwT*fQ9!UWjQG^qobmBIb1WL~y2{gZl?ty9~qOI(y8E z+q?GVmKa9KVZm}uN$HwGz+URZ;{1l?;9}$cqZv69af8Wf=LpB{{Hs*ArVYxWy03%` z(E1!c&eR%fuu8p4LH2Hnc8;vfsx!8OJgNSi#tRjaB8>KstFI${{Aeh4`S69_wZe3z5O0gT20j2lyBI|f0E6^B6M7WQlKbtJAv{pgu42I5|%uQ#SxX8Bpcwq`cE`vvqju)S&o= zdnoZgKc)U>W%pkM$o}3x`wy(j|k-XOivXO>D0HA#EM;;JEkkT|y@y zg?(#Sf|32c;52;*OOK#9#=N1=#RVhzeZucIN0bvn`QsWe08s7ifZ~+#&l8UzP;$~@ zgZlLogjZQ%>G!G`EKsjl-Kp10x_)9wSNqJBdmTF+aq+RIPC0Tr1bfN#*kR?m?B%Vd zn&0&YxDKg>CL`M3d94Wc3h$q$p#&C~VA|JsZ+i5#tN;P%lfeMp6n$fT3D`5jeW7rB z1#>QN<}G6}E!DF%0fd(Nvhj3`NW+~%n?bcH9AR&hBRanZPZi zBTN1$|Emj23L5iy+U4-N@LoR7r-+cEY4W((n^dK6mG3Y2c~q6wpd0 z)c~|gQU+D4j)ADC*ZW5jC=L(}56mrq{zP?}G>XOUN1ePB+GIx`%D#XZh}gZ|x#x`>pLL)c08`v~J%3W+l=l3mdrH z>tdmIjfIFxtsuM(r(c_pZ5omC)%6E)=!mcYG@uw6+LN;9b1V9R zHiFzCF!}}dyIaS{Y&CvG#Vh{iy=xj-7cW!Xr4W$f4TafQT{FV8b#?KKIr;YUrHLC8 zE`189x@q0AmHucA#_f)3M>4U|BRScwJs+%B?RCG|cfcI?V5>3^P>T$sdS1xhd%^hS z^e@jeVYF*v&rUx%ckRnLgpwnOX%Z=W#M5R9L<3e7(F0Q7J}@xkLr|iDenw^@5ZvZ- zynlS$KD@y^ANp8j>!9P!$BjOvkAv*@@BeB3ot|a|kxXWoRsav`w>6^x`TmW`0$|gL z(Oz@A;gNDcX~YB$5=Cu*v^}4O;w^QlfzeTv$O?a@tn98i7LTxi@cc{tX_p%D+|!G3 zP}Psm9_R-?_*~eSNrDs1z6R3b`7}^Un{i2XRz5y0QIg8255o92X9IDA3!yQI>p9vV zYs^jS+=z}7<}4lnAWoTZmjQ6!@eF{~{2ca!jiu&{=)T{D7?JNrFccCnOxv%3Y-TZ$ zV#*R+Kzw&X5USb$RJbWO4QSY$oB$5IJ4i(i5riM;CJQ3!V@_J+^41S?ArtVVag|Mr%+fx}t-n+s4! zK>W)^Hf5ST0OBa`&p0ZJ|EooIF2o=@<@G}<{-FDS#0|OE0=o;pAT686Rqv%ZQJhD~ zqbA1E=Y-?f=e^K&`gaKyWHFiUI+%-5tS*R|c{OrkCeZT`^!1(daPKhtn}UP^wVqy9 z-UyPyh^nv#2mmr2QXePG1bb1jSK5;*Lq7;lxK(}${%Wm~`QSrSXtFTB49Cak@cu;f z0@tbFch?i0diybwKkO zEbW%-GSz*E=S}A;({GGk3#qu3B-38sl->)uYEyLc!F{0G-k$mI6SYG!7cL2J zcDaJT!N7d8A3kg}Sz9!;nBha@b+r#cbv?!Ox~_X01p@ln{Qd zCA44sy|P;5?OE3vO6rBc{dSUNQg+K|RyMwcY_stteX+&nA988+pSe`{2iqU*?S|BP zV0(ZBT)w*_HjmRBBn|oA#Se~!$T3%>KT8!T?=5_c?SE^=D>vbV7c=V z4>@1(ev?(5+wFe{bN4?}kUAh-i@+3e|0_EK=qmruP5m#nMmhli8P?GKhI982Lk58RD)gbSI9L&1PiF4n%Su|VoJx!*)B>Clbh{a|@Xl+$szliLD!%sPum@@%C z^FFqGRLnQK=jgjNfS;A5TYcPTAsu5mS<7@(<=vyl#Mdf+iO_mA8Svwt*_2z?SL*Icu{gS&dKI9k2?>@jQ<-x0R($7?;KgJkOnYk-(d&-oD zl3=*s|Ha}bLa$JOhc}Nj6_^OcELe5D^ui1TaebT8TWPG#85~!9SGBC=ZeQP6t7jS{ z_ObeSOUWB`xj!R!D%%}6;T`H&J|z?hh+tY)O@NzS+6cPZ+zCpz`V4#xC=lBWXz~v> z)sgS6z|Wns%*YlvDR%+cr8$~dk#cJg$E9C!%a0Vh+91#ap+HVtpxw6>8c3-J_&=J9 zOxEy`6P4SiNmk};R5NZMcsI4xKyyR`1SD(#V_{m+g|w<(0DT9}_%HY+V_7CoZr{1w zPVGMb=Of~}7t)w!Ar*x+=5Z;0voy^zE$mfuo-U%FGsLMoV!y1k&G)^b!#n5;sKJ=B zWzv%G{vyf!!_}#)un4EW4tjhiKC2Bq_vhaiI4#}5*rbK`tE;=5YHcBHe)_E3-ah@S z1kBF%3VDX?S`S@6(N1dXRtmZ?WZGN#^KSf^fHV2k-o&EaR>#qUAQSKrbv(=auRo$` zv1z8}`oGxk{7|~^ThuN%K$68#^k;A!vXFp@4g;FRA}=ncjYut zT<8PD1HO%#CXV*+JG8v}-6VGM1+H@GRgs*sl#*Y~u{)Ds5NIu9<|{sNpMiKL~`-#%4|=mburFKcZ; zC363eT7Bd+$02w8;LQmB3pW}usnrMAH{E%|1%S_wGhB}<@Lx9YzTAg8h!n&@t-d(~ zF@RtH{)9f+63yxSRGH0Vkktr1SOI*`&f~@hMvjSJ{%VgR|2ZM?e^nl4 zYz(wOybkEUyUrbikINlb0YrL^EPzz~98g?5ck~Rg@j{zb1Y)nC`*VW66w|M=`Za&B zVZX1kVxlxaUx-m>0l#a*N~P4uQD=}*P9f%S>aH^wE#Z%om`f*T3Nom3Z~ z5b43hOrtL7Tb28Yd=j498^Ag>Ta@A7>05-FS%-LTWNMmS-#FBSz+<_MAth^wM5|_2 zScC}pVG%~sdudH~+C3Dk;aWX28skBnN&J>QGbhLtpP!MU%nTx*8VWFqT}DkZX^Dpv z1acRs+1*8w2u*rRXrTMV)a&Ujrr(+CKIpBZi!Ai2&#fIqZ9mg=6M+Xqc}pM`1<2y- z#dAhK*es9)$|^*#z1Ndk0m z)TtGSU{bU&14P-M-bIN(fUC{!v;ICSdi3jg)nx8B(>D#pCs%W3Uu)(1(0Tw33|F75 zM~DaB;h2QYHeS;rndt8>J3Evt>fA1tTg@KBLPF&4!0zaJ8Xp<=+n|+9xa<&{R{#eRC5wC+tua{< z%Y5Sj1=9J<1@52lad_WNId>-Sc*r)#`tw`#OG)#p+;FQymFb=wN9HekEpceXeET_{ zkyVk+s{AtCR%MHQN-Xl93$Go-JgF&#G*PeS_}@}zBa-% zSWrB&?Atg$lhEL&?Mj=)mHIwTAK06A-qc&^TWcOaF@}jpjhBJEI9|yul6-VqcCqD? zU|zIk>P1`R#cVuBeYuhE=MQ<`q-%uT71KfrzlG!5GSsX@EsNr7vD)V6?d|fxYB9}C z$%PJAl8c%>2R}qA(OexRE-^5mWY*cwjH^_Km-GH-eWQP3c$P*=y`$r3jm>m%9O^}+ zEjm($fuiOHQ3uxDWZ1RHDMJ-Luu}K6f{V{hUTTEAYa$$vw`|m;UfGZDwIu*dgHvZX zyTL>CcOB|43y}w=KiERg%oq!SL+oWg42sv-lhLtuYKe^^A#2yqCuAvGr;xN(M+;Or z%?|mN_7gF(G8_+K`Nd&?PmU;SJGf1}EW*578VmH#==1faDZ5zl?kK-uTXi z#4g)lwG8>4`A6R_lN6|mB!XRt_XtLTW}Z`tIisu^0Cb=3N6(<2qZ50zO`IZckz6%> zzTR0ks+S)rwrn#(J;C$tYg&57Z3no;lsdBzSZSy7?ue-$@6FbU zPU`vubqY|+S2RHbqKsuDSQ|}+>)Hgnyk965SW68eR?cK6nK#`!u{l%8QX*4D+#WVk zO0lr?MMEAvim6?rC4VU)CVOfX3SX7IR8%#SD%F`ad&W-|u%;5j0}?~Q(bBA8ya3a9 z!HN&+tra?Z3E;O@^F)6dpA~vXY*2iWDRD2Sx56LhQM{I&zC!kE zPY=57?c*Q< zky3raCw%UOCQ6Wb@l#bWB7rlE+Uir;W;_oR+|cBN ziB72qEcMoAi4V&z$BC|W^l@=ydO?qYZ}-gB2H@fbb)za3&Fqq+E8v}7%Eli`i%VdE zORlxm0Kw4RZnZ1xN>PEx7*tb_an=)TM~wMWftJUDmCG%nZlMcQ7y2Mik#YW#=Hs}B-@>w85|@Ne zpjr8iHBCJpf&EFx!3F0j-b7q#u{qB|EKJ-zAM8E~uqL>IOk6Yh)-SMaiG zjqLYrIKH1e)!q{R_I;;ahMI7+NG3QXvzw~Ti8*oDcRmKSr?%0fQwo!-A~Qu zs>aXpZgw1Jq<7DrWJFPF$|7wU+w-}8lBTsuT^-`|pWx;c6amT?DMv#kZqGMA+qX+x z?~9fdIg6nJ{ip2i@Ph2h4+7{)%TMGFutiF7Os`UH!vie=q@nf|LiwY_Vw3X1=Uoo( zKjGy1ujIgUX8S{YEyl4Nd3EKR!loTv0~!Vh!bEL=(JE%hg8|x2cWHgS;TVF|X{SgI+C;pHz6Ujz zb6V&@)&%8M{Cg3}ZqY%>Te(ociCB-m2vA=?ZPp(aRt?}STQ1itjP>^GW*zfb?>q6L zfSX&aHbwA7HM_CNV%`CTl!uymg*T{VN%-Cz?-*vs?U#I2Q@4|QE3yVW%scpMefHI`xa;uRicGe zpG)-FW)pH6Ux!k2kw3$I*WHNv?8WfZHPMI)zpI;CS?pzQu9r|SKbR)2-7k(XR2mfv zk36Z6Eu#?B-8s*y9?&)^k#VDxwY(+R;#3^opoM}y?8sq+OqGni39{WLjjG(%LqW0Z zN{&rSiCA`pKLPp^`7*(a1M-3BkJC0Um)$uS=5&8Y2G@fBsTIT1^jLgK+ttJCOies# zcPbcXYCOi2;b9swVlI-H*JU=e{3sEMfJ)rHUKQ>V%J0F1R@KAXgPfpClE;wPtUKN| zgga6R#X0lKHfMP;`g#Xd3M7SQxkDk0Tt|4`OUKmmeZ7i1wBP$~<80amjY^@3$0eWdM!k3)fJ{e>nGT|tx!XKOT#HG6_X&=>w+1(B~URB;8VjhB1OB8Nb z*eK38R9K?zavCTGTFoO*38rgKnlI3YlO8KQ*mb!;Fuw;otOBERtsUCVfA58R=IM=>)RRe3l`?)@i)dA4hKz5o{ZZQ@s*Hyf? zUZ4#OD}3`5?kGzWEzw}Rk0utRvZXQCzM+29vGco`@?sV6hSNH_k$GIb| z4dg?7-{`(4^0@>jLvcYeIa**OQrT|-Db^zx7kvpvgRAyIERlssQTRkx$FT{3ueIGN zaEf4fk+jqn-xDoQRNBQEeORh#8M~XKENKziKDOYEQR`mPiR@?xZ*#>yY~)Dqruf=n zzdg{5v{%q+yVc$<;(Iohz5h`@M#yf`HF}fb8r{J;~c|u$>e$v=XHO-P>k<+ zt(vExhIL$ExWZ9XM0ecn8+X+m7zynV_#PY@8(v$X9be(xZ1O02 z$b1&<`|!%eeY`T+7O6ZPb#=h)2b;lJZ36UcqyeA}o1!+u)cG{wNZTe1pIU>~;p3vk z0S?uEAJHq9k@@o<`T?k1vT>kPe`>Z`s7ibz7N$RdSC(C>CfjL$<})#{{S7@i8>i+piC+lRJCJGks5T0Qx!iB==8svA{=*_>d%js9>99) zWS<73>Z&*Gu+%V_Z5!CJ7aw7A13AG*`8u%vvG|W0dm$H#Hj69|*P~P2_8Lf49*ip> z9tPk@U1!ysmXlRXi|?8{78%OjW5vIbV|B(mJT`y12g_1|`}c&tI1z`Ua*MjdQ$=6Z z!*;Se6cA#JVCQRPjkx>BD%r_V!m@5?^O>jzL=Kup@E+;T!V@QsU8(4>QG!WQ5>{L; z3WEC_mGN|X4oMDluyQPSZ6xoReH$HysT03pH4FqhC1mNW?B6uG*&?qWB?ND(+SA)w zqz5PM2Gja+>*FEn3hy=?yc{xIP$YaUT1-pC);iH1h7Ah(6p&ALyKA#& z*g_#jdQYMe@A+w^Vv;yG6M)XKYt{ijS4nY`8Y@)+}p?f;rj(#R*E~ZTr1D9H0 zduMFc0N7|n_a7Nbx_U3Vb01sFPN;n2&u|LQC-2dAJE|R?_xje z8Xp+e4R-EYm_l2?T6T+SeB{-ja5m#hVf9w*@F0u(cLMxM$mLHWdl6N|u>{JOMU!Ot z4_zA5iKH$~1yXWoxI*T-B^jp0OyM!dWJhT;f&SE4zzsO2Ao`#OiM}D<5y{XUqx;Z3exS-(3*kLfTmhKA@`_!k zJsIlukylOMhaYUBpf7ciu0Pmn^B3lhzzx2K(PDtnZ^T95&uBURU@Oz$UbX^;*I!oQ znSg9H&`kkMZ;f9S+;#sz`=?E~{{^P~#36{WPeQDBfsXWi>W>-ym9F{!Q4fuK7m!qc z1}I1OS4nc?H)IwNNB)E1PYmv|PG|pMleJ&?!FH%U$B$U+Wmx|zNjeEMm4As+WOX)z z4$S%4d;srC$*$#1AeqW~9^OVw5tzWVmGqJdA+msfgMK`f+C!v4KC*isKkNUk77!gX z1;F1a4;$TIoL+tMw>Q503lBi@$Nyj4H~vEkQ5x0Hll&wj6y=F%wOAuG?;+Ig#J#&x z31WXpKU%9{OMdy?^CddtxYnON#1|yIx8&sY1??`x6P7SzL9$1h&fR4dA8r~mK5bOf zQs4B91+;T=9Ntmax|W<8I9-yEXI(5j)K8bVJpTB$Q=|H)=u_8Y+ip>qUlVHbbaotL zhl3!DWqk`L%fJ%4P7A~HgH48;e3wvjZ>8gvAhJzjGMbgUWT+l>F4CH|+{t|2^NR6Y zY@P6seE=S)aV7bZESE{v6=4(8?E9lm$>NZDKzWq3qsRX!MCOCeNamUi+9n7sQa#Vj zxKtl43Kasy;=MI!gw@;Y3Ak4c$hTTA1E}Ws)7Q!>JA6)#Tk%f1ufTp0VvRKB9FI$h zjqqn*Xep^^i3Dx9^4r^H#VeMswQz(kMz7<9ZSY@vN;7eiiB8p1y=xKts{B#qx$&}E zK7&Kp@+y!3s~?OZ(CQD>f4>5ZujDz(`59De^DBi>UcPmkXZt(uAN%cv(9`sWYeOf3 zfN}md>!DcF5ipHejJ`<**ddtj`c8*mt=6%aB5z=5isbQagh)I`M~0CaqD*YLQuFWS ze4KE!sq~jm73zNd_L8NT}R98RC|nwUG~|bl9}Su*4Ei z0Kdp0y8F4rHnjZxEOWod>qiF3xH%eF^7}F!|8*S1STt^X2s{a}?)kH(Ei__qu$+{tTnxnIpw$)X3rXpIJpNX3;0iX2x^(Z?QN60= zfp@~5?`-M4xSO`|2d#RUvO-3RYx@~Xqlx3}ZcTnr_n0Bf#xjQ864nFn%5yE*&Vi^U zwmx>4l5Ofg6z7g>1JdcwQ}mLYF_zFLkhekL0wF8Nw}m*>Q7to$^7Qlz3kwL7i->)9 zufCq>uOj1}NfuI;dHJl!Go(0{K6idoT}tEX_hjhw=? zTUIWB^Tx~9LS%(ZEj*>u9rYJNnLP2VFs48A8M*8=O1I)*{%FQ$HPyW_%|5LZZGeR2Q@lU{%S`_~fh)n;_{&Vw3hV%H1RhE^2 zA(MEp=3D?ktYS;F)`NWLYh=z9+uQZdL};AoK;YMD(};)Co~cwxEz#WI=A>}{MELV8 z(;Kgbdp~?Fv!`GKtmmnZrItc>O6M+yR#NW5}9eba(&))mqbAIRCG0wSXkHH^bAj!w~ zzR%m9IiERa%m9yW|6ZbhOi7>!+Cm0!*804Xx%`U~W5&z}C1Z9SnE;DZKYgX6dwR}_ z3dm%w;)kilW4_;mKM(!O69ZOaBCxe0fUPrZ4bUR8YUi;%<{L5RI{%X8({7b*S{d4Dh%Ju;iHyJPG^e^&qEI@ANS##KX-Iti$l({GI zf+4&qgRS|U^^>O#f`u-cXKT2cQWpoiioOGYQFiQKLOI43x0D6i8(xO3Rl`XAlFPw+ zPuFP1zhlR+=`m`dM8x;kKods+zZnJrtN-r~!+j!u_p5$sr7-|su0~U&g#tP0>u(km zVTzYwE6g-Y&J#H31EFs*@1nr(tyOWUg4AL2@Mg5i{_Vz>pq@WJqvQ{| z@79jw0AN><%pd6y)Pi% zSk2GZ0kNEHs zX6wHMh@fo|MNqR#u%#$1`E2d=jL!Pa$JYcBloG!r=v`g7D-fmd^T%K2q!hXBunP04 z#QDj@S>}Mkb&y+NnDhqzFTERqq%jyfaZFRz$$p|Q@2ze|K75sR`_Zkv@H$Z~bD47s z7MdA9^<2j)Eap)a!@%UOh1nTuE@l^j{mU%zDfzF$+3@dl{Q19no@D4ebx!#QT^8t% z9UaQwot}gv8o-iO%AzYNv&NLMa(%qxd8ieBEq1FKy^gjQ?pk0ZI^^g;@@@z2>&M<%) zqVD{IF3rT32bUiY`W6om6&e4$AfEZF=lcIUO!s$V__<(9c|~W`{E<{`A_Jw1IT2lD zU^qH>z$0m7iuo33d#ze(0wq+a1YcMP;ZO*^$MfrV9s}ri@_oV~iwovi!SL~UYn20$ zp*INA)N6Yt72Izhr)zKog2@e(+LzCrZmZZ6L08uD)7=c+J7~mnxC|GUij!o#R>3 zA4KuQ{qiB>7NhKbrrvc`pPcd^bc&QI)h?1W7s>Z3UevNAc9@c{F+?2qyLhqvx`t;S zp>7;vWxk@~fl+}bbiRpMy9~wJ=!weyTC=zMZ#e1vr>hzNv6PtqD;$Ut5-?xdY4F27 z>jP`a(PG{KU1%Tmsx-~83Yg@`zF0Bf#FI*V^Bh#k#6$b($m%583$~vlMmGxRg@9s* zXIg271LW7hsu52Cn59*>yD>;=D-HLOnKv1^gn7yG>HPJ@P2f;i15izix#zvwuUsAb z+hT|R&?)cqpO1{7ACv!&IjsMii4h`aDquX&dV`CgOW5|vnX}*r;IpA7FCu@-Q{xlF zky5}=gXV9n-A84`s8c|b1|S;m9=&O$Sq8#M)l004X-ERzB+}Hfts=UkXv;_%+mE5{}r&^aw3% zuhfaCTKMf?+j-Ne{N2wv%#4O`uk%JKFB3-7CvStxIL{=Y5@K~Ym6|*H3)ap2V&>K? z!zTo$SMZ`%729`>SRG0$oc)!|ip4U_UGxfXvunez-P09)_CedPLT-(nYb;&jHtS}2 zxaiV0<1+vAfWb3B-M(bNpyG0DS`l%vhYVs=11%LXZbs7Oc4sUG3{j&(3bntX~ z1{saKKW$F-d*75BrYCc zhzTekY_OYw#xE}*OXeX*Cp8sLkuAPS)!}M6EpnsmjU@iKNoBeCT*KC=oe`lpcBi}H z%9>an=Iy&?sn-G74)>|mRJ+lDIgj3_8L?1Pz5Gzi5|rWx0Me`c9G}qT21smFm&B)3 zIsrb`rg?^ir)5pc2umw^7Si(VD!FTRAId`L-y6|Wsqv{bym=Vb&Lt|{KCEFszRI%h|<%C#|d(mgq3+JUcKZNh28 zxXKXswy*EAVZpLhUPzE=r^aB9QJ#m1pIuW%Mt!i#-otdnxMgh74K0Wxg}015 zExIPF81NhYGCfvi2<`xg1wvjKtK08~(6gJcwQT!&lcLUi6Q?=+> z#`KgT_OiUmQLTr3_O38iioSW)>6j`GO`}lz^;!8?mHHS5 zr&MpPW7K$HxxxOVd8HwHz4?6#8i==hpIFg}OX)_{a z@&5)Y#&*CApkmv@X;XQ)WGpk4pX3lvbn6PDU5>_U=ekse-zN^mFWYQ|^HzNhHz8r+ zet7Ba0YC*Ylp|XpS9bjyHWzi@zgiD_BWaZ(XUcdiDV(3VGmTkg=q)s5k5$5}A!$Nn zlPr-<;r($voT8d<=+#1?2Uxu zFXGDy@8TuiubCBRJ^t#a5gb>MbrMwLmiQj;B8Cys#rvT*9)D@LV2+e_alDLQ2RGIc zcXr9}%q2M(eI&)QnQJ*B0T>()%>X~?j#f?&(Y5~2u=6KBv z+M?#CwCIxQb+FT`np!EfN;ZF`r+EZn`04~H{dTuG%FsG*6o95WClFxxh2iNj!1)Nj zscjvr2TjrJwuFOg)P9X0bG}=kYSsPyselqFmKgE~I&kVfBlub*i&Bk;wbuY+Qs-39 zM2Xl0vAI{3Kc?c+rK+*_OAcq~br|E!0zHSN@qOE#IVYnFza6WZp=%~ zT%LPTc9_IfS)gxrySE?9!<0j8b4n<&L{5l|cDsJZTwmsot~JcL#bW;UZU!o~%d~dO zB|0kuWk$bcXn1`o{IYF4@sPRK2B;--dI>nd$%`%BwGI`R>n6zTu;J~jE|Zx zyaR6$cxoTVJd89TTnG@!wVT}8-BMGPr-tq(V0$3WRSph4_;y+I!rtED;nt=&nFb~L z6u`RsPb%GKl3UN7dzsK-elGC>BKgRnK_|y)C*OxorsrGLc82_i!bT$7gWpBocc*)O?$S>;Sk5q2pSp49H!psZW{bDb z-{{NPB5_j(fU6Mw!3oUqW!5RNVU;ey`a!YTK>=9*J3fbzNPRCAz=l z09A|10$V;>V5FaN#J?r{h?X0)o1&t33u?>`}9BO3cA|WGkuTODUz;d(Wl}SkEUUp zB|qrCYB$+RKR6rk{@*nN{N=>@e{5FfAO9od*pCls3_*60^Nwe%OTB-NC)iSc8MP^% z^rqe_qNm*erPPLVeCqITC0NAAa@w?0Y?->5o&8AM%#5=es$PQc0_lhbtV?lqqya*F zx9_FM4-3qcY?5#n9}96YjV1(XbhtUz2J$4GAH##5%QXisUWMlNfL-G&7u*xWO5BO< zL&cC{5AH-b`Cge$u%ATG&Z)ia|EP1+A9F2#G!6J`O#Gkl_rD7$=l}6vTPZcwk;+c5 zy9ZiAED}u^G)9^}p0t2G-Dbb6@z6A=KY6)9hh$Thi#&j`z+)a-A?zDa4D0p!%s;qh zJjzMi_Pds`tsfDsXKecU_2(A*yzUE-FM^YIs&4z7nv<=3P+eDkj(N-XC->m{VwKN%?Gh6?e}b;I^6mikm(09 ze*JWR_2STRx@5@}P8wT={etE2I6B!$EQ7v@c)I8T4lHF^?C>szI)h09FJ>gi)LVuF z)6#`Kaxkr8xrS9(Q|f4t%)q)x&DVnuS7*7nK~{w9yD|(?W5cxDKQuqkc}& zq|Eq^+DBMJaEgYoO}N+z;vhntppl8#r16pr zaTVb_lt2=FFOKQ;+e{OpTcR|TnUKy-c15^6Au&xR`V#=2<)Zt{jJ}_CqFK*^@coTBB+q1O>`(bOkXZ#nV}b@xZ5jZx2`Ly z%0D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�)kCod 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 64 G| 40 | + +BBC 机型默认不支持 ENI,主网卡允许的最大辅助 IP 数量为 40 个。 + +### 1.1.2 单机最大 ENI 数量 +EBC/BCC 机型最大 ENI 数量遵守[BCC节点规格定义](https://cloud.baidu.com/doc/BCC/s/wjwvynogv),而BBC 机型最大 ENI 数量为 1。 + +### 1.2 适用场景 +* 节点上需要创建更少的容器,方便保证单个容器的服务质量。 +* 节点上需要创建更多的容器,甚至需要超出上述 BCC/EBC/BBC 机型的 IP 规模约束,方便保证集群的资源利用率。 + +## 2. 设计方案 +CCE 上通过为 Node 添加 `network.cce.baidubce.com/node-eni-max-ips-num` Annotation 实现给 Node 自定义 ENI 最大辅助 IP 数量。例如: +```bash +kubectl annotate node cce-node-01 network.cce.baidubce.com/node-eni-max-ips-num="40" +``` + +当用户未通过 annotation 指定ENI最大IP数量时,cce-network-operator 会使用上述算法为 Node 自动分配 ENI IP 数量,并尝试将 IP 分配给容器。当用户指定了 ENI 最大 IP 数量时,cce-network-operator 会使用用户指定的数量为 Node 创建 ENI。 + +CCE 上通过为 Node 添加 `network.cce.baidubce.com/node-max-eni-num` Annotation 实现给 Node 自定义最大 ENI数量。例如: +```bash +kubectl annotate node cce-node-01 network.cce.baidubce.com/node-max-eni-num="1" +``` + +## 3. 使用限制 +* `network.cce.baidubce.com/node-eni-max-ips-num` 应大于子网可用 IP 容量,请在设置自定义 ENI 最大 IP 数量时,做好集群网络规划,注意子网可用 IP 容量。 +* `network.cce.baidubce.com/node-eni-max-ips-num` 指定的辅助 IP 数量不可超过实际 ENI 最大 IP 数量的规格配额。如有提升配额需求,请联系百度智能云客服支持。 +* BCC/EBC/BBC 每个计算实例可支持 e * (n - 1) 个辅助 IP。 + * e 表示节点最大支持的 ENI 数量。 + * n 表示每个 ENI 最大支持的 IP 数量。,默认每个 ENI 实例最大可支持 40 个辅助 IP。 +* `network.cce.baidubce.com/node-max-eni-num` 实例支持的最大 ENI 数量不允许超过实例的规格限制,否则会导致实例无法正常创建 ENI。 +* `network.cce.baidubce.com/node-max-eni-num` 不可小于实例已经绑定的 ENI 数量。 \ No newline at end of file diff --git a/cce-network-v2/docs/vpc-eni/primary-interface-with-secondary-ip.md b/cce-network-v2/docs/vpc-eni/primary-interface-with-secondary-ip.md new file mode 100644 index 0000000..9d97a39 --- /dev/null +++ b/cce-network-v2/docs/vpc-eni/primary-interface-with-secondary-ip.md @@ -0,0 +1,169 @@ +# 主网卡辅助 IP 模式 +为了达到简单且一致的产品体验,主网卡辅助 IP 模式在不支持 ENI 的机型上,例如 BBC 和部分 EBC 机型,可以利用在主网卡上配置辅助 IP 的方式,实现每个 Pod 都有一个原生于 VPC 的 IP 地址身份的能力。 + +## 优势 +主网卡辅助 IP 模式,在主网卡上配置辅助 IP,然后通过 ENI 的 SecondaryIP 特性,将辅助 IP 绑定到 ENI。辅助 IP 与网卡的主 IP 使用相同的子网,因此可以保证辅助 IP 与主 IP 在 VPC 中的同等地位。 +与 ENI 辅助 IP 模式相比,主网卡辅助 IP 模式下舍弃了策略路由能力,因此这种模式的数据路径更为简洁易懂。且在主网卡辅助 IP 模式下,可以支持所有机型,拥有更强的兼容性。 + +## 使用限制 +* **请保证实例所在子网 IP 地址空间充足**,至少预留 n * c 个辅助 IP 地址容量的子网空间。 + * n 是所有机器的数量。 + * c 是每个机器最大可分配辅助 IP 数量。 + * 子网容量应在购买机器前提前规划,一旦机器加入集群,无法再修改子网。 +* BBC/EBC 每个实例最多支持 39 个辅助 IP 地址。当节点调度了超过 39 个 Pod 时,CNI 无法为 Pod 分配 IP 地址。 +* 节点加入 CCE 集群前,请确保是纯净的节点,即节点上没有运行过任何容器。推荐将节点执行操作系统重装。 +* 使用 VPC-ENI 模式,推荐内核版本为 5.7 以上。 + +# 实现原理 +主网卡辅助 IP 模式在技术实现上为 ENI 增加了一种新的使用模式 `PrimaryWithSecondaryIP` 。BBC 机型默认使用这种模式,而 EBC 机型则根据百度云 EBC 产品的定义,自动决策使用 `PrimaryWithSecondaryIP` 还是 `Secondary`。 +## BBC 机型案例 +BBC 机型默认使用 `PrimaryWithSecondaryIP` 模式,其网络集合对象实例如下: + +```yaml +apiVersion: cce.baidubce.com/v2 +kind: NetResourceSet +metadata: + annotations: + cce.baidubce.com/ip-resource-capacity-synced: "2024-02-21T02:26:28Z" + generation: 3 + labels: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/instance-gpu: "false" + beta.kubernetes.io/instance-type: BBC + beta.kubernetes.io/os: linux + cce.baidubce.com/baidu-cgpu-priority: "false" + cce.baidubce.com/gpu-share-device-plugin: disable + cce.baidubce.com/kubelet-dir: 2f7661722f6c69622f6b7562656c6574 + cluster-id: cce-z85m1xdk + cluster-role: node + failure-domain.beta.kubernetes.io/region: bj + name: 10.0.19.7 + ownerReferences: + - apiVersion: v1 + kind: Node + name: 10.0.19.7 + uid: 45341763-2b46-472b-884e-695974a67eaf + resourceVersion: "13952347" + uid: d479844c-5519-4d08-a7bc-f850179d3311 +spec: + addresses: + - ip: 10.0.19.7 + type: InternalIP + eni: + availability-zone: zoneD + instance-type: bbc + maxAllocateENI: 1 + maxIPsPerENI: 40 + preAllocateENI: 0 + useMode: Secondary + vpc-id: vpc-w829y8wpni8i + instance-id: i-1SvcOgBl + ipam: + pool: + 10.0.19.8: + resource: eni-ih1seearb7y8 + subnetID: sbn-kx327kp355ni + 10.0.19.9: + resource: eni-ih1seearb7y8 + subnetID: sbn-kx327kp355ni + 10.0.19.10: + resource: eni-ih1seearb7y8 + subnetID: sbn-kx327kp355ni + 10.0.19.11: + resource: eni-ih1seearb7y8 + subnetID: sbn-kx327kp355ni +status: + enis: + eni-ih1seearb7y8: + cceStatus: ReadyOnNode + id: eni-ih1seearb7y8 + subnetId: sbn-kx327kp355ni + vpcStatus: inuse + ipam: + operator-status: {} + used: + 10.0.19.10: + owner: kube-system/nfd-worker-f244k + resource: eni-ih1seearb7y8 + subnetID: sbn-kx327kp355ni +``` + +上面的实例中,以下字段域需要注意: +* `spec.eni.useMode` 字段为 `Secondary`,表示使用 ENI 辅助 IP 模式。 +* `spec.eni.instance-type` 字段为 `bbc`,表示使用 BBC 机型。 +* `spec.eni.maxIPsPerENI` 字段为 40,表示每个 ENI 的最大辅助 IP 数量 +* `status.enis.eni-ih1seearb7y8` 代表一个 ENI 对象,其中 `id` 为 ENI ID,`subnetId` 为子网 ID。百度云每个主机的主网卡,也是一个抽象的 ENI 对象。 + +### BBC ENI 实例 +BBC 实例的 ENI 对象示例如下: +```yaml +apiVersion: cce.baidubce.com/v2 +kind: ENI +metadata: + creationTimestamp: "2024-02-21T02:26:28Z" + finalizers: + - eni-syncer + generation: 2 + labels: + cce.baidubce.com/eni-type: bbc + cce.baidubce.com/instanceid: i-1SvcOgBl + cce.baidubce.com/node: 10.0.19.7 + cce.baidubce.com/vpc-id: vpc-w829y8wpni8i + name: eni-ih1seearb7y8 + ownerReferences: + - apiVersion: cce.baidubce.com/v2 + kind: NetResourceSet + name: 10.0.19.7 + uid: d479844c-5519-4d08-a7bc-f850179d3311 + resourceVersion: "13952271" + uid: 0d1631b9-68ff-42d0-a1b0-dcb9922fd55f +spec: + id: eni-ih1seearb7y8 + instanceID: i-1SvcOgBl + macAddress: fa:20:20:31:4e:53 + name: eth0 + nodeName: 10.0.19.7 + privateIPSet: + - primary: true + privateIPAddress: 10.0.19.7 + subnetID: sbn-kx327kp355ni + - privateIPAddress: 10.0.19.10 + subnetID: sbn-kx327kp355ni + - privateIPAddress: 10.0.19.11 + subnetID: sbn-kx327kp355ni + - privateIPAddress: 10.0.19.8 + subnetID: sbn-kx327kp355ni + - privateIPAddress: 10.0.19.9 + subnetID: sbn-kx327kp355ni + subnetID: sbn-kx327kp355ni + type: bbc + useMode: PrimaryWithSecondaryIP + vpcID: vpc-w829y8wpni8i + vpcVersion: 1 + zoneName: cn-bj-d +status: + CCEStatus: ReadyOnNode + CCEStatusChangeLog: + - CCEStatus: ReadyOnNode + code: ok + time: "2024-02-21T02:26:47Z" + VPCStatus: inuse + VPCStatusChangeLog: + - VPCStatus: inuse + code: ok + time: "2024-02-21T02:26:28Z" + interfaceIndex: 2 + interfaceName: eth0 + vpcVersion: 1 +``` + +上面的实例中,以下字段域需要注意: +* `spec.useMode` 字段为 `PrimaryWithSecondaryIP`,表示使用主网卡辅助 IP 模式。 +* `spec.type` 字段为 `bbc`,表示使用 BBC 机型。 +* `spec.subnetID` 字段为 ENI 的子网 ID。ENI 的主 IP 一旦确定,子网 ID 不可变更。 +* `spec.privateIPSet` 字段为 ENI 的辅助 IP 列表,其中 `primary: true` 表示该 IP 为主IP. +* `status.interfaceIndex` 字段为 ENI 的接口的网卡设备索引。 +* `status.interfaceName` 字段为 ENI 的接口名称。 +* `status.vpcVersion` 字段为 VPC 的版本号,用于与 VPC 做状态同步使用,默认每 20s 与 VPC 做一次状态同步。 +* `status.VPCStatus` 字段为 VPC 的状态,`inuse` 表示 VPC 记录的网卡可用且在使用中。 +* `status.CCEStatus` 字段为CCE的单机引擎记录的 ENI 状态,`ReadyOnNode` 表示 ENI 记录的网卡可用且在使用中。 diff --git a/cce-network-v2/docs/vpc-eni/vpc-eni-cluster-add-bbc.md b/cce-network-v2/docs/vpc-eni/vpc-eni-cluster-add-bbc.md new file mode 100644 index 0000000..b81084b --- /dev/null +++ b/cce-network-v2/docs/vpc-eni/vpc-eni-cluster-add-bbc.md @@ -0,0 +1,85 @@ +# VPC-ENI 集群添加 BBC 节点操作案例 +## 1. 背景信息 +本案例介绍如何添加一个 VPC-ENI 集群的 BBC 节点。在 CCE 限制内,通过自定义参数扩容集群的方法,将 BBC 节点添加到集群中。 +### 1.1 使用限制 +* 请确认已有节点和集群在同一个 VPC 中。 +* 请确认已有节点所在的子网有足够的空闲 IP 资源,请提前做好资源规划。 + * 加入到 CCE 集群的 BBC 节点最多需要分配 40 个 IP 地址 + * BBC 节点最大仅允许创建 39 个使用独立 IP 地址的容器 +* 一个计算实例最多只能加入一个集群。移除集群后,建议重装操作系统以清理相关残留配置。 +* 请确认在节点加入集群前已经完成了重装操作系统操作,推荐内核版本为 5.7 以上。 +## 2. 操作步骤 +### 2.1 CCE 集群添加节点 +请进入 CCE 控制台,在集群列表中,找到目标集群,点击集群名称,进入集群节点管理页面。 +![点击添加节点按钮](./images/scale-cluster-bbc-0.jpg) +### 2.2 使用自定义参数扩容集群 +点击“使用自定义参数扩容集群”链接,进入自定义参数扩容集群页面。 +![点击使用自定义参数扩容集群按钮](./images/scale-cluster-bbc-1.jpg) +接着会自动弹出出入框,请将以下内容复制到输入框中。 +![复制自定义参数](./images/scale-cluster-bbc-2.jpg) + +### 2.3 自定义参数扩容集群 +在自定义参数扩容集群的输入框中,可以通过以下内容模板,替换关键的实例名和密码信息,将已有的 BBC 节点添加到集群中。 + +```json +[ + { + "instanceSpec": { + "adminPassword": "***", + "existed": true, + "existedOption": { + "existedInstanceID": "i-qke6VO0K", + "rebuild": false + }, + "machineType": "BBC", + "clusterRole": "node", + "deployCustomConfig": { + "kubeletRootDir": "/var/lib/kubelet", + "preUserScript": "", + "postUserScript": "", + "enableCordon": false, + "kubeReserved": { + "cpu": "50m", + "memory": "100Mi" + }, + "systemReserved": { + "cpu": "50m", + "memory": "100Mi" + }, + "relationTag": true, + "containerdConfig": { + "dataRoot": "/home/cce/containerd" + } + }, + "instanceResource": { + "cdsList": [] + }, + "vpcConfig": { + "securityGroupType": "normal", + "securityGroup": { + "customSecurityGroups": [], + "enableCCERequiredSecurityGroup": true, + "enableCCEOptionalSecurityGroup": false + } + }, + "labels": { + "cce.baidubce.com/gpu-share-device-plugin": "disable", + "cce.baidubce.com/baidu-cgpu-priority": "false" + }, + "tags": [], + "taints": [], + "relationTag": true, + "instanceOS": { + "imageType": "System" + }, + } + } +] +``` + +上述模板中,需要替换的关键信息如下: +* `adminPassword`: 必须字段。主机密码,请使用符合密码规则的字符串。如果密码不正确,会导致主机无法加入集群。 +* `existedInstanceID`: 必须字段。待加入集群的节点 ID。请在 弹性裸金属服务器 -> 控制台 -> 实例列表 中,找到待加入集群的节点 ID。 +* `taints`: 可选字段。节点污点标签,请根据需要填写。 +* `labels`: 可选字段。节点标签,请根据需要填写。 +* `deployCustomConfig`: 可选字段。节点自定义配置,请根据需要填写。 diff --git a/cce-network-v2/docs/vpc-eni/vpc-eni-node-spec-subnet.md b/cce-network-v2/docs/vpc-eni/vpc-eni-node-spec-subnet.md index 4afda83..61cf4a9 100644 --- a/cce-network-v2/docs/vpc-eni/vpc-eni-node-spec-subnet.md +++ b/cce-network-v2/docs/vpc-eni/vpc-eni-node-spec-subnet.md @@ -10,7 +10,7 @@ VPC-ENI 模式容器网络,容器默认使用创建集群时通过 `eni-subnet ## 2. 设计方案 CCE 上通过在创建 Pod 前为 Node 添加 `network.cce.baidubce.com/node-eni-subnet-ids` Annotation 实现给 Node 指定子网分配 IP。例如: ```bash -kubectl annotate node cce-node-01 network.cce.baidubce.comnode-eni-subnet-ids="sbn-xxxxxx1,sbn-xxxxxx2" +kubectl annotate node cce-node-01 network.cce.baidubce.com/node-eni-subnet-ids="sbn-xxxxxx1,sbn-xxxxxx2" ``` 为了兼容已有 BCC/EBC 集群使用集群子网的默认逻辑,在用户没有设置指定节点子网时,依然能够快速就绪工作,`cce-network-v2-config` 新增 `enable-node-annotation-sync` 配置项,默认为 false。如果 `enable-node-eni-subnet` 为 true,则开启 Node 到 nrs 对象的 annotation 同步逻辑,当用户为 Node 添加 `network.cce.baidubce.com/node-eni-subnet-ids` Annotation 时,会同步到 nrs 对象中。 diff --git a/cce-network-v2/docs/vpc-eni/vpc-eni.md b/cce-network-v2/docs/vpc-eni/vpc-eni.md index 1a1c282..7a46a6e 100644 --- a/cce-network-v2/docs/vpc-eni/vpc-eni.md +++ b/cce-network-v2/docs/vpc-eni/vpc-eni.md @@ -2,7 +2,9 @@ vpc-eni 是CCE容器网络提供的标准容器网络模式之一,其特色能力如下: 1. 完全打通百度智能云VPC,Pod的IP地址是ENI的辅助IP,地址分配自VPC子网。 2. 支持固定IP和跨子网分配IP策略 -3. 兼容BCC/BBC机型 +3. 兼容BCC/BBC/EBC机型,同时兼容主网卡辅助 IP 和 ENI 辅助IP模式 +4. 支持IPv6 +5. BCC 支持跨子网分配 IP # 1. 关键数据结构 ## 1.1 NetResourceSet 每个core/v1.Node都有一个对应的NetResourceSet对象,描述节点上已经绑定的ENI和ENI辅助IP与Pod之间的分配情况,并从core/v1.Node同步便签. @@ -112,7 +114,9 @@ status: ## 1.3 ENI ENI对象用于描述VPC内的ENI弹性网卡,通常ENI会绑定到一个BCC虚机,由CCE管理并将ENI辅助IP分配给Pod。ENI是有配额的,一台BCC最多可绑定8个ENI。CCE上的ENI不允许删除和解绑,只有在NetResourceSet删除后,`eni-syncer`才会自动清理ENI对象。所以在设计上,ENI对象一定属于某个NetResourceSet对象。 -> 在BBC中,主网卡默认也是ENI +每个 ENI 都最少绑定了一个安全组,用于设置容器网络的基本安全策略。修改安全组前,请确认安全组不会与容器网络流冲突,否则可能会导致容器的网络数据包被安全组规则拦截。 + +> 在BBC/EBC中,主网卡默认也是ENI ``` # kubectl get eni eni-m90jdaxu30f1 -oyaml @@ -181,11 +185,13 @@ status: * `privateIPSet`: ENI关联的IP列表,每个ENI都有一个主IP,并有多个辅助IP,总体遵守[VPC中对ENI的配额规则](https://cloud.baidu.com/doc/VPC/s/9jwvytur6)。与ENI的某个内部IP有绑定关系的EIP也会在这里同步。 * `IPv6PrivateIPSet`:ENI关联的IPv6地址列表。 * `subnetID`: ENI 主IP所在的子网。 - * `type`: ENI的类型,包含`bcc`/`bbc`两个可选项。 + * `type`: ENI的类型,包含`bcc`/`bbc`/`ebc`三个可选项。 * `zoneName`: ENI的可用区。 - * `useMode`: ENI的使用模式,支持`Primary`独占ENI;`Secondry`ENI辅助IP两种模式。 + * `useMode`: ENI的使用模式,支持`Primary`独占ENI;`Secondry`ENI辅助IP两种模式;`PrimaryInterfaceWithSecondaryIP`主网卡和辅助IP模式。 * `routeTableOffset`: 辅助IP模式下ENI使用的独立路由表偏移量。具体路由表ID的计算方法是 routeTableOffset + eniIndex。 * `installSourceBasedRouting`: 是否为辅助IP安装策略路由规则。该属性用于控制在辅助IP模式下使用cptp插件时的策略路由的设置。 + * ENI 辅助 IP 模式支持安装策略路由规则 + * 主网卡辅助 IP 模式由于只有一个网卡,不支持策略路由。 * `status` * `CCEStatus`: ENI在单机上的状态。`Pending`: 单机还未发现ENI;`ReadyOnNode`: ENI已经在节点就绪; `UseInPod`: 独占ENI已经分配给Pod。 * `CCEStatusChangeLog`: 状态变更历史,最多保存10条。 @@ -194,7 +200,44 @@ status: * `gatewayIPv4`: ENI 的路由网关地址。 * `interfaceIndex`: ENI 的接口索引,指的是接口在操作系统上的序号。 * `index`: ENI 在单机上的排序序号,从0-253. - * `interfaceName`: ENI 在单机上的接口名,命名规则是 `cce-eni-{interfaceIndex}` + * `interfaceName`: ENI 在单机上的接口名,命名规则是 `cce-eni-{interfaceIndex}`。主网卡辅助 IP 模式中,接口使用操作系统的默认命名规则。 ### 1.3.1 vpcVersion 同步机制 -ENI 资源默认180s与VPC做一次同步,虽然同步周期较长,但`vpcVersion`机制为了保证ENI数据的时效。`vpcVersion`是基于事件的同步,每当集群中有辅助IP申请或释放事件,会立刻触发ENI与VPC的同步。 +ENI 资源默认20s与VPC做一次同步,虽然同步周期较长,但`vpcVersion`机制为了保证ENI数据的时效。`vpcVersion`是基于事件的同步,每当集群中有辅助IP申请或释放事件,会立刻触发ENI与VPC的同步。 + +# 2. 使用限制 +VPC-ENI 模式使用限制如下: +* 容器网络的 ENI 不支持用户手动管理,仅支持自动创建和随云主机销毁而自动释放。 +* 已有 ENI 的云主机无法加入 VPC-ENI 容器网络。因为已有 eni 的云主机所绑定的子网可能不在容器网络管理的子网范围内,影响正常的 IP 地址分配。 + +## 2.1 可用区限制 +* VPC-ENI 模式所有的节点都必须在同一个 VPC 内 +* VPC-ENI 模式仅允许添加有可用容器子网的可用区的云主机 + * 每个子网都有可用区属性,例如 zoneA。 + * 每个云主机也都有可用区属性,例如BCC 云主机属于 zoneA。 +* ENI 和云主机实例必须属于同一个可用区,不可跨可用区使用容器子网。例如在 zoneB 的云主机使用 zoneA 的子网。 + +## 2.2 VPC子网 配额限制 +* 每个VPC最多可创建10个子网。 +* VPC 一旦创建,地址空间则不能修改,能容纳的IP地址数量亦不能修改。 +* **子网一旦创建,可用区和地址空间则不能修改,能容纳的IP地址数量亦不能修改。** + +## 2.2 弹性网卡配额限制 +* **ENI 弹性网卡一旦创建,只能选择加入某个VPC下的一个子网,不能指定IP,也不支持修改子网移出。** +* 每个VPC弹性网卡最大数量500个。 +* 单网卡上IP数量最少1个,最多40个。 +* 云主机可挂载弹性网卡数量=min(主机核数,8)。 +* 云主机绑定的网卡上可配置IP数量。 +* 注意 BBC 和部分 EBC 机型不支持挂载 ENI。 详情查看[主网卡辅助 IP 模式](primary-interface-with-secondary-ip.md) + +| 内存 | IP数量| +| -- | -- | +| 1G | 2 | +|(1-8]G | 8 | +| (8-32]G | 16 | +| (32-64]G | 30 | +| 大于64G | 40 | + +## 2.3 操作系统限制 +* 节点加入 CCE 集群前,请确保是纯净的节点,即节点上没有运行过任何容器。推荐将节点执行操作系统重装。 +* 使用 VPC-ENI 模式,推荐内核版本为 5.7 以上。 \ No newline at end of file diff --git a/cce-network-v2/docs/vpc-route/vpc-route.md b/cce-network-v2/docs/vpc-route/vpc-route.md index 0257a26..65c9ee9 100644 --- a/cce-network-v2/docs/vpc-route/vpc-route.md +++ b/cce-network-v2/docs/vpc-route/vpc-route.md @@ -1,6 +1,21 @@ # VPC Route 网络 -## 1 关键数据结构 -### 1.1 NetResourceSet +## 1. 功能简介 +VPC-Route 模式,将容器IP分配给VPC路由表,通过VPC路由转发到Pod IP。这种模式的容器网络有以下特点: +* 容器IP范围和 VPC CIDR 范围不同,但是 VPC 内所有实例可以访问容器IP。 +* 单机容器 IP 数量无限,可以灵活的扩容。VPC 路由模式的容器网络,每个节点上可以分配一个或多个 CIDR,每个节点上可以分配的容器IP数量是节点上多个 CIDR 的可用 IP 数量。 +* 使用分布式 IPAM,节点和容器弹性能力强。 +* 性能较高,容器使用 underlay 网络无额外封装性能损耗。 + +> VPC 路由模式的容器网络每个节点都有独立的 CIDR,所以不支持固定 IP 等网络特性。 +## 2. 使用限制 +### 2.1 操作系统限制 +* 节点加入 CCE 集群前,请确保是纯净的节点,即节点上没有运行过任何容器。推荐将节点执行操作系统重装。 +* 使用 VPC-ENI 模式,推荐内核版本为 5.7 以上。 +### 2.2 规格限制 +1. 默认每个VPC的路由表条目最多50条。可以通过配额中心申请提高配额。 + +## 3 关键数据结构 +### 3.1 NetResourceSet ``` apiVersion: cce.baidubce.com/v2 kind: NetResourceSet @@ -46,7 +61,7 @@ status: * * `status.ipam.vpc-router-cidrs`:PodCIDR和VPC路由表之间的同步状态。 -### 1.2 CCEEndpoint +### 3.2 CCEEndpoint ``` apiVersion: cce.baidubce.com/v2 kind: CCEEndpoint @@ -87,18 +102,18 @@ apiVersion: cce.baidubce.com/v2 ``` CCEEndpoint 记录了网络端点的状态信息。 -## 2 控制逻辑 -### 2.1 节点就绪 +## 4 控制逻辑 +### 4.1 节点就绪 只有所有CIDR成功写入VPC路由,节点才就绪。 如果CIDR未成功写入VPC路由,则cce-network-v2-agent Pod的健康检查会失败,并有以下日志: ``` level=error msg="fail to health check manager" error="cluster-pool-watcher's health plugin check failed:no VPCRouteCIDRs have been received yet" subsys=daemon-health ``` -## 3 数据面 +## 5 数据面 vpc 路由使用cptp网络驱动,它具备以下特征。 -### 3.1 容器内 -#### 3.1.1 容器地址 +### 5.1 容器内 +#### 5.1.1 容器地址 使用32/128位的地址,不归属任何子网。 ``` # ip addr @@ -112,7 +127,7 @@ vpc 路由使用cptp网络驱动,它具备以下特征。 valid_lft forever preferred_lft forever ``` -#### 3.1.2 容器路由 +#### 5.1.2 容器路由 ``` # ip route default via 172.10.3.9 dev eth0 @@ -121,15 +136,15 @@ default via 172.10.3.9 dev eth0 ``` 值得注意的是网关地址是宿主机端veth的地址。这个地址是 cce-network-v2-agent 在Pod CIDR中申请的。该机器上所有的容器都使用这个gateway地址。且机器重启后,路由地址不变。 -#### 3.1.3 邻居信息 +#### 5.1.3 邻居信息 容器内只有一条邻居信息,即默认网关的地址。mac地址是host端的veth的地址。 ``` # ip neigh 172.10.3.9 dev eth0 lladdr 06:c5:90:3b:4d:6d PERMANENT ``` -### 3.2 宿主机 -#### 3.2.1 宿主机veth地址 +### 5.2 宿主机 +#### 5.2.1 宿主机veth地址 宿主机上,每个veth的地址都是默认网关的地址 ``` # ip addr @@ -140,7 +155,7 @@ default via 172.10.3.9 dev eth0 inet6 fe80::4c5:90ff:fe3b:4d6d/64 scope link valid_lft forever preferred_lft forever ``` -#### 3.2.2 路由 +#### 5.2.2 路由 ``` # ip r default via 192.168.10.1 dev eth0 @@ -149,7 +164,7 @@ default via 192.168.10.1 dev eth0 172.10.3.21 dev vethda5db5af scope host ``` 宿主机上有一条到容器的路由,关联到host上的veth设备,scope为host。 -#### 3.2.3 邻居表项 +#### 5.2.3 邻居表项 宿主机上有一条到容器的静态邻居表,关联到host上的veth设备,mac地址是容器内的mac地址。 ``` # ip neigh diff --git a/cce-network-v2/go.mod b/cce-network-v2/go.mod index 699c25f..de8b468 100644 --- a/cce-network-v2/go.mod +++ b/cce-network-v2/go.mod @@ -74,7 +74,7 @@ require ( github.com/BurntSushi/toml v1.0.0 // indirect github.com/alexflint/go-filemutex v1.2.0 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/baidubce/bce-sdk-go v0.9.148 + github.com/baidubce/bce-sdk-go v0.9.165 github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect diff --git a/cce-network-v2/go.sum b/cce-network-v2/go.sum index 1e825fa..91ecf25 100644 --- a/cce-network-v2/go.sum +++ b/cce-network-v2/go.sum @@ -73,8 +73,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/baidubce/bce-sdk-go v0.9.148 h1:p9HMHnOVG/v6nK7hYHkCnkJyv41KCxuuWmUM8bIpkac= -github.com/baidubce/bce-sdk-go v0.9.148/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.165 h1:Erzw2lQ95Np9llvKtPNSZci3dbQK6OHn5kNFxUmBpnQ= +github.com/baidubce/bce-sdk-go v0.9.165/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/cce-network-v2/operator/Makefile b/cce-network-v2/operator/Makefile index fde9fc8..23375b6 100644 --- a/cce-network-v2/operator/Makefile +++ b/cce-network-v2/operator/Makefile @@ -25,7 +25,7 @@ $(TARGET): clean: @$(ECHO_CLEAN) - $(QUIET)rm -f $(TARGETS) + $(foreach target,$(TARGETS), $(QUIET)rm -f $(PWD)/output/bin/operator/$(target)) $(GO) clean $(GOCLEAN) install: diff --git a/cce-network-v2/operator/main.go b/cce-network-v2/operator/main.go index c24ee68..528a537 100644 --- a/cce-network-v2/operator/main.go +++ b/cce-network-v2/operator/main.go @@ -217,7 +217,7 @@ func checkStatus() error { // built-in leader election capbility in kubernetes. // See: https://github.com/kubernetes/client-go/blob/master/examples/leader-election/main.go func runOperator() { - log.Infof("cee ipam v2 Operator %s", version.Version) + log.Infof("cce ipam v2 Operator %s", version.Version) k8sInitDone := make(chan struct{}) isLeader.Store(false) @@ -343,17 +343,17 @@ func onOperatorStartLeading(ctx context.Context) { } var ( - nodeManager operatorWatchers.NodeEventHandler - cceEndpointManager operatorWatchers.EndpointEventHandler - sbnHandler syncer.SubnetEventHandler - eniHandler syncer.ENIEventHandler + netResourceSetManager operatorWatchers.NodeEventHandler + cceEndpointManager operatorWatchers.EndpointEventHandler + sbnHandler syncer.SubnetEventHandler + eniHandler syncer.ENIEventHandler ) // wait all informer synced operatorWatchers.StartWatchers(shutdownSignal) operatorWatchers.WaitForCacheSync(shutdownSignal) - log.WithField(logfields.Mode, option.Config.IPAM).Info("Initializing IPAM") + log.WithField(logfields.Mode, option.Config.IPAM).Info("Initializing Ethernet IPAM") switch ipamMode := option.Config.IPAM; ipamMode { case ipamOption.IPAMClusterPool, @@ -370,12 +370,12 @@ func onOperatorStartLeading(ctx context.Context) { log.WithError(err).Fatalf("Unable to init %s allocator", ipamMode) } - nm, err := alloc.Start(ctx, operatorWatchers.NetResourceSetClient) + nrsm, err := alloc.Start(ctx, operatorWatchers.NetResourceSetClient) if err != nil { log.WithError(err).Fatalf("Unable to start %s allocator", ipamMode) } - nodeManager = nm + netResourceSetManager = nrsm // Specify fixed IP assignment if ceh, ok := alloc.(endpoint.DirectAllocatorStarter); ok { @@ -422,7 +422,7 @@ func onOperatorStartLeading(ctx context.Context) { operatorWatchers.HandleNodeTolerationAndTaints(stopCh) } - if err := operatorWatchers.StartSynchronizingNetResourceSets(ctx, nodeManager); err != nil { + if err := operatorWatchers.StartSynchronizingNetResourceSets(ctx, netResourceSetManager); err != nil { log.WithError(err).Fatal("Unable to setup node watcher") } @@ -458,7 +458,7 @@ func onOperatorStartLeading(ctx context.Context) { // knows all podCIDRs that are currently set in the cluster, that // it can allocate podCIDRs for the nodes that don't have a podCIDR // set. - nodeManager.Resync(ctx, time.Time{}) + netResourceSetManager.Resync(ctx, time.Time{}) } if option.Config.IPAM == ipamOption.IPAMVpcEni { diff --git a/cce-network-v2/operator/option/config.go b/cce-network-v2/operator/option/config.go index b6c3575..974245f 100644 --- a/cce-network-v2/operator/option/config.go +++ b/cce-network-v2/operator/option/config.go @@ -168,8 +168,6 @@ const ( ResourceENIResyncInterval = "resource-eni-resync-interval" ResourceResyncWorkers = "resource-resync-workers" - // BCECustomerMaxENI is the max eni number of customer - BCECustomerMaxENI = "bce-customer-max-eni" // BCECustomerMaxIP is the max ip number of customer BCECustomerMaxIP = "bce-customer-max-ip" @@ -333,8 +331,6 @@ type OperatorConfig struct { // ResourceResyncWorkers specifies the number of parallel workers to be used in resource handler. ResourceResyncWorkers int64 - // BCECustomerMaxENI is the max eni number of customer - BCECustomerMaxENI int // BCECustomerMaxIP is the max ip number of customer BCECustomerMaxIP int @@ -451,7 +447,6 @@ func (c *OperatorConfig) Populate() { c.ResourceResyncInterval = viper.GetDuration(option.ResourceResyncInterval) c.ResourceENIResyncInterval = viper.GetDuration(ResourceENIResyncInterval) c.ResourceResyncWorkers = viper.GetInt64(ResourceResyncWorkers) - c.BCECustomerMaxENI = viper.GetInt(BCECustomerMaxENI) c.BCECustomerMaxIP = viper.GetInt(BCECustomerMaxIP) c.FixedIPTTL = viper.GetDuration(FixedIPTTL) diff --git a/cce-network-v2/operator/provider_vpc_eni_register.go b/cce-network-v2/operator/provider_vpc_eni_register.go index ed94769..0e7f89a 100644 --- a/cce-network-v2/operator/provider_vpc_eni_register.go +++ b/cce-network-v2/operator/provider_vpc_eni_register.go @@ -34,10 +34,10 @@ func init() { subnetSyncerProviders[ipamOption.IPAMVpcEni] = &bcesync.VPCSubnetSyncher{} eniSyncerProviders[ipamOption.IPAMVpcEni] = &bcesync.VPCENISyncer{} - registerFlags() + registerVpcEniFlags() } -func registerFlags() { +func registerVpcEniFlags() { flags := rootCmd.Flags() flags.String(operatorOption.BCECloudAccessKey, "", "BCE OpenApi AccessKeyID.") @@ -67,9 +67,6 @@ func registerFlags() { flags.Duration(operatorOption.ResourceENIResyncInterval, 60*time.Second, "Interval to resync eni resources") option.BindEnv(operatorOption.ResourceENIResyncInterval) - flags.Int(operatorOption.BCECustomerMaxENI, 0, "max eni number for customer") - option.BindEnv(operatorOption.BCECustomerMaxENI) - flags.Int(operatorOption.BCECustomerMaxIP, 0, "max ip number of eni for customer") option.BindEnv(operatorOption.BCECustomerMaxIP) diff --git a/cce-network-v2/operator/provider_vpc_route.go b/cce-network-v2/operator/provider_vpc_route_register.go similarity index 100% rename from cce-network-v2/operator/provider_vpc_route.go rename to cce-network-v2/operator/provider_vpc_route_register.go diff --git a/cce-network-v2/pkg/bce/agent/eni_link.go b/cce-network-v2/pkg/bce/agent/eni_link.go index da5ff50..4854242 100644 --- a/cce-network-v2/pkg/bce/agent/eni_link.go +++ b/cce-network-v2/pkg/bce/agent/eni_link.go @@ -151,12 +151,6 @@ func (ec *eniLink) ensureLinkConfig() (err error) { return err } - // 2. disable dad - err = ec.disableDad() - if err != nil { - return err - } - // 3. add primary IP err = ec.ensureENIAddr() if err != nil { @@ -299,6 +293,22 @@ func (ec *eniLink) ensureFromPrimaryRoute() (err error) { return nil } +func (ec *eniLink) ensureENINeigh() error { + // set proxy neigh + err := ensureENIArpProxy(ec.log, ec.macAddr) + if err != nil { + ec.log.WithError(err).Error("set arp proxy falied") + return err + } + err = ensureENINDPProxy(ec.log, ec.eni) + if err != nil { + ec.log.WithError(err).Error("set ndp proxy falied") + return err + } + // 2. disable dad + return ec.disableDad() +} + func EnsureRoute(log *logrus.Entry, eniLink netlink.Link, family int, rtTable int, routeDst *net.IPNet) (string, error) { addrs, err := netlink.AddrList(eniLink, family) if err != nil { diff --git a/cce-network-v2/pkg/bce/agent/eni_provider.go b/cce-network-v2/pkg/bce/agent/eni_provider.go index da6f6df..3f911ec 100644 --- a/cce-network-v2/pkg/bce/agent/eni_provider.go +++ b/cce-network-v2/pkg/bce/agent/eni_provider.go @@ -65,80 +65,87 @@ func (eh *eniInitFactory) OnUpdateENI(oldObj, newObj *ccev2.ENI) error { resource := newObj.DeepCopy() eh.fullENIs[resource.Spec.ENI.ID] = resource scopedLog := initLog.WithError(err).WithField("eni", resource.Spec.ENI.ID) - // set device and route on the woker machine only when eni bound at bcc - isBCC := resource.Spec.Type != ccev2.ENIForBBC - isPrimary := resource.Spec.UseMode == ccev2.ENIUseModePrimaryIP - isBCCSecondary := isBCC && !isPrimary - if resource.Status.VPCStatus == ccev2.VPCENIStatusInuse || !isBCC { - eniLink, err := newENILink(resource, eh.release) + if resource.Status.VPCStatus != ccev2.VPCENIStatusInuse { + scopedLog.Debugf("eni is not in use, skip status [%s]", resource.Status.VPCStatus) + return nil + } + + // downward compatibility with BBC models + // TODO 2021-03-08: remove this after BBC secondary models are deprecated + if resource.Spec.Type == ccev2.ENIForBBC { + resource.Spec.UseMode = ccev2.ENIUseModePrimaryWithSecondaryIP + } + + eniLink, err := newENILink(resource, eh.release) + if err != nil { + scopedLog.WithError(err).Error("Get eniLink falied") + return err + } + switch resource.Spec.UseMode { + case ccev2.ENIUseModePrimaryIP: + if err = eniLink.rename(true); err != nil { + scopedLog.WithError(err).Error("rename eniLink falied") + return err + } + case ccev2.ENIUseModePrimaryWithSecondaryIP: + // primary interface with secondary IP mode + // do not need to rename eniLink + default: + // eni with secondary ip mode + if err = eniLink.rename(false); err != nil { + scopedLog.WithError(err).Error("rename eniLink falied") + return err + } + // set rule when eni secondary IP mode + err = eniLink.ensureLinkConfig() if err != nil { - scopedLog.WithError(err).Error("Get eniLink falied") + scopedLog.WithError(err).Error("set eniLink falied") return err } - if isBCC { - if err = eniLink.rename(isPrimary); err != nil { - scopedLog.WithError(err).Error("rename eniLink falied") - return err - } - - if isBCCSecondary { - // set rule when eni secondary IP mode - err = eniLink.ensureLinkConfig() + if resource.Status.CCEStatus == ccev2.ENIStatusReadyOnNode { + if resource.Spec.InstallSourceBasedRouting { + err = ensureENIRule(scopedLog, resource) if err != nil { - scopedLog.WithError(err).Error("set eniLink falied") + scopedLog.WithError(err).Error("install source based routing falied") return err } } } + } - // set device and route on the woker machine only when eni bound at bcc - if _, ok := eh.localENIs[resource.Spec.ENI.ID]; !ok { - resource.Status.InterfaceIndex = eniLink.linkIndex - resource.Status.InterfaceName = eniLink.linkName - resource.Status.ENIIndex = eniLink.eniIndex - if eniLink.ipv4Gateway != "" { - resource.Status.GatewayIPv4 = eniLink.ipv4Gateway - } - if eniLink.ipv6Gateway != "" { - resource.Status.GatewayIPv6 = eniLink.ipv6Gateway - } - - if !reflect.DeepEqual(&resource.Status, &newObj.Status) { - (&resource.Status).AppendCCEENIStatus(ccev2.ENIStatusReadyOnNode) + // set eni neigbor config + err = eniLink.ensureENINeigh() + if err != nil { + return fmt.Errorf("failed to set eni neighbor config: %w", err) + } - resource, err = eh.eniClient.ENIs().UpdateStatus(context.TODO(), resource, metav1.UpdateOptions{}) - if err != nil { - scopedLog.WithError(err).Error("update eni status") - return err - } - } + // set device and route on the woker machine only when eni bound at bcc + if _, ok := eh.localENIs[resource.Spec.ENI.ID]; !ok { + resource.Status.InterfaceIndex = eniLink.linkIndex + resource.Status.InterfaceName = eniLink.linkName + resource.Status.ENIIndex = eniLink.eniIndex + if eniLink.ipv4Gateway != "" { + resource.Status.GatewayIPv4 = eniLink.ipv4Gateway + } + if eniLink.ipv6Gateway != "" { + resource.Status.GatewayIPv6 = eniLink.ipv6Gateway } - if resource.Status.CCEStatus == ccev2.ENIStatusReadyOnNode { - if resource.Spec.InstallSourceBasedRouting { - err = ensureENIRule(scopedLog, resource) - if err != nil { - scopedLog.WithError(err).Error("install source based routing falied") - return err - } - // set proxy neigh - err = ensureENIArpProxy(scopedLog, resource) - if err != nil { - scopedLog.WithError(err).Error("set arp proxy falied") - return err - } - err = ensureENINDPProxy(scopedLog, resource) - if err != nil { - scopedLog.WithError(err).Error("set ndp proxy falied") - return err - } + if !reflect.DeepEqual(&resource.Status, &newObj.Status) { + (&resource.Status).AppendCCEENIStatus(ccev2.ENIStatusReadyOnNode) + + resource, err = eh.eniClient.ENIs().UpdateStatus(context.TODO(), resource, metav1.UpdateOptions{}) + if err != nil { + scopedLog.WithError(err).Error("update eni status") + return err } } - eh.localENIs[resource.Spec.ENI.ID] = resource - qos.GlobalManager.ENIUpdateEventHandler(resource) } + eh.localENIs[resource.Spec.ENI.ID] = resource + qos.GlobalManager.ENIUpdateEventHandler(resource) + return err } diff --git a/cce-network-v2/pkg/bce/agent/source_route.go b/cce-network-v2/pkg/bce/agent/source_route.go index 52c8aa6..f833e9c 100644 --- a/cce-network-v2/pkg/bce/agent/source_route.go +++ b/cce-network-v2/pkg/bce/agent/source_route.go @@ -187,8 +187,8 @@ func ensureENINDPProxy(scopedLog *logrus.Entry, eni *ccev2.ENI) error { return nil } -func ensureENIArpProxy(scopedLog *logrus.Entry, eni *ccev2.ENI) error { - elink, err := link.FindENILinkByMac(eni.Spec.ENI.MacAddress) +func ensureENIArpProxy(scopedLog *logrus.Entry, macAddr string) error { + elink, err := link.FindENILinkByMac(macAddr) if err != nil { return err } diff --git a/cce-network-v2/pkg/bce/api/cloud/cloud.go b/cce-network-v2/pkg/bce/api/cloud/cloud.go index 3de3e77..796a3f9 100644 --- a/cce-network-v2/pkg/bce/api/cloud/cloud.go +++ b/cce-network-v2/pkg/bce/api/cloud/cloud.go @@ -38,8 +38,6 @@ const ( BCCEndpointEnv = "BCC_ENDPOINT" BBCEndpointEnv = "BBC_ENDPOINT" EIPEndpointEnv = "EIP_ENDPOINT" - - connectionTimeoutSInSecond = 20 ) func newBCEClientConfig(ctx context.Context, @@ -47,6 +45,7 @@ func newBCEClientConfig(ctx context.Context, endpointEnv string, preDefinedEndpoints map[string]string, auth Auth, + timeout time.Duration, ) *bce.BceClientConfiguration { endpoint, exist := os.LookupEnv(endpointEnv) if !exist || endpoint == "" { @@ -59,8 +58,8 @@ func newBCEClientConfig(ctx context.Context, UserAgent: bce.DEFAULT_USER_AGENT, Credentials: auth.GetCredentials(ctx), SignOption: auth.GetSignOptions(ctx), - Retry: bce.DEFAULT_RETRY_POLICY, - ConnectionTimeoutInMillis: bce.DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS, + Retry: bce.NewNoRetryPolicy(), + ConnectionTimeoutInMillis: int(timeout.Milliseconds()), } } @@ -71,12 +70,11 @@ func New( secretAccessKey string, kubeClient kubernetes.Interface, debug bool, + timeout time.Duration, ) (Interface, error) { ctx := context.TODO() - - if debug { - sdklog.SetLogHandler(sdklog.STDOUT) - } + // set logrus as bce sdk default logger + sdklog.SetLogger(&bceLogger{}) var auth Auth var err error @@ -90,35 +88,30 @@ func New( return nil, err } - bccClientConfig := newBCEClientConfig(ctx, region, BCCEndpointEnv, BCCEndpoints, auth) - bbcClientConfig := newBCEClientConfig(ctx, region, BBCEndpointEnv, BBCEndpoints, auth) - eipClientConfig := newBCEClientConfig(ctx, region, EIPEndpointEnv, EIPEndpoints, auth) + bccClientConfig := newBCEClientConfig(ctx, region, BCCEndpointEnv, BCCEndpoints, auth, timeout) + bbcClientConfig := newBCEClientConfig(ctx, region, BBCEndpointEnv, BBCEndpoints, auth, timeout) + eipClientConfig := newBCEClientConfig(ctx, region, EIPEndpointEnv, EIPEndpoints, auth, timeout) vpcClient := &vpc.Client{ BceClient: bce.NewBceClient(bccClientConfig, auth.GetSigner(ctx)), } - vpcClient.Config.ConnectionTimeoutInMillis = connectionTimeoutSInSecond * 1000 bccClient := &bcc.Client{ BceClient: bce.NewBceClient(bccClientConfig, auth.GetSigner(ctx)), } - bccClient.Config.ConnectionTimeoutInMillis = connectionTimeoutSInSecond * 1000 eipClient := &eip.Client{ BceClient: bce.NewBceClient(eipClientConfig, auth.GetSigner(ctx)), } - eipClient.Config.ConnectionTimeoutInMillis = connectionTimeoutSInSecond * 1000 // todo iaas sdk 暂未支持过滤 eri 和 eni,暂时自行封装一层支持,待后续 sdk 支持过滤 eri 和 eni 后,去除这部分封装 eniClient := &eniExt.Client{ Client: &eni.Client{BceClient: bce.NewBceClient(bccClientConfig, auth.GetSigner(ctx))}, } - eniClient.Config.ConnectionTimeoutInMillis = connectionTimeoutSInSecond * 1000 bbcClient := &bbc.Client{ BceClient: bce.NewBceClient(bbcClientConfig, auth.GetSigner(ctx)), } - bbcClient.Config.ConnectionTimeoutInMillis = connectionTimeoutSInSecond * 1000 hpcClient := &hpc.Client{ BceClient: bce.NewBceClient(bccClientConfig, auth.GetSigner(ctx)), @@ -376,7 +369,17 @@ func (c *Client) StatENI(ctx context.Context, eniID string) (*eni.Eni, error) { t := time.Now() resp, err := c.eniClient.GetEniDetail(eniID) exportMetric("StatENI", t, err) - return resp, nil + return resp, err +} + +// GetENIQuota implements Interface. +func (c *Client) GetENIQuota(ctx context.Context, instanceID string) (*eni.EniQuoteInfo, error) { + t := time.Now() + resp, err := c.eniClient.GetEniQuota(&eni.EniQuoteArgs{ + InstanceId: instanceID, + }) + exportMetric("GET /v1/eni/quota", t, err) + return resp, err } func (c *Client) ListRouteTable(ctx context.Context, vpcID, routeTableID string) ([]vpc.RouteRule, error) { @@ -521,3 +524,30 @@ func (c *Client) BatchAddHpcEniPrivateIP(ctx context.Context, args *hpc.EniBatch exportMetricAndLog(ctx, "BatchAddHpcEniPrivateIP", t, err) return resp, err } + +// BCCBatchAddIP implements Interface. +func (c *Client) BCCBatchAddIP(ctx context.Context, args *bccapi.BatchAddIpArgs) (*bccapi.BatchAddIpResponse, error) { + t := time.Now() + resp, err := c.bccClient.BatchAddIP(args) + exportMetricAndLog(ctx, BCCBatchAddIP, t, err) + return resp, err +} + +// BCCBatchDelIP implements Interface. +func (c *Client) BCCBatchDelIP(ctx context.Context, args *bccapi.BatchDelIpArgs) error { + t := time.Now() + err := c.bccClient.BatchDelIP(args) + exportMetricAndLog(ctx, BCCBatchDelIP, t, err) + return err +} + +// ListBCCInstanceEni implements Interface. +func (c *Client) ListBCCInstanceEni(ctx context.Context, instanceID string) ([]bccapi.Eni, error) { + t := time.Now() + resp, err := c.bccClient.ListInstanceEnis(instanceID) + exportMetricAndLog(ctx, BCCListENIs, t, err) + if err != nil { + return nil, err + } + return resp.EniList, err +} diff --git a/cce-network-v2/pkg/bce/api/cloud/error.go b/cce-network-v2/pkg/bce/api/cloud/error.go index 072b6b8..65299be 100644 --- a/cce-network-v2/pkg/bce/api/cloud/error.go +++ b/cce-network-v2/pkg/bce/api/cloud/error.go @@ -52,7 +52,9 @@ func ReasonForError(err error) ErrorReason { return ErrorReasonSubnetHasNoMoreIP case caseInsensitiveContains(errMsg, "RateLimit"): return ErrorReasonRateLimit - case caseInsensitiveContains(errMsg, "NoSuchObject") || caseInsensitiveContains(errMsg, "is invalid"): + case caseInsensitiveContains(errMsg, "NoSuchObject"): + return ErrorReasonNoSuchObject + case caseInsensitiveContains(errMsg, "is invalid"): // TODO: remove BadRequest when IaaS fixes their API return ErrorReasonBBCENIPrivateIPNotFound case caseInsensitiveContains(errMsg, "VmMemoryCanNotAttachMoreIpException"): @@ -117,7 +119,8 @@ func IsErrorRouteRuleRepeated(err error) bool { } func IsErrorQuotaLimitExceeded(err error) bool { - return ReasonForError(err) == ErrorReasonQuotaLimitExceeded + return ReasonForError(err) == ErrorReasonQuotaLimitExceeded || + IsErrorQuotaLimitExceeded(err) } func IsErrorCreateRouteRuleExceededQuota(err error) bool { diff --git a/cce-network-v2/pkg/bce/api/cloud/flow_control.go b/cce-network-v2/pkg/bce/api/cloud/flow_control.go index 8b8ef9c..4cf1ccd 100644 --- a/cce-network-v2/pkg/bce/api/cloud/flow_control.go +++ b/cce-network-v2/pkg/bce/api/cloud/flow_control.go @@ -70,6 +70,11 @@ const ( GetHPCEniID = "bcecloud/apis/v1/GetHPCEniID" BatchDeleteHpcEniPrivateIP = "bcecloud/apis/v1/BatchDeleteHpcEniPrivateIP" BatchAddHpcEniPrivateIP = "bcecloud/apis/v1/BatchAddHpcEniPrivateIP" + + // BCC primary interface API + BCCListENIs = "bcecloud/bcc/apis/v2/eni/{instanceID}" + BCCBatchAddIP = "bcecloud/bcc/apis/v2/instance/batchAddIp" + BCCBatchDelIP = "bcecloud/bcc/apis/v2/instance/batchDelIp" ) var apiRateLimitDefaults = map[string]rate.APILimiterParameters{ @@ -125,6 +130,29 @@ var apiRateLimitDefaults = map[string]rate.APILimiterParameters{ Log: false, }, + // for bcc eni + BCCListENIs: { + RateLimit: 5, + RateBurst: 5, + ParallelRequests: 5, + MaxWaitDuration: 30 * time.Second, + Log: false, + }, + BCCBatchAddIP: { + RateLimit: 5, + RateBurst: 5, + ParallelRequests: 5, + MaxWaitDuration: 30 * time.Second, + Log: false, + }, + BCCBatchDelIP: { + RateLimit: 5, + RateBurst: 5, + ParallelRequests: 5, + MaxWaitDuration: 30 * time.Second, + Log: false, + }, + // for route table ListRouteTable: { RateLimit: 1, @@ -183,6 +211,39 @@ type flowControlClient struct { limiter rate.ServiceLimiterManager } +// BCCBatchAddIP implements Interface. +func (fc *flowControlClient) BCCBatchAddIP(ctx context.Context, args *bccapi.BatchAddIpArgs) (*bccapi.BatchAddIpResponse, error) { + req, err := fc.limiter.Wait(ctx, BCCBatchAddIP) + if err != nil { + return nil, err + } + ret, err := fc.client.BCCBatchAddIP(ctx, args) + req.Error(err) + return ret, err +} + +// BCCBatchDelIP implements Interface. +func (fc *flowControlClient) BCCBatchDelIP(ctx context.Context, args *bccapi.BatchDelIpArgs) error { + req, err := fc.limiter.Wait(ctx, BCCBatchDelIP) + if err != nil { + return err + } + err = fc.client.BCCBatchDelIP(ctx, args) + req.Error(err) + return err +} + +// ListBCCInstanceEni implements Interface. +func (fc *flowControlClient) ListBCCInstanceEni(ctx context.Context, instanceID string) ([]bccapi.Eni, error) { + req, err := fc.limiter.Wait(ctx, BCCListENIs) + if err != nil { + return nil, err + } + ret, err := fc.client.ListBCCInstanceEni(ctx, instanceID) + req.Error(err) + return ret, err +} + // NewFlowControlClient returns a client with flow control. func NewFlowControlClient(client Interface, qps float64, burst int, timeout time.Duration) (Interface, error) { apiLimiterSet, err := rate.NewAPILimiterSet(option.Config.APIRateLimit, apiRateLimitDefaults, rate.SimpleMetricsObserver) @@ -556,4 +617,15 @@ func (fc *flowControlClient) StatENI(ctx context.Context, eniID string) (*eni.En return ret, err } +// GetENIQuota implements Interface. +func (fc *flowControlClient) GetENIQuota(ctx context.Context, instanceID string) (*eni.EniQuoteInfo, error) { + req, err := fc.limiter.Wait(ctx, StatENI) + if err != nil { + return nil, err + } + ret, err := fc.client.GetENIQuota(ctx, instanceID) + req.Error(err) + return ret, err +} + var _ Interface = &flowControlClient{} diff --git a/cce-network-v2/pkg/bce/api/cloud/logger.go b/cce-network-v2/pkg/bce/api/cloud/logger.go new file mode 100644 index 0000000..bf40cdc --- /dev/null +++ b/cce-network-v2/pkg/bce/api/cloud/logger.go @@ -0,0 +1,33 @@ +package cloud + +import ( + sdklog "github.com/baidubce/bce-sdk-go/util/log" + "github.com/sirupsen/logrus" +) + +var _ sdklog.SDKLogger = &bceLogger{} + +type bceLogger struct{} + +// Logging implements log.SDKLogger. +func (*bceLogger) Logging(level sdklog.Level, format string, args ...interface{}) { + // convert log level to logrus level + var logrusLevel logrus.Level + switch level { + case sdklog.DEBUG: + logrusLevel = logrus.DebugLevel + case sdklog.INFO: + logrusLevel = logrus.InfoLevel + case sdklog.WARN: + logrusLevel = logrus.WarnLevel + case sdklog.ERROR: + logrusLevel = logrus.ErrorLevel + case sdklog.FATAL: + logrusLevel = logrus.FatalLevel + case sdklog.PANIC: + logrusLevel = logrus.PanicLevel + default: + logrusLevel = logrus.TraceLevel + } + log.Logf(logrusLevel, format, args...) +} diff --git a/cce-network-v2/pkg/bce/api/cloud/testing/fake_cloud.go b/cce-network-v2/pkg/bce/api/cloud/testing/fake_cloud.go index 92a0c1b..d9ea853 100644 --- a/cce-network-v2/pkg/bce/api/cloud/testing/fake_cloud.go +++ b/cce-network-v2/pkg/bce/api/cloud/testing/fake_cloud.go @@ -52,6 +52,26 @@ type FakeBceCloud struct { bbcConfig *apiConfig } +// BCCBatchAddIP implements cloud.Interface. +func (*FakeBceCloud) BCCBatchAddIP(ctx context.Context, args *bccapi.BatchAddIpArgs) (*bccapi.BatchAddIpResponse, error) { + panic("unimplemented") +} + +// BCCBatchDelIP implements cloud.Interface. +func (*FakeBceCloud) BCCBatchDelIP(ctx context.Context, args *bccapi.BatchDelIpArgs) error { + panic("unimplemented") +} + +// ListBCCInstanceEni implements cloud.Interface. +func (*FakeBceCloud) ListBCCInstanceEni(ctx context.Context, instanceID string) ([]bccapi.Eni, error) { + panic("unimplemented") +} + +// GetENIQuota implements cloud.Interface. +func (*FakeBceCloud) GetENIQuota(ctx context.Context, instanceID string) (*eni.EniQuoteInfo, error) { + panic("unimplemented") +} + // BindENIPublicIP implements cloud.Interface func (*FakeBceCloud) BindENIPublicIP(ctx context.Context, privateIP string, publicIP string, eniID string) error { panic("unimplemented") diff --git a/cce-network-v2/pkg/bce/api/cloud/testing/mock_cloud.go b/cce-network-v2/pkg/bce/api/cloud/testing/mock_cloud.go index b617064..ba1cf35 100644 --- a/cce-network-v2/pkg/bce/api/cloud/testing/mock_cloud.go +++ b/cce-network-v2/pkg/bce/api/cloud/testing/mock_cloud.go @@ -130,6 +130,35 @@ func (mr *MockInterfaceMockRecorder) BBCBatchDelIP(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BBCBatchDelIP", reflect.TypeOf((*MockInterface)(nil).BBCBatchDelIP), arg0, arg1) } +// BCCBatchAddIP mocks base method. +func (m *MockInterface) BCCBatchAddIP(arg0 context.Context, arg1 *api.BatchAddIpArgs) (*api.BatchAddIpResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BCCBatchAddIP", arg0, arg1) + ret0, _ := ret[0].(*api.BatchAddIpResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BCCBatchAddIP indicates an expected call of BCCBatchAddIP. +func (mr *MockInterfaceMockRecorder) BCCBatchAddIP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BCCBatchAddIP", reflect.TypeOf((*MockInterface)(nil).BCCBatchAddIP), arg0, arg1) +} + +// BCCBatchDelIP mocks base method. +func (m *MockInterface) BCCBatchDelIP(arg0 context.Context, arg1 *api.BatchDelIpArgs) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BCCBatchDelIP", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// BCCBatchDelIP indicates an expected call of BCCBatchDelIP. +func (mr *MockInterfaceMockRecorder) BCCBatchDelIP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BCCBatchDelIP", reflect.TypeOf((*MockInterface)(nil).BCCBatchDelIP), arg0, arg1) +} + // BatchAddHpcEniPrivateIP mocks base method. func (m *MockInterface) BatchAddHpcEniPrivateIP(arg0 context.Context, arg1 *hpc.EniBatchPrivateIPArgs) (*hpc.BatchAddPrivateIPResult, error) { m.ctrl.T.Helper() @@ -377,6 +406,21 @@ func (mr *MockInterfaceMockRecorder) GetBCCInstanceDetail(arg0, arg1 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBCCInstanceDetail", reflect.TypeOf((*MockInterface)(nil).GetBCCInstanceDetail), arg0, arg1) } +// GetENIQuota mocks base method. +func (m *MockInterface) GetENIQuota(arg0 context.Context, arg1 string) (*eni.EniQuoteInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetENIQuota", arg0, arg1) + ret0, _ := ret[0].(*eni.EniQuoteInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetENIQuota indicates an expected call of GetENIQuota. +func (mr *MockInterfaceMockRecorder) GetENIQuota(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetENIQuota", reflect.TypeOf((*MockInterface)(nil).GetENIQuota), arg0, arg1) +} + // GetHPCEniID mocks base method. func (m *MockInterface) GetHPCEniID(arg0 context.Context, arg1 string) (*hpc.EniList, error) { m.ctrl.T.Helper() @@ -392,6 +436,21 @@ func (mr *MockInterfaceMockRecorder) GetHPCEniID(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHPCEniID", reflect.TypeOf((*MockInterface)(nil).GetHPCEniID), arg0, arg1) } +// ListBCCInstanceEni mocks base method. +func (m *MockInterface) ListBCCInstanceEni(arg0 context.Context, arg1 string) ([]api.Eni, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListBCCInstanceEni", arg0, arg1) + ret0, _ := ret[0].([]api.Eni) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListBCCInstanceEni indicates an expected call of ListBCCInstanceEni. +func (mr *MockInterfaceMockRecorder) ListBCCInstanceEni(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBCCInstanceEni", reflect.TypeOf((*MockInterface)(nil).ListBCCInstanceEni), arg0, arg1) +} + // ListEIPs mocks base method. func (m *MockInterface) ListEIPs(arg0 context.Context, arg1 eip.ListEipArgs) ([]eip.EipModel, error) { m.ctrl.T.Helper() diff --git a/cce-network-v2/pkg/bce/api/cloud/types.go b/cce-network-v2/pkg/bce/api/cloud/types.go index b5cad7a..d4e7ed0 100644 --- a/cce-network-v2/pkg/bce/api/cloud/types.go +++ b/cce-network-v2/pkg/bce/api/cloud/types.go @@ -53,6 +53,20 @@ type Interface interface { AttachENI(ctx context.Context, args *eni.EniInstance) error DetachENI(ctx context.Context, args *eni.EniInstance) error StatENI(ctx context.Context, eniID string) (*eni.Eni, error) + GetENIQuota(ctx context.Context, instanceID string) (*eni.EniQuoteInfo, error) + + // ListBCCInstanceEni Query the list of BCC eni network interface. + // Unlike the VPC interface, this interface can query the primaty network interface of BCC/EBC + // However, the `ListENIs`` and `StatENI`` interfaces of VPC cannot retrieve relevant information + // about the primary network interface of BCC/EBC. + ListBCCInstanceEni(ctx context.Context, instanceID string) ([]bccapi.Eni, error) + + // BCCBatchAddIP batch add secondary IP to primary interface of BCC/EBC + BCCBatchAddIP(ctx context.Context, args *bccapi.BatchAddIpArgs) (*bccapi.BatchAddIpResponse, error) + + // BCCBatchDelIP batch delete secondary IP to primary interface of BCC/EBC + // Waring: Do not mistakenly delete the primary IP address of the main network card + BCCBatchDelIP(ctx context.Context, args *bccapi.BatchDelIpArgs) error ListRouteTable(ctx context.Context, vpcID, routeTableID string) ([]vpc.RouteRule, error) CreateRouteRule(ctx context.Context, args *vpc.CreateRouteRuleArgs) (string, error) diff --git a/cce-network-v2/pkg/bce/bcesync/bcc_primary_eni.go b/cce-network-v2/pkg/bce/bcesync/bcc_primary_eni.go new file mode 100644 index 0000000..c0b6479 --- /dev/null +++ b/cce-network-v2/pkg/bce/bcesync/bcc_primary_eni.go @@ -0,0 +1,121 @@ +package bcesync + +import ( + "context" + "fmt" + + enisdk "github.com/baidubce/bce-sdk-go/services/eni" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/labels" + + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/cloud" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/eni" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" + ccev2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/syncer" +) + +// remoteEniSyncher +type remoteEniSyncher interface { + syncENI(ctx context.Context) (result []eni.Eni, err error) + statENI(ctx context.Context, eniID string) (*eni.Eni, error) + + // use eni machine to manager status of eni + useENIMachine() bool + + setENIUpdater(updater syncer.ENIUpdater) +} + +type remoteBCCPrimarySyncher struct { + updater syncer.ENIUpdater + bceclient cloud.Interface + syncManager *SyncManager[eni.Eni] +} + +var _ remoteEniSyncher = &remoteBCCPrimarySyncher{} + +func (es *remoteBCCPrimarySyncher) setENIUpdater(updater syncer.ENIUpdater) { + es.updater = updater +} + +// syncENI Sync eni from BCE Cloud, and all eni data are subject to BCE Cloud +func (es *remoteBCCPrimarySyncher) syncENI(ctx context.Context) (result []eni.Eni, err error) { + scopedLog := log.WithField(taskLogField, eniControllerName) + label := labels.Set(map[string]string{k8s.LabelENIUseMode: string(ccev2.ENIUseModePrimaryWithSecondaryIP)}) + + k8senis, err := es.updater.Lister().List(label.AsSelector()) + if err != nil { + scopedLog.WithError(err).Errorf("list k8s primary eni failed") + return result, err + } + + for _, k8seni := range k8senis { + eniresult, err := es.statENI(ctx, k8seni.Spec.ID) + if err != nil { + scopedLog.WithFields(logrus.Fields{ + "eniID": k8seni.Name, + "instanceID": k8seni.Spec.ENI.InstanceID, + "node": k8seni.Spec.NodeName, + }). + WithError(err).Errorf("stat bcc primary eni failed") + continue + } + + result = append(result, *eniresult) + } + return result, nil +} + +// statENI returns one ENI with the given name from bce cloud +func (es *remoteBCCPrimarySyncher) statENI(ctx context.Context, eniID string) (*eni.Eni, error) { + k8seni, err := es.updater.Lister().Get(eniID) + if err != nil { + return nil, fmt.Errorf("failed to get k8s eni %w", err) + } + intanceID := k8seni.Spec.ENI.InstanceID + var result []eni.Eni + eniResult, err := es.bceclient.ListBCCInstanceEni(ctx, intanceID) + if err != nil { + return nil, fmt.Errorf("failed to get bcc instance ENI %w", err) + } + for _, bcceni := range eniResult { + if bcceni.EniId != k8seni.Spec.ENI.ID { + continue + } + trancelateENI := eni.Eni{ + Eni: enisdk.Eni{ + EniId: bcceni.EniId, + Name: bcceni.Name, + ZoneName: bcceni.ZoneName, + Description: bcceni.Description, + InstanceId: bcceni.InstanceId, + MacAddress: bcceni.MacAddress, + VpcId: bcceni.VpcId, + SubnetId: bcceni.SubnetId, + Status: bcceni.Status, + }, + } + for _, ips := range bcceni.PrivateIpSet { + trancelateENI.PrivateIpSet = append(trancelateENI.PrivateIpSet, enisdk.PrivateIp{ + PrivateIpAddress: ips.PrivateIpAddress, + Primary: ips.Primary, + PublicIpAddress: ips.PublicIpAddress, + }) + if ips.Ipv6Address != "" { + trancelateENI.Ipv6PrivateIpSet = append(trancelateENI.Ipv6PrivateIpSet, enisdk.PrivateIp{ + PrivateIpAddress: ips.Ipv6Address, + Primary: ips.Primary, + }) + } + } + result = append(result, trancelateENI) + es.syncManager.AddItems(result) + return &trancelateENI, nil + } + + return nil, fmt.Errorf("failed to get bcc instance ENI with eniID: %s", eniID) +} + +func (es *remoteBCCPrimarySyncher) useENIMachine() bool { + return false +} diff --git a/cce-network-v2/pkg/bce/bcesync/eni.go b/cce-network-v2/pkg/bce/bcesync/eni.go index 1146bab..d3528a1 100644 --- a/cce-network-v2/pkg/bce/bcesync/eni.go +++ b/cce-network-v2/pkg/bce/bcesync/eni.go @@ -42,58 +42,97 @@ var eniLog = logging.NewSubysLogger(eniControllerName) // VPCENISyncer only work with single vpc cluster type VPCENISyncer struct { - eni *eniSyncher - bbceni *bbcENISyncer + eni *eniSyncher + physicalEni *physicalENISyncer + primaryENI *eniSyncher } // NewVPCENISyncer create a new VPCENISyncer func (es *VPCENISyncer) Init(ctx context.Context) error { eventRecorder := k8s.EventBroadcaster().NewRecorder(scheme.Scheme, corev1.EventSource{Component: eniControllerName}) + bceclient := option.BCEClient() + resyncPeriod := operatorOption.Config.ResourceResyncInterval - es.eni = &eniSyncher{eventRecorder: eventRecorder} - es.eni.VPCIDs = append(es.eni.VPCIDs, operatorOption.Config.BCECloudVPCID) - es.eni.ClusterID = operatorOption.Config.CCEClusterID + // 1. init vpc remote syncer + vpcRemote := &remoteVpcEniSyncher{ + bceclient: bceclient, + eventRecorder: eventRecorder, + ClusterID: operatorOption.Config.CCEClusterID, + } + es.eni = &eniSyncher{ + bceclient: bceclient, + resyncPeriod: resyncPeriod, + + remoteSyncer: vpcRemote, + eventRecorder: eventRecorder, + } err := es.eni.Init(ctx) if err != nil { - return err + return fmt.Errorf("init eni syncer failed: %v", err) } + vpcRemote.syncManager = es.eni.syncManager + vpcRemote.VPCIDs = append(vpcRemote.VPCIDs, operatorOption.Config.BCECloudVPCID) - es.bbceni = &bbcENISyncer{eventRecorder: eventRecorder} + // 2. init bcc remote syncer + bccRemote := &remoteBCCPrimarySyncher{ + bceclient: bceclient, + } + es.primaryENI = &eniSyncher{ + bceclient: bceclient, + resyncPeriod: resyncPeriod, + + remoteSyncer: bccRemote, + eventRecorder: eventRecorder, + } + es.primaryENI.Init(ctx) + if err != nil { + return fmt.Errorf("init primary eni syncer failed: %v", err) + } + bccRemote.syncManager = es.primaryENI.syncManager - return es.bbceni.Init(ctx) + // 3. init physical eni syncer + es.physicalEni = &physicalENISyncer{eventRecorder: eventRecorder} + return es.physicalEni.Init(ctx) } // StartENISyncer implements syncer.ENISyncher func (es *VPCENISyncer) StartENISyncer(ctx context.Context, updater syncer.ENIUpdater) syncer.ENIEventHandler { - es.bbceni.StartENISyncer(ctx, updater) + es.physicalEni.StartENISyncer(ctx, updater) es.eni.StartENISyncer(ctx, updater) + es.primaryENI.StartENISyncer(ctx, updater) return es } // Create implements syncer.ENIEventHandler func (es *VPCENISyncer) Create(resource *ccev2.ENI) error { if resource.Spec.Type == ccev2.ENIForBBC { - return es.bbceni.Create(resource) + return es.physicalEni.Create(resource) + } + if resource.Spec.Type == ccev2.ENIForEBC && resource.Spec.UseMode == ccev2.ENIUseModePrimaryWithSecondaryIP { + return es.primaryENI.Update(resource) } return es.eni.Create(resource) } // Delete implements syncer.ENIEventHandler func (es *VPCENISyncer) Delete(name string) error { - es.bbceni.Delete(name) + es.physicalEni.Delete(name) return es.eni.Delete(name) } // ResyncENI implements syncer.ENIEventHandler func (es *VPCENISyncer) ResyncENI(ctx context.Context) time.Duration { - es.bbceni.ResyncENI(ctx) + es.physicalEni.ResyncENI(ctx) return es.eni.ResyncENI(ctx) } // Update implements syncer.ENIEventHandler func (es *VPCENISyncer) Update(resource *ccev2.ENI) error { if resource.Spec.Type == ccev2.ENIForBBC { - return es.bbceni.Update(resource) + return es.physicalEni.Update(resource) + } + if resource.Spec.Type == ccev2.ENIForEBC && resource.Spec.UseMode == ccev2.ENIUseModePrimaryWithSecondaryIP { + return es.primaryENI.Update(resource) } return es.eni.Update(resource) } @@ -105,154 +144,42 @@ var ( // eniSyncher create SyncerManager for ENI type eniSyncher struct { - VPCIDs []string - ClusterID string - SyncManager *SyncManager[eni.Eni] + syncManager *SyncManager[eni.Eni] updater syncer.ENIUpdater bceclient cloud.Interface resyncPeriod time.Duration + remoteSyncer remoteEniSyncher eventRecorder record.EventRecorder } // Init initialise the sync manager. // add vpcIDs to list -func (ss *eniSyncher) Init(ctx context.Context) error { - ss.bceclient = option.BCEClient() - ss.resyncPeriod = operatorOption.Config.ResourceResyncInterval - ss.SyncManager = NewSyncManager(eniControllerName, ss.resyncPeriod, ss.syncENI) - +func (es *eniSyncher) Init(ctx context.Context) error { + es.syncManager = NewSyncManager(eniControllerName, es.resyncPeriod, es.remoteSyncer.syncENI) return nil } -func (ss *eniSyncher) StartENISyncer(ctx context.Context, updater syncer.ENIUpdater) syncer.ENIEventHandler { - ss.updater = updater - ss.SyncManager.Run() +func (es *eniSyncher) StartENISyncer(ctx context.Context, updater syncer.ENIUpdater) syncer.ENIEventHandler { + es.remoteSyncer.setENIUpdater(updater) + es.updater = updater + es.syncManager.Run() log.WithField(taskLogField, eniControllerName).Infof("ENISyncher is running") - return ss -} - -// syncENI Sync eni from BCE Cloud, and all eni data are subject to BCE Cloud -func (ss *eniSyncher) syncENI(ctx context.Context) (result []eni.Eni, err error) { - for _, vpcID := range ss.VPCIDs { - listArgs := enisdk.ListEniArgs{ - VpcId: vpcID, - Name: fmt.Sprintf("%s/", ss.ClusterID), - } - enis, err := ss.bceclient.ListENIs(context.TODO(), listArgs) - if err != nil { - log.WithField(taskLogField, eniControllerName). - WithField("request", logfields.Json(listArgs)). - WithError(err).Errorf("sync eni failed") - return result, err - } - - for i := 0; i < len(enis); i++ { - result = append(result, eni.Eni{Eni: enis[i]}) - ss.createExternalENI(&enis[i]) - } - } - return -} - -// eni is not created on cce, we should create it? -// If this ENI is missing, CCE will continue to try to create new ENIs. -// This will result in the inability to properly identify the capacity -// of the ENI -func (ss *eniSyncher) createExternalENI(eni *enisdk.Eni) { - old, err := ss.updater.Lister().Get(eni.EniId) - if err != nil && !kerrors.IsNotFound(err) { - return - } - if old != nil { - return - } - - if eni.Status == string(ccev2.VPCENIStatusDetaching) || eni.Status == string(ccev2.VPCENIStatusDeleted) { - return - } - scopeLog := eniLog.WithFields(logrus.Fields{ - "eniID": eni.EniId, - "vpcID": eni.VpcId, - "eniName": eni.Name, - "instanceID": eni.InstanceId, - "status": eni.Status, - }) - scopeLog.Infof("start to create external eni") - - // find node by instanceID - nodeList, err := k8s.CCEClient().Informers.Cce().V2().NetResourceSets().Lister().List(labels.Everything()) - if err != nil { - return - } - var resource *ccev2.NetResourceSet - for _, node := range nodeList { - if node.Spec.InstanceID == eni.InstanceId { - resource = node - break - } - } - if resource == nil { - return - } - scopeLog = scopeLog.WithField("nodeName", resource.Name) - scopeLog.Debugf("find node by instanceID success") - - newENI := &ccev2.ENI{ - ObjectMeta: metav1.ObjectMeta{ - Name: eni.EniId, - Labels: map[string]string{ - k8s.LabelInstanceID: eni.InstanceId, - k8s.LabelNodeName: resource.Name, - }, - Annotations: map[string]string{ - k8s.AnnotationExternalENI: eni.CreatedTime, - }, - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: ccev2.SchemeGroupVersion.String(), - Kind: ccev2.NRSKindDefinition, - Name: resource.Name, - UID: resource.UID, - }}, - }, - Spec: ccev2.ENISpec{ - NodeName: resource.Name, - UseMode: ccev2.ENIUseMode(resource.Spec.ENI.UseMode), - ENI: models.ENI{ - ID: eni.EniId, - Name: eni.Name, - ZoneName: eni.ZoneName, - InstanceID: eni.InstanceId, - VpcID: eni.VpcId, - SubnetID: eni.SubnetId, - SecurityGroupIds: eni.SecurityGroupIds, - EnterpriseSecurityGroupIds: eni.EnterpriseSecurityGroupIds, - }, - Type: ccev2.ENIForBCC, - RouteTableOffset: resource.Spec.ENI.RouteTableOffset, - InstallSourceBasedRouting: resource.Spec.ENI.InstallSourceBasedRouting, - }, - } - _, err = ss.updater.Create(newENI) - if err != nil { - ss.eventRecorder.Eventf(resource, corev1.EventTypeWarning, "FailedCreateExternalENI", "Failed to create external ENI on nrs %s: %s", resource.Name, err) - return - } - ss.eventRecorder.Eventf(resource, corev1.EventTypeNormal, "CreateExternalENISuccess", "create external ENI %s on nrs %s success", eni.EniId, resource.Name) + return es } // Create Process synchronization of new enis // For a new eni, we should generally query the details of the eni directly // and synchronously -func (ss *eniSyncher) Create(resource *ccev2.ENI) error { +func (es *eniSyncher) Create(resource *ccev2.ENI) error { log.WithField(taskLogField, eniControllerName). Infof("create a new eni(%s) crd", resource.Name) - return ss.Update(resource) + return es.Update(resource) } -func (ss *eniSyncher) Update(resource *ccev2.ENI) error { +func (es *eniSyncher) Update(resource *ccev2.ENI) error { var err error - if resource.Spec.Type != ccev2.ENIForBCC && resource.Spec.Type != ccev2.ENIDefaultBCC { + if resource.Spec.Type != ccev2.ENIForBCC && resource.Spec.Type != ccev2.ENIForEBC { return nil } @@ -277,7 +204,7 @@ func (ss *eniSyncher) Update(resource *ccev2.ENI) error { } scopeLog = scopeLog.WithField("retry", retry) - err := ss.handleENIUpdate(resource, scopeLog) + err := es.handleENIUpdate(resource, scopeLog) if kerrors.IsConflict(err) || kerrors.IsResourceExpired(err) { continue } @@ -287,7 +214,7 @@ func (ss *eniSyncher) Update(resource *ccev2.ENI) error { return nil } -func (ss *eniSyncher) handleENIUpdate(resource *ccev2.ENI, scopeLog *logrus.Entry) error { +func (es *eniSyncher) handleENIUpdate(resource *ccev2.ENI, scopeLog *logrus.Entry) error { var ( newObj = resource.DeepCopy() err error @@ -295,32 +222,39 @@ func (ss *eniSyncher) handleENIUpdate(resource *ccev2.ENI, scopeLog *logrus.Entr eniStatus *ccev2.ENIStatus updateError error ) - skipRefresh := ss.mangeFinalizer(newObj) - if !skipRefresh { - scopeLog.Debug("start eni machine") - // start machine - machine := eniStateMachine{ - ss: ss, - ctx: ctx, - resource: newObj, - } - err = machine.start() - _, isDelayError := err.(*cm.DelayEvent) - if err != nil { - if isDelayError && newObj.Status.VPCStatus == resource.Status.VPCStatus { - // if vpc status is not changed, will retry after 5s - scopeLog.Infof("eni vpc status not changed, will retry later") - return err - } else { - scopeLog.WithError(err).Error("eni machine failed") - return err + // delete old eni + if newObj.Status.VPCStatus == ccev2.VPCENIStatusDeleted { + return nil + } + + skipRefresh := es.mangeFinalizer(newObj) + if !skipRefresh { + if es.remoteSyncer.useENIMachine() { + scopeLog.Debug("start eni machine") + // start machine + machine := eniStateMachine{ + es: es, + ctx: ctx, + resource: newObj, } + err = machine.start() + _, isDelayError := err.(*cm.DelayEvent) + if err != nil { + if isDelayError && newObj.Status.VPCStatus == resource.Status.VPCStatus { + // if vpc status is not changed, will retry after 5s + scopeLog.Infof("eni vpc status not changed, will retry later") + return err + } else { + scopeLog.WithError(err).Error("eni machine failed") + return err + } + } } scopeLog.Debug("start refresh eni") - err = ss.refreshENI(ctx, newObj) + err = es.refreshENI(ctx, newObj) if err != nil { scopeLog.WithError(err).Error("refresh eni failed") return err @@ -332,7 +266,7 @@ func (ss *eniSyncher) handleENIUpdate(resource *ccev2.ENI, scopeLog *logrus.Entr if !reflect.DeepEqual(&newObj.Spec, &resource.Spec) || !reflect.DeepEqual(newObj.Labels, resource.Labels) || !reflect.DeepEqual(newObj.Finalizers, resource.Finalizers) { - newObj, updateError = ss.updater.Update(newObj) + newObj, updateError = es.updater.Update(newObj) if updateError != nil { scopeLog.WithError(updateError).Error("update eni spec failed") return updateError @@ -346,7 +280,7 @@ func (ss *eniSyncher) handleENIUpdate(resource *ccev2.ENI, scopeLog *logrus.Entr "vpcStatus": newObj.Status.VPCStatus, "cceStatus": newObj.Status.CCEStatus, }) - _, updateError = ss.updater.UpdateStatus(newObj) + _, updateError = es.updater.UpdateStatus(newObj) if updateError != nil { scopeLog.WithError(updateError).Error("update eni status failed") return updateError @@ -362,45 +296,59 @@ func (*eniSyncher) mangeFinalizer(newObj *ccev2.ENI) bool { if newObj.DeletionTimestamp == nil && len(newObj.Finalizers) == 0 { newObj.Finalizers = append(newObj.Finalizers, FinalizerENI) } + var finalizers []string + if newObj.DeletionTimestamp != nil && len(newObj.Finalizers) != 0 { node, err := k8s.CCEClient().Informers.Cce().V2().NetResourceSets().Lister().Get(newObj.Spec.NodeName) - if kerrors.IsNotFound(err) || - (node != nil && node.DeletionTimestamp != nil) || - (node != nil && len(newObj.GetOwnerReferences()) != 0 && node.GetUID() != newObj.GetOwnerReferences()[0].UID) { - var finalizers []string - for _, f := range newObj.Finalizers { - if f == FinalizerENI { - continue - } - finalizers = append(finalizers, f) - } - newObj.Finalizers = finalizers - log.Infof("remove finalizer from deletable ENI %s on NetResourceSet %s ", newObj.Name, newObj.Spec.NodeName) - return true + if kerrors.IsNotFound(err) { + goto removeFinalizer + } + if node != nil && node.DeletionTimestamp != nil { + goto removeFinalizer + } + if node != nil && len(newObj.GetOwnerReferences()) != 0 && node.GetUID() != newObj.GetOwnerReferences()[0].UID { + goto removeFinalizer + } + + // eni is not inuse + if newObj.Status.VPCStatus != ccev2.VPCENIStatusDeleted && + newObj.Status.VPCStatus != ccev2.VPCENIStatusInuse { + goto removeFinalizer } } return false + +removeFinalizer: + for _, f := range newObj.Finalizers { + if f == FinalizerENI { + continue + } + finalizers = append(finalizers, f) + } + newObj.Finalizers = finalizers + log.Infof("remove finalizer from deletable ENI %s on NetResourceSet %s ", newObj.Name, newObj.Spec.NodeName) + return true } -func (ss *eniSyncher) Delete(name string) error { +func (es *eniSyncher) Delete(name string) error { log.WithField(taskLogField, eniControllerName). Infof("eni(%s) have been deleted", name) - eni, _ := ss.updater.Lister().Get(name) + eni, _ := es.updater.Lister().Get(name) if eni == nil { return nil } - if ss.mangeFinalizer(eni) { - _, err := ss.updater.Update(eni) + if es.mangeFinalizer(eni) { + _, err := es.updater.Update(eni) return err } return nil } -func (ss *eniSyncher) ResyncENI(context.Context) time.Duration { +func (es *eniSyncher) ResyncENI(context.Context) time.Duration { log.WithField(taskLogField, eniControllerName).Infof("start to resync eni") - ss.SyncManager.RunImmediately() - return ss.resyncPeriod + es.syncManager.RunImmediately() + return es.resyncPeriod } // override ENI spec @@ -416,7 +364,7 @@ func (es *eniSyncher) refreshENI(ctx context.Context, newObj *ccev2.ENI) error { // should refresh eni if newObj.Spec.VPCVersion != newObj.Status.VPCVersion { - eniCache, err = es.statENI(ctx, newObj.Name) + eniCache, err = es.remoteSyncer.statENI(ctx, newObj.Name) } else { eniCache, err = es.getENIWithCache(ctx, newObj) } @@ -459,12 +407,12 @@ func (es *eniSyncher) refreshENI(ctx context.Context, newObj *ccev2.ENI) error { } // getENIWithCache gets a ENI from the cache if it is there, otherwise -func (ss *eniSyncher) getENIWithCache(ctx context.Context, resource *ccev2.ENI) (*eni.Eni, error) { +func (es *eniSyncher) getENIWithCache(ctx context.Context, resource *ccev2.ENI) (*eni.Eni, error) { var err error - eniCache := ss.SyncManager.Get(resource.Name) + eniCache := es.syncManager.Get(resource.Name) // Directly request VPC back to the source if eniCache == nil { - eniCache, err = ss.statENI(ctx, resource.Name) + eniCache, err = es.remoteSyncer.statENI(ctx, resource.Name) } if err == nil && eniCache == nil { return nil, errors.New(string(cloud.ErrorReasonNoSuchObject)) @@ -472,9 +420,24 @@ func (ss *eniSyncher) getENIWithCache(ctx context.Context, resource *ccev2.ENI) return eniCache, err } +type remoteVpcEniSyncher struct { + updater syncer.ENIUpdater + bceclient cloud.Interface + syncManager *SyncManager[eni.Eni] + + VPCIDs []string + ClusterID string + + eventRecorder record.EventRecorder +} + +func (es *remoteVpcEniSyncher) setENIUpdater(updater syncer.ENIUpdater) { + es.updater = updater +} + // statENI returns one ENI with the given name from bce cloud -func (ss *eniSyncher) statENI(ctx context.Context, ENIID string) (*eni.Eni, error) { - eniCache, err := ss.bceclient.StatENI(ctx, ENIID) +func (es *remoteVpcEniSyncher) statENI(ctx context.Context, ENIID string) (*eni.Eni, error) { + eniCache, err := es.bceclient.StatENI(ctx, ENIID) if err != nil { log.WithField(taskLogField, eniControllerName). WithField("ENIID", ENIID). @@ -483,13 +446,126 @@ func (ss *eniSyncher) statENI(ctx context.Context, ENIID string) (*eni.Eni, erro return nil, err } result := eni.Eni{Eni: *eniCache} - ss.SyncManager.AddItems([]eni.Eni{result}) + es.syncManager.AddItems([]eni.Eni{result}) return &result, nil } +// syncENI Sync eni from BCE Cloud, and all eni data are subject to BCE Cloud +func (es *remoteVpcEniSyncher) syncENI(ctx context.Context) (result []eni.Eni, err error) { + for _, vpcID := range es.VPCIDs { + listArgs := enisdk.ListEniArgs{ + VpcId: vpcID, + Name: fmt.Sprintf("%s/", es.ClusterID), + } + enis, err := es.bceclient.ListENIs(context.TODO(), listArgs) + if err != nil { + log.WithField(taskLogField, eniControllerName). + WithField("request", logfields.Json(listArgs)). + WithError(err).Errorf("sync eni failed") + return result, err + } + + for i := 0; i < len(enis); i++ { + result = append(result, eni.Eni{Eni: enis[i]}) + es.createExternalENI(&enis[i]) + } + } + return +} + +func (es *remoteVpcEniSyncher) useENIMachine() bool { + return true +} + +// eni is not created on cce, we should create it? +// If this ENI is missing, CCE will continue to try to create new ENIs. +// This will result in the inability to properly identify the capacity +// of the ENI +func (es *remoteVpcEniSyncher) createExternalENI(eni *enisdk.Eni) { + old, err := es.updater.Lister().Get(eni.EniId) + if err != nil && !kerrors.IsNotFound(err) { + return + } + if old != nil { + return + } + + if eni.Status == string(ccev2.VPCENIStatusDetaching) || eni.Status == string(ccev2.VPCENIStatusDeleted) { + return + } + scopeLog := eniLog.WithFields(logrus.Fields{ + "eniID": eni.EniId, + "vpcID": eni.VpcId, + "eniName": eni.Name, + "instanceID": eni.InstanceId, + "status": eni.Status, + }) + scopeLog.Infof("start to create external eni") + + // find node by instanceID + nodeList, err := k8s.CCEClient().Informers.Cce().V2().NetResourceSets().Lister().List(labels.Everything()) + if err != nil { + return + } + var resource *ccev2.NetResourceSet + for _, node := range nodeList { + if node.Spec.InstanceID == eni.InstanceId { + resource = node + break + } + } + if resource == nil { + return + } + scopeLog = scopeLog.WithField("nodeName", resource.Name) + scopeLog.Debugf("find node by instanceID success") + + newENI := &ccev2.ENI{ + ObjectMeta: metav1.ObjectMeta{ + Name: eni.EniId, + Labels: map[string]string{ + k8s.LabelInstanceID: eni.InstanceId, + k8s.LabelNodeName: resource.Name, + }, + Annotations: map[string]string{ + k8s.AnnotationExternalENI: eni.CreatedTime, + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: ccev2.SchemeGroupVersion.String(), + Kind: ccev2.NRSKindDefinition, + Name: resource.Name, + UID: resource.UID, + }}, + }, + Spec: ccev2.ENISpec{ + NodeName: resource.Name, + UseMode: ccev2.ENIUseMode(resource.Spec.ENI.UseMode), + ENI: models.ENI{ + ID: eni.EniId, + Name: eni.Name, + ZoneName: eni.ZoneName, + InstanceID: eni.InstanceId, + VpcID: eni.VpcId, + SubnetID: eni.SubnetId, + SecurityGroupIds: eni.SecurityGroupIds, + EnterpriseSecurityGroupIds: eni.EnterpriseSecurityGroupIds, + }, + Type: ccev2.ENIForBCC, + RouteTableOffset: resource.Spec.ENI.RouteTableOffset, + InstallSourceBasedRouting: resource.Spec.ENI.InstallSourceBasedRouting, + }, + } + _, err = es.updater.Create(newENI) + if err != nil { + es.eventRecorder.Eventf(resource, corev1.EventTypeWarning, "FailedCreateExternalENI", "Failed to create external ENI on nrs %s: %s", resource.Name, err) + return + } + es.eventRecorder.Eventf(resource, corev1.EventTypeNormal, "CreateExternalENISuccess", "create external ENI %s on nrs %s success", eni.EniId, resource.Name) +} + // eniStateMachine ENI state machine, used to control the state flow of ENI type eniStateMachine struct { - ss *eniSyncher + es *eniSyncher ctx context.Context resource *ccev2.ENI vpceni *eni.Eni @@ -498,12 +574,18 @@ type eniStateMachine struct { // Start state machine flow func (esm *eniStateMachine) start() error { var err error - if esm.resource.Status.VPCStatus != ccev2.VPCENIStatusInuse { + if esm.resource.Status.VPCStatus != ccev2.VPCENIStatusInuse && esm.resource.Status.VPCStatus != ccev2.VPCENIStatusDeleted { // refresh status of ENI - esm.vpceni, err = esm.ss.statENI(esm.ctx, esm.resource.Name) - if err != nil { + esm.vpceni, err = esm.es.remoteSyncer.statENI(esm.ctx, esm.resource.Name) + if cloud.IsErrorReasonNoSuchObject(err) { + // eni not found, will delete it which not inuse + log.WithField("eniID", esm.resource.Name).Error("not inuse eni not found in vpc, will delete it") + return esm.deleteENI() + + } else if err != nil { return fmt.Errorf("eni state machine failed to refresh eni(%s) status: %v", esm.resource.Name, err) } + switch esm.resource.Status.VPCStatus { case ccev2.VPCENIStatusAvailable: err = esm.attachENI() @@ -521,7 +603,7 @@ func (esm *eniStateMachine) start() error { return err } - // regresh the status of ENI + // refresh the status of ENI if esm.resource.Status.VPCStatus != ccev2.VPCENIStatus(esm.vpceni.Status) { (&esm.resource.Status).AppendVPCStatus(ccev2.VPCENIStatus(esm.vpceni.Status)) return nil @@ -547,15 +629,18 @@ func (esm *eniStateMachine) attachENI() error { } // try to attach eni to bcc instance - err := esm.ss.bceclient.AttachENI(esm.ctx, &enisdk.EniInstance{ + err := esm.es.bceclient.AttachENI(esm.ctx, &enisdk.EniInstance{ InstanceId: esm.resource.Spec.ENI.InstanceID, EniId: esm.resource.Spec.ENI.ID, }) if err != nil { - esm.ss.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "AttachENIFailed", "failed attach eni(%s) to %s, will delete it: %v", esm.resource.Spec.ENI.ID, esm.resource.Spec.ENI.InstanceID, err) + esm.es.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "AttachENIFailed", "failed attach eni(%s) to %s, will delete it: %v", esm.resource.Spec.ENI.ID, esm.resource.Spec.ENI.InstanceID, err) err2 := esm.deleteENI() - err = fmt.Errorf("failed to attach eni(%s) to instance(%s): %s, delete eni crd: %s", esm.resource.Spec.ENI.ID, esm.resource.Spec.ENI.InstanceID, err.Error(), err2.Error()) + err = fmt.Errorf("failed to attach eni(%s) to instance(%s): %s, will delete eni crd", esm.resource.Spec.ENI.ID, esm.resource.Spec.ENI.InstanceID, err.Error()) + if err2 != nil { + log.WithField("eniID", esm.resource.Name).Errorf("failed to delete eni crd: %v", err2) + } return err } @@ -567,19 +652,20 @@ func (esm *eniStateMachine) attachENI() error { // deleteENI roback to delete eni func (esm *eniStateMachine) deleteENI() error { - err := esm.ss.bceclient.DeleteENI(esm.ctx, esm.resource.Spec.ENI.ID) - if err != nil { - esm.ss.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "DeleteENIFailed", "failed to delete eni(%s): %v", esm.resource.Spec.ENI.ID, err) + err := esm.es.bceclient.DeleteENI(esm.ctx, esm.resource.Spec.ENI.ID) + if err != nil && !cloud.IsErrorReasonNoSuchObject(err) && !cloud.IsErrorENINotFound(err) { + esm.es.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "DeleteENIFailed", "failed to delete eni(%s): %v", esm.resource.Spec.ENI.ID, err) return fmt.Errorf("failed to delete eni(%s): %s", esm.resource.Spec.ENI.ID, err.Error()) } - esm.ss.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "DeleteENISuccess", "delete eni(%s) success", esm.resource.Spec.ENI.ID) + esm.es.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "DeleteENISuccess", "delete eni(%s) success", esm.resource.Spec.ENI.ID) // delete resource after delete eni in cloud - err = esm.ss.updater.Delete(esm.resource.Name) + err = esm.es.updater.Delete(esm.resource.Name) if err != nil { return fmt.Errorf("failed to delete eni(%s) crd resource: %s", esm.resource.Name, err.Error()) } - return fmt.Errorf("eni(%s) delete success", esm.resource.Spec.ENI.ID) + log.WithField("eniID", esm.resource.Name).Info("delete eni crd resource success") + return nil } // attachingENI Processing ENI in the attaching state @@ -591,7 +677,7 @@ func (esm *eniStateMachine) attachingENI() error { } if esm.resource.CreationTimestamp.Add(ENIMaxCreateDuration).Before(time.Now()) { - esm.ss.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "AttachingENIError", "eni(%s) is in attaching status more than %s, will delete it", esm.resource.Spec.ENI.ID, ENIMaxCreateDuration.String()) + esm.es.eventRecorder.Eventf(esm.resource, corev1.EventTypeWarning, "AttachingENIError", "eni(%s) is in attaching status more than %s, will delete it", esm.resource.Spec.ENI.ID, ENIMaxCreateDuration.String()) return esm.deleteENI() } return nil diff --git a/cce-network-v2/pkg/bce/bcesync/bbc_eni.go b/cce-network-v2/pkg/bce/bcesync/physical_eni.go similarity index 69% rename from cce-network-v2/pkg/bce/bcesync/bbc_eni.go rename to cce-network-v2/pkg/bce/bcesync/physical_eni.go index 6ba4cf2..e5cb359 100644 --- a/cce-network-v2/pkg/bce/bcesync/bbc_eni.go +++ b/cce-network-v2/pkg/bce/bcesync/physical_eni.go @@ -26,12 +26,12 @@ import ( ) var ( - bbcENILog = logging.NewSubysLogger("bbc-eni-sync-manager") - bbcENIControllerName = "bbc" + eniControllerName + physicalENILog = logging.NewSubysLogger("physical-eni-sync-manager") + physicalENIControllerName = "physical" + eniControllerName ) -// bbcENISyncer create SyncerManager for ENI -type bbcENISyncer struct { +// physicalENISyncer create SyncerManager for ENI +type physicalENISyncer struct { VPCIDs []string ClusterID string SyncManager *SyncManager[bbc.GetInstanceEniResult] @@ -45,23 +45,23 @@ type bbcENISyncer struct { // Init initialise the sync manager. // add vpcIDs to list -func (ss *bbcENISyncer) Init(ctx context.Context) error { - ss.bceclient = option.BCEClient() - ss.resyncPeriod = operatorOption.Config.ResourceResyncInterval - ss.SyncManager = NewSyncManager(bbcENIControllerName, ss.resyncPeriod, ss.syncENI) - ss.enilister = k8s.CCEClient().Informers.Cce().V2().ENIs().Lister() +func (pes *physicalENISyncer) Init(ctx context.Context) error { + pes.bceclient = option.BCEClient() + pes.resyncPeriod = operatorOption.Config.ResourceResyncInterval + pes.SyncManager = NewSyncManager(physicalENIControllerName, pes.resyncPeriod, pes.syncENI) + pes.enilister = k8s.CCEClient().Informers.Cce().V2().ENIs().Lister() return nil } -func (ss *bbcENISyncer) StartENISyncer(ctx context.Context, updater syncer.ENIUpdater) syncer.ENIEventHandler { - ss.updater = updater - ss.SyncManager.Run() - log.WithField(taskLogField, bbcENIControllerName).Infof("bbcENISyncer is running") - return ss +func (pes *physicalENISyncer) StartENISyncer(ctx context.Context, updater syncer.ENIUpdater) syncer.ENIEventHandler { + pes.updater = updater + pes.SyncManager.Run() + log.WithField(taskLogField, physicalENIControllerName).Infof("physicalENISyncer is running") + return pes } // syncENI Sync eni from BCE Cloud, and all eni data are subject to BCE Cloud -func (ss *bbcENISyncer) syncENI(ctx context.Context) (result []bbc.GetInstanceEniResult, err error) { +func (pes *physicalENISyncer) syncENI(ctx context.Context) (result []bbc.GetInstanceEniResult, err error) { var ( results []bbc.GetInstanceEniResult ) @@ -69,7 +69,7 @@ func (ss *bbcENISyncer) syncENI(ctx context.Context) (result []bbc.GetInstanceEn selector, _ := metav1.LabelSelectorAsSelector(metav1.SetAsLabelSelector(labels.Set{ k8s.LabelENIType: string(ccev2.ENIForBBC), })) - enis, err := ss.enilister.List(selector) + enis, err := pes.enilister.List(selector) if err != nil { return nil, fmt.Errorf("list ENIs failed: %w", err) } @@ -78,9 +78,9 @@ func (ss *bbcENISyncer) syncENI(ctx context.Context) (result []bbc.GetInstanceEn if instanceId == "" && eni.Labels != nil { instanceId = eni.Labels[k8s.LabelInstanceID] } - eniResult, err := ss.bceclient.GetBBCInstanceENI(ctx, instanceId) + eniResult, err := pes.bceclient.GetBBCInstanceENI(ctx, instanceId) if err != nil { - bbcENILog.WithError(err).WithField("node", eni.Spec.NodeName).Errorf("get bbc ENI %s failed", eni.Name) + physicalENILog.WithError(err).WithField("node", eni.Spec.NodeName).Errorf("get physical ENI %s failed", eni.Name) continue } results = append(results, *eniResult) @@ -92,13 +92,13 @@ func (ss *bbcENISyncer) syncENI(ctx context.Context) (result []bbc.GetInstanceEn // Create Process synchronization of new enis // For a new eni, we should generally query the details of the eni directly // and synchronously -func (ss *bbcENISyncer) Create(resource *ccev2.ENI) error { - log.WithField(taskLogField, bbcENIControllerName). +func (pes *physicalENISyncer) Create(resource *ccev2.ENI) error { + log.WithField(taskLogField, physicalENIControllerName). Infof("create a new eni(%s) crd", resource.Name) - return ss.Update(resource) + return pes.Update(resource) } -func (ss *bbcENISyncer) Update(resource *ccev2.ENI) error { +func (pes *physicalENISyncer) Update(resource *ccev2.ENI) error { if resource.Spec.Type != ccev2.ENIForBBC { return nil } @@ -110,48 +110,48 @@ func (ss *bbcENISyncer) Update(resource *ccev2.ENI) error { ctx = logfields.NewContext() ) - scopeLog := bbcENILog.WithFields(logrus.Fields{ + scopeLog := physicalENILog.WithFields(logrus.Fields{ "eniID": newObj.Name, "vpcID": newObj.Spec.ENI.VpcID, "eniName": newObj.Spec.ENI.Name, "instanceID": newObj.Spec.ENI.InstanceID, "status": newObj.Status.VPCStatus, - "method": "bbcENISyncer.Update", + "method": "physicalENISyncer.Update", }) - ss.mangeFinalizer(newObj) + pes.mangeFinalizer(newObj) - scopeLog.Debug("start refresh bbc eni") - err = ss.refreshENI(ctx, newObj) + scopeLog.Debug("start refresh physical eni") + err = pes.refreshENI(ctx, newObj) if err != nil { - scopeLog.WithError(err).Error("refresh bbc eni failed") + scopeLog.WithError(err).Error("refresh physical eni failed") return err } // update spec and status if !reflect.DeepEqual(&newObj.Spec, &resource.Spec) || !reflect.DeepEqual(newObj.Labels, resource.Labels) { - newObj, err = ss.updater.Update(newObj) + newObj, err = pes.updater.Update(newObj) if err != nil { - scopeLog.WithError(err).Error("update bbc eni spec failed") + scopeLog.WithError(err).Error("update physical eni spec failed") return err } - scopeLog.Info("update bbc eni spec success") + scopeLog.Info("update physical eni spec success") } if !reflect.DeepEqual(eniStatus, &resource.Status) { newObj.Status = *eniStatus - _, err = ss.updater.UpdateStatus(newObj) + _, err = pes.updater.UpdateStatus(newObj) if err != nil { - scopeLog.WithError(err).Error("update bbc eni status failed") + scopeLog.WithError(err).Error("update physical eni status failed") return err } - scopeLog.Info("update bbc eni status success") + scopeLog.Info("update physical eni status success") } return nil } // mangeFinalizer except for node deletion, direct deletion of ENI objects is prohibited -func (*bbcENISyncer) mangeFinalizer(newObj *ccev2.ENI) { +func (*physicalENISyncer) mangeFinalizer(newObj *ccev2.ENI) { if newObj.DeletionTimestamp == nil && len(newObj.Finalizers) == 0 { newObj.Finalizers = append(newObj.Finalizers, FinalizerENI) } @@ -170,16 +170,16 @@ func (*bbcENISyncer) mangeFinalizer(newObj *ccev2.ENI) { } } -func (ss *bbcENISyncer) Delete(name string) error { - bbcENILog.WithField(taskLogField, bbcENIControllerName). +func (pes *physicalENISyncer) Delete(name string) error { + physicalENILog.WithField(taskLogField, physicalENIControllerName). Infof("eni(%s) have been deleted", name) return nil } -func (ss *bbcENISyncer) ResyncENI(context.Context) time.Duration { - log.WithField(taskLogField, bbcENIControllerName).Infof("start to resync bbc eni") - ss.SyncManager.RunImmediately() - return ss.resyncPeriod +func (pes *physicalENISyncer) ResyncENI(context.Context) time.Duration { + log.WithField(taskLogField, physicalENIControllerName).Infof("start to resync physical eni") + pes.SyncManager.RunImmediately() + return pes.resyncPeriod } // override ENI spec @@ -187,7 +187,7 @@ func (ss *bbcENISyncer) ResyncENI(context.Context) time.Duration { // 1. set ip family by private ip address // 2. set subnet by priveip search subnet of the private IP from subnets // 3. override eni status -func (es *bbcENISyncer) refreshENI(ctx context.Context, newObj *ccev2.ENI) error { +func (es *physicalENISyncer) refreshENI(ctx context.Context, newObj *ccev2.ENI) error { var ( eniCache *bbc.GetInstanceEniResult err error @@ -255,16 +255,16 @@ func (es *bbcENISyncer) refreshENI(ctx context.Context, newObj *ccev2.ENI) error } // getENIWithCache gets a ENI from the cache if it is there, otherwise -func (ss *bbcENISyncer) getENIWithCache(ctx context.Context, resource *ccev2.ENI) (*bbc.GetInstanceEniResult, error) { +func (pes *physicalENISyncer) getENIWithCache(ctx context.Context, resource *ccev2.ENI) (*bbc.GetInstanceEniResult, error) { var err error - eniCache := ss.SyncManager.Get(resource.Name) + eniCache := pes.SyncManager.Get(resource.Name) // Directly request VPC back to the source if eniCache == nil { instanceId := resource.Spec.ENI.InstanceID if instanceId == "" && resource.Labels != nil { instanceId = resource.Labels[k8s.LabelInstanceID] } - eniCache, err = ss.statENI(ctx, instanceId) + eniCache, err = pes.statENI(ctx, instanceId) } if err == nil && eniCache == nil { return nil, errors.New(string(cloud.ErrorReasonNoSuchObject)) @@ -273,19 +273,19 @@ func (ss *bbcENISyncer) getENIWithCache(ctx context.Context, resource *ccev2.ENI } // statENI returns one ENI with the given name from bce cloud -func (ss *bbcENISyncer) statENI(ctx context.Context, instanceID string) (*bbc.GetInstanceEniResult, error) { +func (pes *physicalENISyncer) statENI(ctx context.Context, instanceID string) (*bbc.GetInstanceEniResult, error) { var ( eniCache *bbc.GetInstanceEniResult err error ) - eniCache, err = ss.bceclient.GetBBCInstanceENI(ctx, instanceID) + eniCache, err = pes.bceclient.GetBBCInstanceENI(ctx, instanceID) if err != nil { - bbcENILog.WithField(taskLogField, bbcENIControllerName). + physicalENILog.WithField(taskLogField, physicalENIControllerName). WithField("instanceID", instanceID). WithContext(ctx). WithError(err).Errorf("stat eni failed") return nil, err } - ss.SyncManager.AddItems([]bbc.GetInstanceEniResult{*eniCache}) + pes.SyncManager.AddItems([]bbc.GetInstanceEniResult{*eniCache}) return eniCache, nil } diff --git a/cce-network-v2/pkg/bce/bcesync/subnet.go b/cce-network-v2/pkg/bce/bcesync/subnet.go index 5207cc7..863ff64 100644 --- a/cce-network-v2/pkg/bce/bcesync/subnet.go +++ b/cce-network-v2/pkg/bce/bcesync/subnet.go @@ -181,7 +181,7 @@ func SearchSubnetID(vpcID, defaultSbnID, privateIPStr string) string { sbn, err := k8s.CCEClient().Informers.Cce().V1().Subnets().Lister().Get(defaultSbnID) if err != nil { log.WithField(taskLogField, eniControllerName). - WithError(err).Errorf("get subnet %s failed", defaultSbnID) + WithError(err).Errorf("failed to get subnet %s", defaultSbnID) } if ccev1.IsInSubnet(sbn, privateIPStr) { return defaultSbnID diff --git a/cce-network-v2/pkg/bce/limit/ip_resource_manager.go b/cce-network-v2/pkg/bce/limit/ip_resource_manager.go deleted file mode 100644 index cfbd7d7..0000000 --- a/cce-network-v2/pkg/bce/limit/ip_resource_manager.go +++ /dev/null @@ -1,403 +0,0 @@ -package limit - -import ( - "context" - "fmt" - "strconv" - - operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/math" - bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - k8sutilnet "k8s.io/utils/net" -) - -var ( - // template to patch node.capacity - patchCapacityBodyTemplate = `{"op":"%s","path":"/status/capacity/cce.baidubce.com~1%s","value":"%d"}` - patchAddOp = "add" - patchModiffyOp = "replace" - - log = logging.NewSubysLogger("ip-resource-manager") -) - -type simpleIPResourceManager struct { - kubeClient kubernetes.Interface - preAttachedENINum int - node *corev1.Node -} - -// NodeCapacity is the limit of the node -type NodeCapacity struct { - // MaxENINum is the maximum number of ENI devices that can be attached to the node - MaxENINum int - // MaxIPPerENI is the maximum number of IP addresses that can be attached to the ENI device - MaxIPPerENI int - - // CustomerENIResource is the maximum number of ENI devices that can be attached to the node - // this value will patch to k8s node - CustomerENIResource int - CustomerIPResource int -} - -// patchENICapacityInfoToNode patches eni capacity info to node if not exists. -// so user can reset these values. -func (manager *simpleIPResourceManager) patchENICapacityInfoToNode(ctx context.Context, maxENINum, maxIPNum int) error { - node := manager.node - if node.Annotations == nil { - node.Annotations = make(map[string]string) - } - - // update node capacity - needUpdateIPResourceFlag := true - ipPathBody := fmt.Sprintf(patchCapacityBodyTemplate, patchAddOp, "ip", maxIPNum) - if ipRe, ok := node.Status.Capacity[k8s.ResourceIPForNode]; ok { - if ipRe.Value() == int64(maxIPNum) { - needUpdateIPResourceFlag = false - } - ipPathBody = fmt.Sprintf(patchCapacityBodyTemplate, patchModiffyOp, "ip", maxIPNum) - } - - needUpdateENIResourceFlag := true - eniPathBody := fmt.Sprintf(patchCapacityBodyTemplate, patchAddOp, "eni", maxENINum) - if eniRe, ok := node.Status.Capacity[k8s.ResourceENIForNode]; ok { - if eniRe.Value() == int64(maxENINum) { - needUpdateENIResourceFlag = false - } - eniPathBody = fmt.Sprintf(patchCapacityBodyTemplate, patchModiffyOp, "eni", maxENINum) - } - - // patch annotations - if needUpdateENIResourceFlag || needUpdateIPResourceFlag { - patchData := []byte(fmt.Sprintf(`[%s, %s]`, ipPathBody, eniPathBody)) - _, err := manager.kubeClient.CoreV1().Nodes().Patch(ctx, manager.node.Name, types.JSONPatchType, patchData, metav1.PatchOptions{}, "status") - if err != nil { - return err - } - log.WithContext(ctx).Infof("patch ip resource (maxENI: %d, maxIP: %d) to node capacity success", maxENINum, maxIPNum) - } - - return nil -} - -// IPResourceManager SyncCapacity syncs node capacity -type IPResourceManager interface { - // CalaculateCapacity calculate node capacity - CalaculateCapacity() *NodeCapacity - // SyncCapacity syncs node capacity - SyncCapacity(ctx context.Context) error -} - -type bbcIPResourceManager struct { - *simpleIPResourceManager - limiter *NodeCapacity -} - -var _ IPResourceManager = &bbcIPResourceManager{} - -// NewBBCIPResourceManager creates a new ip resource manager for BBC instance -func NewBBCIPResourceManager(kubeClient kubernetes.Interface, node *corev1.Node) IPResourceManager { - return &bbcIPResourceManager{ - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 0, - node: node, - }, - } -} - -// CalaculateCapacity implements IPResourceManager -func (manager *bbcIPResourceManager) CalaculateCapacity() *NodeCapacity { - var ( - maxENINum = 1 - maxIPPerENI = 40 - ) - if operatorOption.Config.BCECustomerMaxENI != 0 { - maxENINum = operatorOption.Config.BCECustomerMaxENI - } - - if operatorOption.Config.BCECustomerMaxIP != 0 { - maxIPPerENI = operatorOption.Config.BCECustomerMaxIP - } - manager.limiter = &NodeCapacity{ - MaxENINum: maxENINum, - MaxIPPerENI: maxIPPerENI, - CustomerENIResource: 0, - CustomerIPResource: maxIPPerENI, - } - return manager.limiter -} - -func (manager *bbcIPResourceManager) SyncCapacity(ctx context.Context) error { - - return manager.patchENICapacityInfoToNode(ctx, manager.limiter.CustomerENIResource, manager.limiter.CustomerIPResource) -} - -var _ IPResourceManager = &bbcIPResourceManager{} - -type bccIPResourceManager struct { - limiter *NodeCapacity - *simpleIPResourceManager - cpuCount int - memoryCapacityInGB int -} - -// CalaculateCapacity implements IPResourceManager -func (manager *bccIPResourceManager) CalaculateCapacity() *NodeCapacity { - var ( - maxENINum = GetMaxENIPerNode(manager.cpuCount) - maxIPPerENI = GetMaxIPPerENI(manager.memoryCapacityInGB) - ) - if operatorOption.Config.BCECustomerMaxENI != 0 { - maxENINum = operatorOption.Config.BCECustomerMaxENI - } - - if operatorOption.Config.BCECustomerMaxIP != 0 { - maxIPPerENI = operatorOption.Config.BCECustomerMaxIP - } - - manager.limiter = &NodeCapacity{ - MaxENINum: maxENINum, - MaxIPPerENI: maxIPPerENI, - CustomerENIResource: maxENINum, - CustomerIPResource: (maxIPPerENI - 1) * maxENINum, - } - return manager.limiter -} - -// NewBCCIPResourceManager creates a new ip resource manager for BCC instance -func NewBCCIPResourceManager(kubeClient kubernetes.Interface, preAttachedENINum int, node *corev1.Node, - cpuCount, memoryCapacityInGB int) IPResourceManager { - return &bccIPResourceManager{ - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: preAttachedENINum, - node: node, - }, - cpuCount: cpuCount, - memoryCapacityInGB: memoryCapacityInGB, - } -} - -func (manager *bccIPResourceManager) SyncCapacity(ctx context.Context) error { - return manager.patchENICapacityInfoToNode(ctx, manager.limiter.CustomerENIResource, manager.limiter.CustomerIPResource) -} - -var _ IPResourceManager = &bccIPResourceManager{} - -type noopResourceManager struct{} - -func NewNoopIPResourceManager() IPResourceManager { - return &noopResourceManager{} -} - -// CalaculateCapacity implements IPResourceManager -func (*noopResourceManager) CalaculateCapacity() *NodeCapacity { - return &NodeCapacity{} -} - -// SyncCapacity implements IPResourceManager -func (*noopResourceManager) SyncCapacity(ctx context.Context) error { - return nil -} - -var _ IPResourceManager = &noopResourceManager{} - -// In this mode, each node has its own CIDR of pod IP -type rangeIPResourceManager struct { - *simpleIPResourceManager -} - -// CalaculateCapacity implements IPResourceManager -func (*rangeIPResourceManager) CalaculateCapacity() *NodeCapacity { - panic("unimplemented") -} - -// NewRangeIPResourceManager creates a new ip resource manager for range mode -func NewRangeIPResourceManager(kubeClient kubernetes.Interface, preAttachedENINum int, node *corev1.Node) *rangeIPResourceManager { - return &rangeIPResourceManager{ - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: preAttachedENINum, - node: node, - }, - } -} - -// getIPRangeSize Calculate the maximum number of pod IPS according to the CIDR of the node -func (manager *rangeIPResourceManager) getIPRangeSize() int { - node := manager.node - // according to node specification, if spec.PodCIDRs is not empty, the first element must equal to spec.PodCIDR - podCIDRs := make([]string, 0) - if len(node.Spec.PodCIDRs) == 0 { - podCIDRs = append(podCIDRs, node.Spec.PodCIDR) - } else { - for _, podCIDR := range node.Spec.PodCIDRs { - podCIDRs = append(podCIDRs, podCIDR) - } - } - cidrs, err := k8sutilnet.ParseCIDRs(podCIDRs) - if err != nil { - log.Errorf("parse cidr for ip range error: %v", err) - } - - var ipv4RangeSize, ipv6RangeSize int64 - - for _, podCIDR := range cidrs { - size := k8sutilnet.RangeSize(podCIDR) - if k8sutilnet.IsIPv4CIDR(podCIDR) { - ipv4RangeSize += size - } else { - ipv6RangeSize += size - } - } - return math.IntMax(int(ipv4RangeSize), int(ipv6RangeSize)) -} - -func (manager *rangeIPResourceManager) SyncCapacity(ctx context.Context) error { - var maxENINum, maxIPPerENI int - maxENINum = 1 - if operatorOption.Config.BCECustomerMaxENI != 0 { - maxENINum = operatorOption.Config.BCECustomerMaxENI - } - maxIPPerENI = manager.getIPRangeSize() - if operatorOption.Config.BCECustomerMaxIP != 0 { - maxIPPerENI = operatorOption.Config.BCECustomerMaxIP - } - - return manager.patchENICapacityInfoToNode(ctx, maxENINum, maxIPPerENI) -} - -var _ IPResourceManager = &rangeIPResourceManager{} - -type crossVPCEniResourceManager struct { - *simpleIPResourceManager - bccInstance *bccapi.InstanceModel -} - -// CalaculateCapacity implements IPResourceManager -func (*crossVPCEniResourceManager) CalaculateCapacity() *NodeCapacity { - panic("unimplemented") -} - -func NewCrossVPCEniResourceManager(kubeClient kubernetes.Interface, node *corev1.Node, bccInstance *bccapi.InstanceModel) *crossVPCEniResourceManager { - return &crossVPCEniResourceManager{ - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - node: node, - }, - bccInstance: bccInstance, - } -} - -func (manager *crossVPCEniResourceManager) SyncCapacity(ctx context.Context) error { - var ( - maxEniNum int - maxEniNumByAnno int - maxEniNumByLabel int - node *corev1.Node - ) - - maxEniNum = GetMaxENIPerNode(manager.bccInstance.CpuCount) - if operatorOption.Config.BCECustomerMaxENI != 0 { - maxEniNum = operatorOption.Config.BCECustomerMaxENI - } - - node = manager.node - - maxEniNumStr, ok := node.Annotations[k8s.NodeAnnotationMaxCrossVPCEni] - if ok { - i, err := strconv.ParseInt(maxEniNumStr, 10, 32) - if err != nil { - return err - } - maxEniNumByAnno = int(i) - - if maxEniNumByAnno < maxEniNum { - maxEniNum = maxEniNumByAnno - } - } - - maxEniNumStr, ok = node.Labels[k8s.NodeLabelMaxCrossVPCEni] - if ok { - i, err := strconv.ParseInt(maxEniNumStr, 10, 32) - if err != nil { - return err - } - maxEniNumByLabel = int(i) - - if maxEniNumByLabel < maxEniNum { - maxEniNum = maxEniNumByLabel - } - } - - return manager.patchCrossVPCEniCapacityInfoToNode(ctx, maxEniNum) -} - -func (manager *crossVPCEniResourceManager) patchCrossVPCEniCapacityInfoToNode(ctx context.Context, maxEniNum int) error { - var ( - patchBodyTemplate = `[{"op":"%s","path":"/status/capacity/cross-vpc-eni.cce.io~1eni","value":"%d"}]` - patchBody = fmt.Sprintf(patchBodyTemplate, patchAddOp, maxEniNum) - node = manager.node - needUpdateEniResourceFlag = true - ) - - resource, ok := node.Status.Capacity[k8s.ResourceCrossVPCEni] - if ok { - if resource.Value() == int64(maxEniNum) { - needUpdateEniResourceFlag = false - } - patchBody = fmt.Sprintf(patchBodyTemplate, patchModiffyOp, maxEniNum) - } - - if needUpdateEniResourceFlag { - _, err := manager.kubeClient.CoreV1().Nodes().Patch(ctx, manager.node.Name, types.JSONPatchType, []byte(patchBody), metav1.PatchOptions{}, "status") - if err != nil { - log.WithContext(ctx).Errorf("failed to patch %v capacity: %v", k8s.ResourceCrossVPCEni, err) - return err - } - log.WithContext(ctx).Infof("patch %v capacity %v to node capacity success", k8s.ResourceCrossVPCEni, maxEniNum) - } - return nil -} - -var _ IPResourceManager = &crossVPCEniResourceManager{} - -// GetMaxIPPerENI returns the max num of IPs that can be attached to single ENI -// Ref: https://cloud.baidu.com/doc/VPC/s/0jwvytzll -func GetMaxIPPerENI(memoryCapacityInGB int) int { - maxIPNum := 0 - - switch { - case memoryCapacityInGB > 0 && memoryCapacityInGB < 2: - maxIPNum = 2 - case memoryCapacityInGB >= 2 && memoryCapacityInGB <= 8: - maxIPNum = 8 - case memoryCapacityInGB > 8 && memoryCapacityInGB <= 32: - maxIPNum = 16 - case memoryCapacityInGB > 32 && memoryCapacityInGB <= 64: - maxIPNum = 30 - case memoryCapacityInGB > 64: - maxIPNum = 40 - } - return maxIPNum -} - -// GetMaxENIPerNode returns the max num of ENIs that can be attached to a node -func GetMaxENIPerNode(CPUCount int) int { - maxENINum := 0 - - switch { - case CPUCount > 0 && CPUCount < 8: - maxENINum = CPUCount - case CPUCount >= 8: - maxENINum = 8 - } - - return maxENINum -} diff --git a/cce-network-v2/pkg/bce/limit/ip_resource_manager_test.go b/cce-network-v2/pkg/bce/limit/ip_resource_manager_test.go deleted file mode 100644 index b65c987..0000000 --- a/cce-network-v2/pkg/bce/limit/ip_resource_manager_test.go +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (c) 2021 Baidu, Inc. All Rights Reserved. - * - * 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 limit - -import ( - "context" - "testing" - - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/client/clientset/versioned" - bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" - "github.com/golang/mock/gomock" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/record" - - mockcloud "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/cloud/testing" - crdfake "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/client/clientset/versioned/fake" - kubefake "k8s.io/client-go/kubernetes/fake" -) - -func Test_crossVPCEniResourceManager_SyncCapacity(t *testing.T) { - type fields struct { - ctrl *gomock.Controller - simpleIPResourceManager *simpleIPResourceManager - bccInstance *bccapi.InstanceModel - } - type args struct { - ctx context.Context - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "patch 失败流程", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: true, - }, - { - name: "正常新增 resource 流程", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{}, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: false, - }, - { - name: "正常修改 resource 流程", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: false, - }, - { - name: "正常新增 resource 流程,node anno 自定义最大 eni 数量为 3", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - Annotations: map[string]string{ - "cross-vpc-eni.cce.io/maxEniNumber": "3", - }, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.MustParse("3"), - }, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: false, - }, - { - name: "正常新增 resource 流程,node anno 自定义最大 eni 错误", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - Annotations: map[string]string{ - "cross-vpc-eni.cce.io/maxEniNumber": "xxx", - }, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.MustParse("3"), - }, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: true, - }, - { - name: "正常新增 resource 流程,node label 自定义最大 eni 数量为 3", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - Labels: map[string]string{ - "cross-vpc-eni.cce.io/max-eni-number": "3", - }, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.MustParse("3"), - }, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: false, - }, - { - name: "正常新增 resource 流程,node label 自定义最大 eni 错误", - fields: func() fields { - ctrl := gomock.NewController(t) - kubeClient, _, _, _, _ := setupEnv(ctrl) - kubeClient.CoreV1().Nodes().Create(context.TODO(), &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.Quantity{}, - }, - }, - }, metav1.CreateOptions{}) - - return fields{ - ctrl: ctrl, - simpleIPResourceManager: &simpleIPResourceManager{ - kubeClient: kubeClient, - preAttachedENINum: 1, - node: &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "6.0.16.4", - Labels: map[string]string{ - "cross-vpc-eni.cce.io/max-eni-number": "xxx", - }, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - "cross-vpc-eni.cce.io/eni": resource.MustParse("3"), - }, - }, - }, - }, - bccInstance: &bccapi.InstanceModel{ - CpuCount: 8, - }, - } - }(), - args: args{ - ctx: context.TODO(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - if tt.fields.ctrl != nil { - defer tt.fields.ctrl.Finish() - } - t.Run(tt.name, func(t *testing.T) { - manager := &crossVPCEniResourceManager{ - simpleIPResourceManager: tt.fields.simpleIPResourceManager, - bccInstance: tt.fields.bccInstance, - } - if err := manager.SyncCapacity(tt.args.ctx); (err != nil) != tt.wantErr { - t.Errorf("crossVPCEniResourceManager.SyncCapacity() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestNewCrossVPCEniResourceManager(t *testing.T) { - type args struct { - kubeClient kubernetes.Interface - node *corev1.Node - bccInstance *bccapi.InstanceModel - } - tests := []struct { - name string - args args - }{ - { - name: "正常流程", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewCrossVPCEniResourceManager(tt.args.kubeClient, tt.args.node, tt.args.bccInstance); got == nil { - t.Errorf("NewCrossVPCEniResourceManager() = %v", got) - } - }) - } -} - -func setupEnv(ctrl *gomock.Controller) ( - kubernetes.Interface, - versioned.Interface, - *mockcloud.MockInterface, - record.EventBroadcaster, - record.EventRecorder, -) { - kubeClient := kubefake.NewSimpleClientset() - crdClient := crdfake.NewSimpleClientset() - cloudClient := mockcloud.NewMockInterface(ctrl) - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{ - Interface: kubeClient.CoreV1().Events(""), - }) - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cce-ipam"}) - - return kubeClient, - crdClient, cloudClient, - eventBroadcaster, recorder -} diff --git a/cce-network-v2/pkg/bce/option/client.go b/cce-network-v2/pkg/bce/option/client.go index a27c2a5..2f7017c 100644 --- a/cce-network-v2/pkg/bce/option/client.go +++ b/cce-network-v2/pkg/bce/option/client.go @@ -48,7 +48,8 @@ func BCEClient() cloud.Interface { operatorOption.Config.CCEClusterID, operatorOption.Config.BCECloudAccessKey, operatorOption.Config.BCECloudSecureKey, - k8s.Client(), option.Config.Debug) + k8s.Client(), + option.Config.Debug, operatorOption.Config.DefaultAPITimeoutLimit) if err != nil { log.Fatalf("[InitBCEClient] failed to init bce client %v", err) } diff --git a/cce-network-v2/pkg/bce/vpceni/allocator_provider.go b/cce-network-v2/pkg/bce/vpceni/allocator_provider.go index 5b0c69c..81b09ae 100644 --- a/cce-network-v2/pkg/bce/vpceni/allocator_provider.go +++ b/cce-network-v2/pkg/bce/vpceni/allocator_provider.go @@ -59,7 +59,7 @@ func (provider *BCEAllocatorProvider) Init(ctx context.Context) error { } // Start implements allocator.AllocatorProvider -func (provider *BCEAllocatorProvider) Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (allocator.NodeEventHandler, error) { +func (provider *BCEAllocatorProvider) Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (allocator.NetResourceSetEventHandler, error) { var iMetrics ipam.MetricsAPI log.Info("Starting Baidu BCE allocator...") @@ -71,17 +71,17 @@ func (provider *BCEAllocatorProvider) Start(ctx context.Context, getterUpdater i } provider.manager.nrsGetterUpdater = getterUpdater - nodeManager, err := ipam.NewNetResourceSetManager(provider.manager, getterUpdater, iMetrics, + netResourceSetManager, err := ipam.NewNetResourceSetManager(provider.manager, getterUpdater, iMetrics, operatorOption.Config.ParallelAllocWorkers, true, false) if err != nil { return nil, fmt.Errorf("unable to initialize bce instance manager: %w", err) } - if err := nodeManager.Start(ctx); err != nil { + if err := netResourceSetManager.Start(ctx); err != nil { return nil, err } - return nodeManager, nil + return netResourceSetManager, nil } // StartEndpointManager implements endpoint.DirectAllocatorStarter diff --git a/cce-network-v2/pkg/bce/vpceni/instances_test.go b/cce-network-v2/pkg/bce/vpceni/instances_test.go index eed909a..b8a875a 100644 --- a/cce-network-v2/pkg/bce/vpceni/instances_test.go +++ b/cce-network-v2/pkg/bce/vpceni/instances_test.go @@ -1,8 +1,6 @@ package vpceni import ( - "testing" - "github.com/golang/mock/gomock" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/watchers" @@ -10,8 +8,7 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" ) -func newMockInstancesManager(t *testing.T) *InstancesManager { - mockCtl := gomock.NewController(t) +func newMockInstancesManager(mockCtl *gomock.Controller) *InstancesManager { mockCloudInterface := cloudtesting.NewMockInterface(mockCtl) im := newInstancesManager(mockCloudInterface, diff --git a/cce-network-v2/pkg/bce/vpceni/node_bbc.go b/cce-network-v2/pkg/bce/vpceni/node_bbc.go index 8b2f341..0caceec 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_bbc.go +++ b/cce-network-v2/pkg/bce/vpceni/node_bbc.go @@ -24,7 +24,6 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/api/v1/models" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/metadata" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/limit" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" @@ -35,6 +34,10 @@ import ( "github.com/baidubce/bce-sdk-go/services/bbc" ) +const ( + defaultBBCMaxIPsPerENI = 40 +) + // bccNode is a wrapper of Node, which is used to distinguish bcc node type bbcNode struct { *bceNode @@ -141,7 +144,7 @@ func (n *bbcNode) createBBCENI(scopedLog *logrus.Entry) error { Spec: ccev2.ENISpec{ NodeName: n.k8sObj.Name, Type: ccev2.ENIForBBC, - UseMode: ccev2.ENIUseModeSecondaryIP, + UseMode: ccev2.ENIUseModePrimaryWithSecondaryIP, ENI: models.ENI{ ID: bbceni.Id, Name: bbceni.Name, @@ -153,6 +156,8 @@ func (n *bbcNode) createBBCENI(scopedLog *logrus.Entry) error { IPV6PrivateIPSet: ipv6IPSet, MacAddress: bbceni.MacAddress, }, + RouteTableOffset: n.k8sObj.Spec.ENI.RouteTableOffset, + InstallSourceBasedRouting: false, }, Status: ccev2.ENIStatus{}, } @@ -172,7 +177,7 @@ func (n *bbcNode) createBBCENI(scopedLog *logrus.Entry) error { return n.updateNrsSubnetIfNeed([]string{bbceni.SubnetId}) } -func (n *bbcNode) calculateLimiter(scopeLog *logrus.Entry) (limit.IPResourceManager, error) { +func (n *bbcNode) refreshENIQuota(scopeLog *logrus.Entry) (ENIQuotaManager, error) { scopeLog = scopeLog.WithField("nodeName", n.k8sObj.Name).WithField("method", "generateIPResourceManager") client := k8s.WatcherClient() if client == nil { @@ -180,13 +185,15 @@ func (n *bbcNode) calculateLimiter(scopeLog *logrus.Entry) (limit.IPResourceMana } k8sNode, err := client.Informers.Core().V1().Nodes().Lister().Get(n.k8sObj.Name) if err != nil { - scopeLog.Errorf("Get node failed: %v", err) - return nil, err + return nil, fmt.Errorf("failed to get k8s node %s: %v", n.k8sObj.Name, err) } - resourceManger := limit.NewBBCIPResourceManager(client, k8sNode) - n.capacity = resourceManger.CalaculateCapacity() - return resourceManger, nil + // default bbc ip quota + eniQuota := newCustomerIPQuota(scopeLog, client, k8sNode, n.instanceID, n.manager.bceclient) + eniQuota.SetMaxENI(1) + eniQuota.SetMaxIP(defaultBBCMaxIPsPerENI) + + return eniQuota, nil } // allocateIPs implements realNodeInf @@ -268,14 +275,14 @@ func (n *bbcNode) prepareIPAllocation(scopedLog *logrus.Entry) (a *ipam.Allocati return allocation, nil } - if n.capacity != nil { - err := n.manager.ForeachInstance(n.instanceID, func(instanceID, interfaceID string, iface ipamTypes.InterfaceRevision) error { + eniQuota := n.getENIQuota() + if eniQuota != nil { + n.manager.ForeachInstance(n.instanceID, func(instanceID, interfaceID string, iface ipamTypes.InterfaceRevision) error { e, ok := iface.Resource.(*eniResource) if !ok { return nil } - - allocation.AvailableForAllocationIPv4 = n.capacity.MaxIPPerENI - len(e.Spec.PrivateIPSet) + allocation.AvailableForAllocationIPv4 = eniQuota.GetMaxIP() - len(e.Spec.PrivateIPSet) allocation.InterfaceID = e.Name if n.enableNodeAnnotationSubnet() { @@ -308,8 +315,8 @@ func (n *bbcNode) prepareIPAllocation(scopedLog *logrus.Entry) (a *ipam.Allocati } // GetMaximumAllocatable implements realNodeInf -func (*bbcNode) getMaximumAllocatable(capacity *limit.NodeCapacity) int { - return capacity.MaxIPPerENI - 1 +func (*bbcNode) getMaximumAllocatable(eniQuota ENIQuotaManager) int { + return eniQuota.GetMaxIP() - 1 } // GetMinimumAllocatable implements realNodeInf diff --git a/cce-network-v2/pkg/bce/vpceni/node_bbc_test.go b/cce-network-v2/pkg/bce/vpceni/node_bbc_test.go index d8d57bd..3720016 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_bbc_test.go +++ b/cce-network-v2/pkg/bce/vpceni/node_bbc_test.go @@ -7,12 +7,12 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" k8sutilnet "k8s.io/utils/net" operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/limit" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" @@ -257,7 +257,8 @@ func Test_bbcNode_allocateIPs(t *testing.T) { // 包含初始化 mock 对象,保存到 clientgo缓存中,并返回 BCCNode 实例 func bbcTestContext(t *testing.T) (*bceNode, error) { ccemock.InitMockEnv() - im := newMockInstancesManager(t) + mockCtl := gomock.NewController(t) + im := newMockInstancesManager(mockCtl) k8sObj := ccemock.NewMockSimpleNrs("10.128.34.56", "bbc") err := ccemock.EnsureNrsToInformer(t, []*ccev2.NetResourceSet{k8sObj}) @@ -265,6 +266,12 @@ func bbcTestContext(t *testing.T) (*bceNode, error) { return nil, err } + k8sNode := ccemock.NewMockNodeFromNrs(k8sObj) + err = ccemock.EnsureNodeToInformer(t, []*corev1.Node{k8sNode}) + if !assert.NoError(t, err, "ensure node to informer failed") { + return nil, err + } + sbn := ccemock.NewMockSubnet("sbn-bbcprimary", "10.128.34.0/24") err = ccemock.EnsureSubnetsToInformer(t, []*ccev1.Subnet{sbn}) if !assert.NoError(t, err, "ensure subnet to informer failed") { @@ -278,10 +285,9 @@ func bbcTestContext(t *testing.T) (*bceNode, error) { node := NewNode(nil, k8sObj, im) assert.NotNil(t, node) - node.capacity = &limit.NodeCapacity{ - MaxENINum: 1, - MaxIPPerENI: 40, - } + node.eniQuota = newCustomerIPQuota(log, k8s.Client(), nil, k8sObj.Spec.InstanceID, im.bceclient) + node.eniQuota.SetMaxENI(1) + node.eniQuota.SetMaxIP(40) return node, nil } diff --git a/cce-network-v2/pkg/bce/vpceni/node_bcc.go b/cce-network-v2/pkg/bce/vpceni/node_bcc.go index 391188c..9337ce5 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_bcc.go +++ b/cce-network-v2/pkg/bce/vpceni/node_bcc.go @@ -22,20 +22,29 @@ import ( operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/watchers" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/metadata" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/limit" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" ccev2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/math" + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/client-go/tools/cache" ) // bccNode is a wrapper of Node, which is used to distinguish bcc node type bccNode struct { *bceNode + + // bcc instance info + bccInfo *bccapi.InstanceModel + + // usePrimaryENIWithSecondaryMode primary eni with secondary IP mode only use by ebc instance + usePrimaryENIWithSecondaryMode bool } func newBCCNode(super *bceNode) *bccNode { @@ -46,8 +55,34 @@ func newBCCNode(super *bceNode) *bccNode { return node } -func (n *bccNode) calculateLimiter(scopeLog *logrus.Entry) (limit.IPResourceManager, error) { - scopeLog = scopeLog.WithField("nodeName", n.k8sObj.Name).WithField("method", "generateIPResourceManager") +func (n *bccNode) refreshBCCInfo() error { + if n.bccInfo != nil { + return nil + } + bccInfo, err := n.manager.bceclient.GetBCCInstanceDetail(context.TODO(), n.instanceID) + if err != nil { + n.log.Errorf("faild to get bcc instance detail: %v", err) + return err + } + n.log.WithField("bccInfo", logfields.Repr(bccInfo)).Infof("Get bcc instance detail") + n.bccInfo = bccInfo + + // TODO delete this test code block after bcc instance eniQuota is fixed + // The tentative plan is for April 15th + { + if n.bccInfo.Spec == "ebc.la2.c256m1024.2d" { + bccInfo.EniQuota = 0 + } + } + + if bccInfo.EniQuota == 0 { + n.usePrimaryENIWithSecondaryMode = true + } + return nil +} + +func (n *bccNode) refreshENIQuota(scopeLog *logrus.Entry) (ENIQuotaManager, error) { + scopeLog = scopeLog.WithField("nodeName", n.k8sObj.Name).WithField("method", "getENIQuota") client := k8s.WatcherClient() if client == nil { scopeLog.Fatal("K8s client is nil") @@ -58,36 +93,68 @@ func (n *bccNode) calculateLimiter(scopeLog *logrus.Entry) (limit.IPResourceMana return nil, err } - bccInstance, err := n.manager.bceclient.GetBCCInstanceDetail(context.TODO(), n.k8sObj.Spec.InstanceID) + err = n.refreshBCCInfo() if err != nil { - scopeLog.WithField("instanceID", n.k8sObj.Spec.InstanceID).Errorf("GetBCCInstanceDetail failed: %v", err) return nil, err } - resourceManager := limit.NewBCCIPResourceManager(client, n.k8sObj.Spec.ENI.PreAllocateENI, k8sNode, bccInstance.CpuCount, bccInstance.MemoryCapacityInGB) + // if bcc instance is not created by cce-network-v2, there is no need to check IP resouce + if n.bccInfo == nil { + return nil, fmt.Errorf("bcc info instance is nil") + } + + eniQuota := newCustomerIPQuota(scopeLog, client, k8sNode, n.instanceID, n.manager.bceclient) + // default bbc ip quota + defaltENINums, defaultIPs := getDefaultBCCEniQuota(k8sNode) + + // Expect all BCC models to support ENI + // EBC models may not support eni, and there are also some non console created + // BCCs that do not support this parameter + if n.bccInfo.EniQuota != 0 || n.k8sObj.Spec.ENI.InstanceType == string(ccev2.ENIForBCC) { + eniQuota.SetMaxENI(n.bccInfo.EniQuota) + if n.bccInfo.EniQuota == 0 { + eniQuota.SetMaxENI(defaltENINums) + } + } + eniQuota.SetMaxIP(defaultIPs) - n.capacity = resourceManager.CalaculateCapacity() // if node use primary ENI mode, there is no need to check IP resouce if n.k8sObj.Spec.ENI.UseMode == string(ccev2.ENIUseModePrimaryIP) { - n.capacity.CustomerIPResource = 0 - } else { - n.capacity.CustomerENIResource = 0 + eniQuota.SetMaxIP(0) } - return resourceManager, nil + return eniQuota, nil +} + +func getDefaultBCCEniQuota(k8sNode *corev1.Node) (int, int) { + var ( + cpuNum, memGB int + ) + if cpu, ok := k8sNode.Status.Capacity[corev1.ResourceCPU]; ok { + cpuNum = int(cpu.ScaledValue(resource.Milli)) / 1000 + } + if mem, ok := k8sNode.Status.Capacity[corev1.ResourceMemory]; ok { + memGB = int(mem.Value() / 1024 / 1024 / 1024) + } + return calculateMaxENIPerNode(cpuNum), calculateMaxIPPerENI(memGB) } // PrepareIPAllocation is called to calculate the number of IPs that // can be allocated on the node and whether a new network interface // must be attached to the node. - func (n *bccNode) prepareIPAllocation(scopedLog *logrus.Entry) (a *ipam.AllocationAction, err error) { + err = n.refreshBCCInfo() + if err != nil { + scopedLog.Errorf("failed to refresh ebc info: %v", err) + return nil, fmt.Errorf("failed to refresh ebc info") + } return n.__prepareIPAllocation(scopedLog, true) } + func (n *bccNode) __prepareIPAllocation(scopedLog *logrus.Entry, checkSubnet bool) (a *ipam.AllocationAction, err error) { a = &ipam.AllocationAction{} - limiter := n.bceNode.calculateLimiter() - if limiter == nil { - return nil, fmt.Errorf("limiter is nil") + eniQuota := n.bceNode.getENIQuota() + if eniQuota == nil { + return nil, fmt.Errorf("eniQuota is nil, please retry later") } eniCount := 0 @@ -126,7 +193,7 @@ func (n *bccNode) __prepareIPAllocation(scopedLog *logrus.Entry, checkSubnet boo } // The limits include the primary IP, so we need to take it into account // when computing the effective number of available addresses on the ENI. - effectiveLimits := limiter.MaxIPPerENI + effectiveLimits := eniQuota.GetMaxIP() scopedLog.WithFields(logrus.Fields{ "addressLimit": effectiveLimits, }).Debug("Considering ENI for allocation") @@ -196,7 +263,7 @@ func (n *bccNode) __prepareIPAllocation(scopedLog *logrus.Entry, checkSubnet boo } return nil }) - a.AvailableInterfaces = limiter.MaxENINum - eniCount + a.AvailableInterfaces = math.IntMax(eniQuota.GetMaxENI()-eniCount, 0) return } @@ -261,15 +328,15 @@ func (n *bccNode) releaseIPs(ctx context.Context, release *ipam.ReleaseAction, i } // GetMaximumAllocatable Impl -func (n *bccNode) getMaximumAllocatable(capacity *limit.NodeCapacity) int { +func (n *bccNode) getMaximumAllocatable(eniQuota ENIQuotaManager) int { if n.k8sObj.Spec.ENI.UseMode == string(ccev2.ENIUseModePrimaryIP) { return 0 } - if capacity.MaxENINum == 0 { - return capacity.MaxIPPerENI - 1 + if eniQuota.GetMaxENI() == 0 { + return eniQuota.GetMaxIP() - 1 } - max := capacity.MaxENINum * (capacity.MaxIPPerENI - 1) + max := eniQuota.GetMaxENI() * (eniQuota.GetMaxIP() - 1) if operatorOption.Config.EnableIPv6 { max = max * 2 } diff --git a/cce-network-v2/pkg/bce/vpceni/node_bcc_eni.go b/cce-network-v2/pkg/bce/vpceni/node_bcc_eni.go index e265c5d..ee2b51b 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_bcc_eni.go +++ b/cce-network-v2/pkg/bce/vpceni/node_bcc_eni.go @@ -146,7 +146,7 @@ func (n *bccNode) createInterface(ctx context.Context, allocation *ipam.Allocati } var ( - limiter = n.bceNode.calculateLimiter() + eniQuota = n.bceNode.getENIQuota() availableENICount = 0 inuseENICount = 0 ) @@ -164,7 +164,7 @@ func (n *bccNode) createInterface(ctx context.Context, allocation *ipam.Allocati } } else if n.k8sObj.Spec.ENI.UseMode == string(ccev2.ENIUseModeSecondaryIP) { // if the length of private ip set is greater than the MaxIPPerENI, then the ENI is in use - if len(e.Spec.ENI.PrivateIPSet) >= limiter.MaxIPPerENI { + if len(e.Spec.ENI.PrivateIPSet) >= eniQuota.GetMaxIP() { inuseENICount++ } } @@ -172,7 +172,7 @@ func (n *bccNode) createInterface(ctx context.Context, allocation *ipam.Allocati return nil }) - if availableENICount >= limiter.MaxENINum { + if availableENICount >= eniQuota.GetMaxENI() { msg = errUnableToDetermineLimits err = fmt.Errorf(msg) return @@ -253,7 +253,7 @@ func (n *bccNode) createENIOnCluster(ctx context.Context, scopedLog *logrus.Entr }, RouteTableOffset: resource.Spec.ENI.RouteTableOffset, InstallSourceBasedRouting: resource.Spec.ENI.InstallSourceBasedRouting, - Type: ccev2.ENIForBCC, + Type: ccev2.ENIType(resource.Spec.ENI.InstanceType), }, } @@ -268,7 +268,7 @@ func (n *bccNode) createENIOnCluster(ctx context.Context, scopedLog *logrus.Entr newENI.Spec.ENI.ID = eniID newENI.Name = eniID - n.creatingEni.addCreatingENI(newENI) + n.creatingEni.addCreatingENI(newENI.Name, time.Now()) _, err = k8s.CCEClient().CceV2().ENIs().Create(ctx, newENI, metav1.CreateOptions{}) if err != nil { @@ -315,8 +315,7 @@ func (n *bceNode) createENI(ctx context.Context, resource *ccev2.ENI, scopedLog EnterpriseSecurityGroupIds: resource.Spec.ENI.EnterpriseSecurityGroupIds, Description: defaults.DefaultENIDescription, PrivateIpSet: []enisdk.PrivateIp{{ - Primary: true, - PrivateIpAddress: "", + Primary: true, }}, } if createENIArgs.EnterpriseSecurityGroupIds == nil { @@ -324,19 +323,20 @@ func (n *bceNode) createENI(ctx context.Context, resource *ccev2.ENI, scopedLog } // use the given ip to create a new ENI - var privateIPs []*enisdk.PrivateIp + var privateIPs []enisdk.PrivateIp for i := 0; i < len(resource.Spec.ENI.PrivateIPSet); i++ { - privateIPs = append(privateIPs, &enisdk.PrivateIp{ + privateIPs = append(privateIPs, enisdk.PrivateIp{ PublicIpAddress: resource.Spec.ENI.PrivateIPSet[i].PublicIPAddress, Primary: resource.Spec.ENI.PrivateIPSet[i].Primary, PrivateIpAddress: resource.Spec.ENI.PrivateIPSet[i].PrivateIPAddress, }) } if len(privateIPs) == 0 { - privateIPs = append(privateIPs, &enisdk.PrivateIp{ + privateIPs = append(privateIPs, enisdk.PrivateIp{ Primary: true, }) } + createENIArgs.PrivateIpSet = privateIPs eniID, err := n.manager.bceclient.CreateENI(ctx, createENIArgs) if err != nil { diff --git a/cce-network-v2/pkg/bce/vpceni/node_bcc_test.go b/cce-network-v2/pkg/bce/vpceni/node_bcc_test.go new file mode 100644 index 0000000..e95a710 --- /dev/null +++ b/cce-network-v2/pkg/bce/vpceni/node_bcc_test.go @@ -0,0 +1,23 @@ +package vpceni + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/stretchr/testify/assert" +) + +func Test_getDefaultBCCEniQuota(t *testing.T) { + maxEni, maxIP := getDefaultBCCEniQuota(&corev1.Node{ + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + corev1.ResourceCPU: *resource.NewScaledQuantity(10000, resource.Milli), + corev1.ResourceMemory: *resource.NewQuantity(6*1024*1024*1024, resource.BinarySI), + }, + }, + }) + assert.Equal(t, 8, maxEni) + assert.Equal(t, 8, maxIP) +} diff --git a/cce-network-v2/pkg/bce/vpceni/node_ebc.go b/cce-network-v2/pkg/bce/vpceni/node_ebc.go new file mode 100644 index 0000000..d6046e9 --- /dev/null +++ b/cce-network-v2/pkg/bce/vpceni/node_ebc.go @@ -0,0 +1,233 @@ +package vpceni + +import ( + "context" + "fmt" + "net" + + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/api/v1/models" + operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/metadata" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" + ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" + ccev2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ebc is a wrapper of bcc node, which is used to distinguish ebc node +// +// warning: due to the fact that the EBC primary interface with secondary IP mode does not +// support extension functions such as PSTS, and its scalability is weak, it is particularly +// dependent on the number of subnet IPs +type ebcNode struct { + *bccNode + + primaryENISubnetID string + haveCreatePrimaryENI bool +} + +func newEBCNode(super *bccNode) *ebcNode { + node := &ebcNode{ + bccNode: super, + } + node.instanceType = string(metadata.InstanceTypeExEBC) + return node +} + +func (n *ebcNode) prepareIPAllocation(scopedLog *logrus.Entry) (a *ipam.AllocationAction, err error) { + a, err = n.bccNode.prepareIPAllocation(scopedLog) + if err != nil || n.haveCreatePrimaryENI { + return a, err + } + err = n.refreshPrimarySubnet() + if err != nil { + return a, err + } + + // may should create a new primary ENI + if a.AvailableInterfaces == 0 && a.InterfaceID == "" && n.usePrimaryENIWithSecondaryMode { + n.manager.ForeachInstance(n.instanceID, + func(instanceID, interfaceID string, iface ipamTypes.InterfaceRevision) error { + _, ok := iface.Resource.(*eniResource) + if !ok { + return nil + } + n.haveCreatePrimaryENI = true + return nil + }) + if !n.haveCreatePrimaryENI { + // this is an important opportunity to create eni + a.AvailableInterfaces = 1 + } + } + return a, err +} + +func (n *ebcNode) refreshPrimarySubnet() error { + // get customer quota from cloud + err := n.refreshBCCInfo() + if err != nil { + return err + } + n.primaryENISubnetID = n.bccInfo.NicInfo.SubnetId + subnets := n.FilterAvailableSubnetIds([]string{n.primaryENISubnetID}) + n.availableSubnets = subnets + return nil +} + +// CreateInterface create a new ENI +func (n *ebcNode) createInterface(ctx context.Context, allocation *ipam.AllocationAction, scopedLog *logrus.Entry) (interfaceNum int, msg string, err error) { + if n.usePrimaryENIWithSecondaryMode { + scopedLog.Infof("The maximum number of ENIs is 0, use primary interface with seconary IP mode") + err := n.createPrimaryENIOnCluster(ctx, scopedLog, n.k8sObj) + if err != nil { + return 0, "", err + } + return 1, "create primary ENI on cluster", nil + } + return n.bccNode.createInterface(ctx, allocation, scopedLog) +} + +func (n *ebcNode) createPrimaryENIOnCluster(ctx context.Context, scopedLog *logrus.Entry, resource *ccev2.NetResourceSet) error { + // get customer quota from cloud + // TODO: we will use vpc data to set ip quota + err := n.refreshBCCInfo() + if err != nil { + return err + } + bccInfo := n.bccInfo + err = n.refreshPrimarySubnet() + if err != nil { + return err + } + + // create subnet object + zone := api.TransAvailableZoneToZoneName(operatorOption.Config.BCECloudContry, operatorOption.Config.BCECloudRegion, resource.Spec.ENI.AvailabilityZone) + + newENI := &ccev2.ENI{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + k8s.LabelInstanceID: n.instanceID, + k8s.LabelNodeName: resource.Name, + k8s.LabelENIType: resource.Spec.ENI.InstanceType, + k8s.LabelENIUseMode: string(ccev2.ENIUseModePrimaryWithSecondaryIP), + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: ccev2.SchemeGroupVersion.String(), + Kind: ccev2.NRSKindDefinition, + Name: resource.Name, + UID: resource.UID, + }}, + Name: n.bccInfo.NicInfo.EniId, + }, + Spec: ccev2.ENISpec{ + NodeName: resource.Name, + UseMode: ccev2.ENIUseModePrimaryWithSecondaryIP, + ENI: models.ENI{ + ID: bccInfo.NicInfo.EniId, + Name: bccInfo.NicInfo.Name, + ZoneName: zone, + InstanceID: n.instanceID, + VpcID: bccInfo.NicInfo.VpcId, + SubnetID: bccInfo.NicInfo.SubnetId, + SecurityGroupIds: bccInfo.NicInfo.SecurityGroups, + }, + RouteTableOffset: resource.Spec.ENI.RouteTableOffset, + InstallSourceBasedRouting: false, + Type: ccev2.ENIType(resource.Spec.ENI.InstanceType), + }, + } + _, err = k8s.CCEClient().CceV2().ENIs().Create(ctx, newENI, metav1.CreateOptions{}) + if err != nil { + scopedLog.Errorf("failed to create primary ENI %s with secondary IP: %v", newENI.Name, err) + return fmt.Errorf("failed to create primary ENI %s on k8s", newENI.Name) + } + n.haveCreatePrimaryENI = true + return nil +} + +// AllocateIPs is called after invoking PrepareIPAllocation and needs +// to perform the actual allocation. +func (n *ebcNode) allocateIPs(ctx context.Context, scopedLog *logrus.Entry, allocation *ipam.AllocationAction, ipv4ToAllocate, ipv6ToAllocate int) ( + ipv4PrivateIPSet, ipv6PrivateIPSet []*models.PrivateIP, err error) { + // case1: use bcc eni with secondary ip mode + if !n.usePrimaryENIWithSecondaryMode { + return n.bccNode.allocateIPs(ctx, scopedLog, allocation, ipv4ToAllocate, ipv6ToAllocate) + } + + // case2: use primary eni with secondary ip mode + if ipv4ToAllocate > 0 { + // allocate ip + resp, err := n.manager.bceclient.BCCBatchAddIP(ctx, &bccapi.BatchAddIpArgs{ + InstanceId: n.instanceID, + SecondaryPrivateIpAddressCount: ipv4ToAllocate, + AllocateMultiIpv6Addr: ipv6ToAllocate > 0, + }) + err = n.manager.HandlerVPCError(scopedLog, err, string(allocation.PoolID)) + if err != nil { + return nil, nil, fmt.Errorf("allocate ip to eni %s failed: %v", allocation.InterfaceID, err) + } + scopedLog.WithField("ips", resp.PrivateIps).Debug("allocate ip to eni success") + + for _, ipstring := range resp.PrivateIps { + ip := net.ParseIP(ipstring) + if ip.To4() == nil { + ipv6PrivateIPSet = append(ipv6PrivateIPSet, &models.PrivateIP{ + PrivateIPAddress: ipstring, + SubnetID: string(allocation.PoolID), + }) + } else { + ipv4PrivateIPSet = append(ipv4PrivateIPSet, &models.PrivateIP{ + PrivateIPAddress: ipstring, + SubnetID: string(allocation.PoolID), + }) + } + } + } + return +} + +// ReleaseIPs is called after invoking PrepareIPRelease and needs to +// perform the release of IPs. +func (n *ebcNode) releaseIPs(ctx context.Context, release *ipam.ReleaseAction, ipv4ToRelease, ipv6ToRelease []string) error { + if !n.usePrimaryENIWithSecondaryMode { + return n.bccNode.releaseIPs(ctx, release, ipv4ToRelease, ipv6ToRelease) + } + if len(ipv4ToRelease) > 0 { + err := n.manager.bceclient.BCCBatchDelIP(ctx, &bccapi.BatchDelIpArgs{ + InstanceId: n.instanceID, + PrivateIps: ipv4ToRelease, + }) + if err != nil { + return fmt.Errorf("release ipv4 %v from eni %s failed: %v", ipv4ToRelease, release.InterfaceID, err) + } + } + if len(ipv6ToRelease) > 0 { + err := n.manager.bceclient.BCCBatchDelIP(ctx, &bccapi.BatchDelIpArgs{ + InstanceId: n.instanceID, + PrivateIps: ipv6ToRelease, + }) + if err != nil { + return fmt.Errorf("release ipv4 %v from eni %s failed: %v", ipv4ToRelease, release.InterfaceID, err) + } + } + return nil +} + +func (n *ebcNode) allocateIPCrossSubnet(ctx context.Context, sbnID string) (result []*models.PrivateIP, eniID string, err error) { + if !n.usePrimaryENIWithSecondaryMode { + return n.bccNode.allocateIPCrossSubnet(ctx, sbnID) + } + return nil, "", fmt.Errorf("ebc primary interface with secondary IP mode not support allocate ip cross subnet") +} + +func (n *ebcNode) reuseIPs(ctx context.Context, ips []*models.PrivateIP, owner string) (eniID string, err error) { + if !n.usePrimaryENIWithSecondaryMode { + return n.bccNode.reuseIPs(ctx, ips, owner) + } + return "", fmt.Errorf("ebc primary interface with secondary IP mode not support allocate ip cross subnet") +} diff --git a/cce-network-v2/pkg/bce/vpceni/node_super.go b/cce-network-v2/pkg/bce/vpceni/node_super.go index 8214ea4..8c850f2 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_super.go +++ b/cce-network-v2/pkg/bce/vpceni/node_super.go @@ -19,6 +19,7 @@ import ( "fmt" "net" "reflect" + "strconv" "strings" "time" @@ -36,7 +37,6 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/metadata" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/bcesync" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/limit" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/endpoint" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" @@ -50,6 +50,10 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/math" ) +const ( + DayDuration = 24 * time.Hour +) + func init() { ccev2.AddToScheme(scheme.Scheme) } @@ -73,7 +77,7 @@ const ( ) type realNodeInf interface { - calculateLimiter(scopeLog *logrus.Entry) (limit.IPResourceManager, error) + refreshENIQuota(scopeLog *logrus.Entry) (ENIQuotaManager, error) createInterface(ctx context.Context, allocation *ipam.AllocationAction, scopedLog *logrus.Entry) (interfaceNum int, msg string, err error) // PrepareIPAllocation is called to calculate the number of IPs that // can be allocated on the node and whether a new network interface @@ -85,7 +89,7 @@ type realNodeInf interface { ipv4PrivateIPSet, ipv6PrivateIPSet []*models.PrivateIP, err error) releaseIPs(ctx context.Context, release *ipam.ReleaseAction, ipv4ToRelease, ipv6ToRelease []string) error - getMaximumAllocatable(capacity *limit.NodeCapacity) int + getMaximumAllocatable(eniQuota ENIQuotaManager) int getMinimumAllocatable() int // direct ip allocation @@ -116,9 +120,10 @@ type bceNode struct { // The node currently being created by ENI should have only one. creatingEni *creatingEniSet - // limit - capacity *limit.NodeCapacity - limiterLock lock.RWMutex + // eniQuota is the quota manager for ENI + eniQuota ENIQuotaManager + lastResyncEniQuotaTime time.Time + limiterLock lock.RWMutex // nextCreateENITime not allow create eni before this time nextCreateENITime *time.Time @@ -158,75 +163,119 @@ func NewNode(node *ipam.NetResource, k8sObj *ccev2.NetResourceSet, manager *Inst switch anode.k8sObj.Spec.ENI.InstanceType { case string(metadata.InstanceTypeExBBC): anode.real = newBBCNode(anode) + case string(metadata.InstanceTypeExEBC), string(metadata.InstanceTypeExEHC): + anode.real = newEBCNode(newBCCNode(anode)) default: anode.real = newBCCNode(anode) } + anode.getENIQuota() } return anode } -func (n *bceNode) calculateLimiter() *limit.NodeCapacity { +func (n *bceNode) getENIQuota() ENIQuotaManager { n.limiterLock.Lock() defer n.limiterLock.Unlock() - // faset path to claculate limiter - if isSetLimiter(n) { - return n.capacity - } - - // slow path to claculate limiter ctx := logfields.NewContext() scopedLog := n.log.WithFields(logrus.Fields{ "nodeName": n.k8sObj.Name, - "method": "calculateLimiter", + "method": "getENIQuota", "instanceType": n.k8sObj.Spec.ENI.InstanceType, }).WithContext(ctx) - ipResourceManager, err := n.real.calculateLimiter(scopedLog) - if err != nil { - scopedLog.Errorf("Calculate limiter failed: %v", err) - return n.capacity + if n.eniQuota != nil && n.lastResyncEniQuotaTime.Add(DayDuration).After(time.Now()) { + return n.eniQuota } - // 3. Override the CCE Node capacity to the CCE Node object - err = n.overrideENICapacityToNode(ipResourceManager, n.capacity) - if err != nil { - scopedLog.Errorf("Override ENI capacity to NetResourceSet failed: %v", err) - return n.capacity + + // fast path to claculate eni quota when operaor has been restarted + // isSetLimiter returns true if the node has set limiter + // if the node has set limiter, it will return a limiter from the ENI spec + if n.k8sObj.Annotations != nil && n.k8sObj.Annotations[k8s.AnnotationIPResourceCapacitySynced] != "" { + lastResyncTime := n.k8sObj.Annotations[k8s.AnnotationIPResourceCapacitySynced] + t, err := time.Parse(time.RFC3339, lastResyncTime) + if err != nil || t.Add(DayDuration).Before(time.Now()) { + goto slowPath + } + eniQuota := newCustomerIPQuota(nil, nil, nil, n.instanceID, nil) + eniQuota.SetMaxENI(n.k8sObj.Spec.ENI.MaxAllocateENI) + eniQuota.SetMaxIP(n.k8sObj.Spec.ENI.MaxIPsPerENI) + // use customer max ip nums if defiend + if operatorOption.Config.BCECustomerMaxIP != 0 && operatorOption.Config.BCECustomerMaxIP == eniQuota.GetMaxIP() { + goto slowPath + } + n.eniQuota = eniQuota + return n.eniQuota } - scopedLog.WithField("limiter", logfields.Json(n.capacity)). - Info("override ENI capacity to NetResourceSet success") - return n.capacity + +slowPath: + return n.slowCalculateRealENICapacity(scopedLog) } -// isSetLimiter returns true if the node has set limiter -// if the node has set limiter, it will return a limiter from the ENI spec -func isSetLimiter(n *bceNode) bool { - if n.capacity != nil { - return true +// slow path to claculate limiter +func (n *bceNode) slowCalculateRealENICapacity(scopedLog *logrus.Entry) ENIQuotaManager { + eniQuota, err := n.real.refreshENIQuota(scopedLog) + if err != nil { + scopedLog.Errorf("calculate eniQuota failed: %v", err) + return nil } - if n.k8sObj.Labels != nil && n.k8sObj.Labels[k8s.LabelIPResourceCapacitySynced] != "" { - n.capacity = &limit.NodeCapacity{ - MaxENINum: n.k8sObj.Spec.ENI.MaxAllocateENI, - MaxIPPerENI: n.k8sObj.Spec.ENI.MaxIPsPerENI, + + if len(n.k8sObj.Annotations) > 0 { + // override max eni num from annotation + if maxENIStr, ok := n.k8sObj.Annotations[k8s.AnnotationNodeMaxENINum]; ok { + maxENI, err := strconv.Atoi(maxENIStr) + if err != nil { + scopedLog.Errorf("parse max eni num [%s] from annotation failed: %v", maxENIStr, err) + } else if maxENI < eniQuota.GetMaxENI() { + scopedLog.Warnf("max eni num from annotation is smaller than real: %d < %d", maxENI, eniQuota.GetMaxENI()) + } else if maxENI >= 0 { + eniQuota.SetMaxENI(maxENI) + scopedLog.Infof("override max eni num from annotation: %d", maxENI) + } } - // use customer max ip nums if defiend - if operatorOption.Config.BCECustomerMaxIP != 0 && operatorOption.Config.BCECustomerMaxIP != n.capacity.MaxIPPerENI { - return false + + // override max ip num from annotation + if maxIPStr, ok := n.k8sObj.Annotations[k8s.AnnotationNodeMaxPerENIIPsNum]; ok { + maxIP, err := strconv.Atoi(maxIPStr) + if err != nil { + scopedLog.Errorf("parse max ip num [%s] from annotation failed: %v", maxIPStr, err) + } else if maxIP >= 0 { + eniQuota.SetMaxIP(maxIP) + } } - return true } - return false + + // if eniQuota is empty, it means the node has not set quota + // it should be retry later + if eniQuota.GetMaxENI() == 0 && eniQuota.GetMaxIP() == 0 { + return nil + } + // 3. Override the CCE Node capacity to the CCE Node object + err = n.overrideENICapacityToNode(eniQuota) + if err != nil { + scopedLog.Errorf("override ENI capacity to NetResourceSet failed: %v", err) + return nil + } + + n.eniQuota = eniQuota + n.lastResyncEniQuotaTime = time.Now() + + scopedLog.WithFields(logrus.Fields{ + "maxENI": n.eniQuota.GetMaxENI(), + "maxIPPerENI": n.eniQuota.GetMaxIP(), + }).Info("override ENI capacity to NetResourceSet success") + return n.eniQuota } // overrideENICapacityToNode accoding to the Node.limiter field, override the // capacity of ENI to the Node.k8sObj.Spec -func (n *bceNode) overrideENICapacityToNode(ipResourceManager limit.IPResourceManager, limiter *limit.NodeCapacity) error { +func (n *bceNode) overrideENICapacityToNode(eniQuota ENIQuotaManager) error { ctx := logfields.NewContext() // todo move capacity to better place in k8s rest api - err := ipResourceManager.SyncCapacity(ctx) + err := eniQuota.SyncCapacityToK8s(ctx) if err != nil { - return fmt.Errorf("sync capacity failed: %v", err) + return fmt.Errorf("failed to sync eni capacity to k8s node: %v", err) } // update the node until 30s timeout // if update operation return error, we will get the leatest version of node and try again @@ -236,17 +285,21 @@ func (n *bceNode) overrideENICapacityToNode(ipResourceManager limit.IPResourceMa return false, err } k8sObj := old.DeepCopy() - k8sObj.Spec.ENI.MaxAllocateENI = limiter.MaxENINum - k8sObj.Spec.ENI.MaxIPsPerENI = limiter.MaxIPPerENI - if k8sObj.Labels == nil { - k8sObj.Labels = map[string]string{} + k8sObj.Spec.ENI.MaxAllocateENI = eniQuota.GetMaxENI() + k8sObj.Spec.ENI.MaxIPsPerENI = eniQuota.GetMaxIP() + if k8sObj.Annotations == nil { + k8sObj.Annotations = map[string]string{} } - k8sObj.Labels[k8s.LabelIPResourceCapacitySynced] = "true" + + now := time.Now().Format(time.RFC3339) + k8sObj.Annotations[k8s.AnnotationIPResourceCapacitySynced] = now + n.manager.nrsGetterUpdater.Update(old, k8sObj) updated, err := n.manager.nrsGetterUpdater.Update(old, k8sObj) - if err == nil && updated != nil { + if err == nil { n.k8sObj = updated return true, nil } + n.log.Errorf("failed to override nrs %s capacity: %v", n.k8sObj.Name, err) return false, nil }) if err != nil { @@ -269,7 +322,8 @@ func (n *bceNode) UpdatedNode(obj *ccev2.NetResourceSet) { // of the functions AllocateIPs(), ReleaseIPs() and CreateInterface(). func (n *bceNode) ResyncInterfacesAndIPs(ctx context.Context, scopedLog *logrus.Entry) (ipamTypes.AllocationMap, error) { var ( - a = ipamTypes.AllocationMap{} + a = ipamTypes.AllocationMap{} + allENIId []string ) n.waitForENISynced(ctx) @@ -285,11 +339,13 @@ func (n *bceNode) ResyncInterfacesAndIPs(ctx context.Context, scopedLog *logrus. if !ok { return nil } + allENIId = append(allENIId, e.Name) // eni is not ready to use eniSubnet := e.Spec.ENI.SubnetID if e.Status.VPCStatus != ccev2.VPCENIStatusInuse { haveCreatingENI = true + n.creatingEni.addCreatingENI(e.Name, e.CreationTimestamp.Time) return nil } @@ -326,6 +382,7 @@ func (n *bceNode) ResyncInterfacesAndIPs(ctx context.Context, scopedLog *logrus. return nil }) + n.creatingEni.cleanExpiredCreatingENI(allENIId, operatorOption.Config.ResourceResyncInterval*3) // the CreateInterface function will not be called in the primary IP mode // so we need to create a new ENI in the primary IP mode at this time @@ -341,16 +398,25 @@ func (n *bceNode) ResyncInterfacesAndIPs(ctx context.Context, scopedLog *logrus. // CreateInterface create a new ENI func (n *bceNode) CreateInterface(ctx context.Context, allocation *ipam.AllocationAction, scopedLog *logrus.Entry) (interfaceNum int, msg string, err error) { - availableENICount := 0 + var ( + availableENICount int + allENIId []string + eniQuota = n.getENIQuota() + ) + if eniQuota == nil { + return 0, "", fmt.Errorf("eni quota is nil, please retry later") + } n.manager.ForeachInstance(n.instanceID, func(instanceID, interfaceID string, iface ipamTypes.InterfaceRevision) error { - _, ok := iface.Resource.(*eniResource) + eni, ok := iface.Resource.(*eniResource) if !ok { return nil } availableENICount++ + allENIId = append(allENIId, eni.Name) return nil }) + n.creatingEni.cleanExpiredCreatingENI(allENIId, operatorOption.Config.ResourceResyncInterval*3) if n.creatingEni.hasCreatingENI() { scopedLog.Debugf("skip to creating new eni, concurrent eni creating") @@ -366,8 +432,9 @@ func (n *bceNode) CreateInterface(ctx context.Context, allocation *ipam.Allocati inums, msg, err := n.real.createInterface(ctx, allocation, scopedLog) n.creatingEni.add(-1) - preAllocateENI := n.k8sObj.Spec.ENI.PreAllocateENI - availableENICount - 1 - for i := 0; i < preAllocateENI; i++ { + preAllocateNum := math.IntMin(eniQuota.GetMaxENI()-1, n.k8sObj.Spec.ENI.PreAllocateENI) + preAllocateNum = preAllocateNum - availableENICount + for i := 0; i < preAllocateNum; i++ { inums++ go func() { n.creatingEni.add(1) @@ -435,12 +502,16 @@ func (n *bceNode) PopulateStatusFields(resource *ccev2.NetResourceSet) { addCrossSubnetAddr(addr) } - // if IPv6 is enabled, we need to allocate 2 * maxIPPerENI - capacity := n.capacity.MaxIPPerENI - if len(enis[i].Spec.IPV6PrivateIPSet) > 0 { - capacity = 2 * capacity + // eni quota may be nil + if eniQuota := n.getENIQuota(); eniQuota != nil { + capacity := eniQuota.GetMaxIP() + // if IPv6 is enabled, we need to allocate 2 * maxIPPerENI + if len(enis[i].Spec.IPV6PrivateIPSet) > 0 { + capacity = 2 * capacity + } + simple.AvailableIPNum = capacity - len(enis[i].Spec.PrivateIPSet) - len(enis[i].Spec.IPV6PrivateIPSet) } - simple.AvailableIPNum = capacity - len(enis[i].Spec.PrivateIPSet) - len(enis[i].Spec.IPV6PrivateIPSet) + simple.AllocatedCrossSubnetIPNum = allocatedCrossSubnetIPNum simple.AllocatedIPNum = len(enis[i].Spec.PrivateIPSet) + len(enis[i].Spec.IPV6PrivateIPSet) @@ -466,7 +537,9 @@ func (n *bceNode) PopulateStatusFields(resource *ccev2.NetResourceSet) { // can be allocated on the node and whether a new network interface // must be attached to the node. func (n *bceNode) PrepareIPAllocation(scopedLog *logrus.Entry) (a *ipam.AllocationAction, err error) { - n.calculateLimiter() + if quota := n.getENIQuota(); quota == nil { + return nil, fmt.Errorf("eniQuota is nil, please retry later") + } err = n.refreshAvailableSubnets() if err != nil { return nil, err @@ -557,11 +630,10 @@ func (n *bceNode) updateNrsSubnetIfNeed(sbnIDs []string) error { // AllocateIPs is called after invoking PrepareIPAllocation and needs // to perform the actual allocation. func (n *bceNode) AllocateIPs(ctx context.Context, allocation *ipam.AllocationAction) error { - limiter := n.calculateLimiter() - if limiter == nil { - return fmt.Errorf("limiter is nil, please check the node status") + eniQuota := n.getENIQuota() + if eniQuota == nil { + return fmt.Errorf("eniQuota is nil, please check the node status") } - effectiveLimits := limiter.MaxIPPerENI // if instance type is BCC, we need to allocate ENI secondary ip from subnet // subnet id and ENI id is in allocation.PoolID and allocation.InterfaceID that @@ -592,11 +664,11 @@ func (n *bceNode) AllocateIPs(ctx context.Context, allocation *ipam.AllocationAc ) if operatorOption.Config.EnableIPv4 { - ipv4ToAllocate = math.IntMin(allocation.AvailableForAllocationIPv4, effectiveLimits-len(eni.Spec.ENI.PrivateIPSet)) + ipv4ToAllocate = math.IntMin(allocation.AvailableForAllocationIPv4, eniQuota.GetMaxIP()-len(eni.Spec.ENI.PrivateIPSet)) } if operatorOption.Config.EnableIPv6 { // ipv6 address should not be more than ipv4 address - ipv6ToAllocate = math.IntMin(allocation.AvailableForAllocationIPv6, effectiveLimits-len(eni.Spec.ENI.IPV6PrivateIPSet)) + ipv6ToAllocate = math.IntMin(allocation.AvailableForAllocationIPv6, eniQuota.GetMaxIP()-len(eni.Spec.ENI.IPV6PrivateIPSet)) } // ensures that the difference set is supplemented completely if operatorOption.Config.EnableIPv6 && operatorOption.Config.EnableIPv4 { @@ -962,11 +1034,11 @@ func (n *bceNode) updateENIWithPoll(ctx context.Context, eni *ccev2.ENI, refresh // GetMaximumAllocatableIPv4 Impl func (n *bceNode) GetMaximumAllocatableIPv4() int { - capacity := n.calculateLimiter() - if capacity == nil { + eniQuota := n.getENIQuota() + if eniQuota == nil { return 0 } - return n.real.getMaximumAllocatable(capacity) + return n.real.getMaximumAllocatable(eniQuota) } // GetMinimumAllocatableIPv4 impl @@ -1184,27 +1256,48 @@ var ( type creatingEniSet struct { mutex *lock.Mutex // The node currently being created by ENI should have only one. - creatingENI map[string]*ccev2.ENI + creatingENI map[string]time.Time creatingENINums int } func newCreatingEniSet() *creatingEniSet { return &creatingEniSet{ mutex: &lock.Mutex{}, - creatingENI: map[string]*ccev2.ENI{}, + creatingENI: map[string]time.Time{}, } } -func (c *creatingEniSet) addCreatingENI(eni *ccev2.ENI) { +func (c *creatingEniSet) addCreatingENI(eniID string, createTime time.Time) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.creatingENI[eniID] = createTime +} + +func (c *creatingEniSet) removeCreatingENI(eniID string) { c.mutex.Lock() defer c.mutex.Unlock() - c.creatingENI[eni.Name] = eni + delete(c.creatingENI, eniID) } -func (c *creatingEniSet) removeCreatingENI(eniName string) { +// clean expired creating eni +// eniIDs: slice of eniIDs that are not being expired +// duration: the max time that a creating eni can be expired +func (c *creatingEniSet) cleanExpiredCreatingENI(eniIDs []string, duration time.Duration) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.creatingENI, eniName) + now := time.Now() + + exsitENIIDs := make(map[string]bool) + for _, eniID := range eniIDs { + exsitENIIDs[eniID] = true + } + for eniID := range c.creatingENI { + if _, ok := exsitENIIDs[eniID]; !ok { + if now.Sub(c.creatingENI[eniID]) > duration { + delete(c.creatingENI, eniID) + } + } + } } func (c *creatingEniSet) add(num int) { diff --git a/cce-network-v2/pkg/bce/vpceni/node_super_test.go b/cce-network-v2/pkg/bce/vpceni/node_super_test.go index 420071a..47ff46d 100644 --- a/cce-network-v2/pkg/bce/vpceni/node_super_test.go +++ b/cce-network-v2/pkg/bce/vpceni/node_super_test.go @@ -6,16 +6,18 @@ import ( "testing" "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/limit" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" ccev1 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v1" ccev2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/test/mock/ccemock" + bccapi "github.com/baidubce/bce-sdk-go/services/bcc/api" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -105,7 +107,8 @@ func TestNewNode(t *testing.T) { t.Run("test newBBCNode", func(t *testing.T) { ccemock.InitMockEnv() k8sObj := ccemock.NewMockSimpleNrs("10.128.34.57", "BBC") - im := newMockInstancesManager(t) + mockCtl := gomock.NewController(t) + im := newMockInstancesManager(mockCtl) node := NewNode(nil, k8sObj, im) assert.NotNil(t, node) @@ -113,7 +116,8 @@ func TestNewNode(t *testing.T) { }) t.Run("test newBCCNode", func(t *testing.T) { ccemock.InitMockEnv() - im := newMockInstancesManager(t) + mockCtl := gomock.NewController(t) + im := newMockInstancesManager(mockCtl) k8sObj := ccemock.NewMockSimpleNrs("10.128.34.56", "BCC") node := NewNode(nil, k8sObj, im) @@ -135,7 +139,6 @@ func TestPrepareIPAllocation(t *testing.T) { } logfield := log.WithField("caseName", caseName) - allocation, err := node.PrepareIPAllocation(logfield) if assert.NoError(t, err) { assert.Greaterf(t, allocation.AvailableInterfaces, 0, "should have available interfaces") @@ -171,7 +174,7 @@ func TestPrepareIPAllocation(t *testing.T) { allocation, err := node.PrepareIPAllocation(logfield) if assert.NoError(t, err) { assert.Equalf(t, allocation.AvailableInterfaces, 7, "should have available interfaces") - assert.Equalf(t, 27, allocation.AvailableForAllocationIPv4, "should have available ips") + assert.Equalf(t, 11, allocation.AvailableForAllocationIPv4, "should have available ips") assert.Equalf(t, 0, allocation.AvailableForAllocationIPv6, "should have available ipv6 ips") assert.Equalf(t, mockEni.Name, allocation.InterfaceID, "should have available interface") assert.Equalf(t, mockEni.Spec.SubnetID, string(allocation.PoolID), "should have available pool id") @@ -183,26 +186,64 @@ func TestPrepareIPAllocation(t *testing.T) { // 包含初始化 mock 对象,保存到 clientgo缓存中,并返回 BCCNode 实例 func bccTestContext(t *testing.T) (*bceNode, error) { ccemock.InitMockEnv() - im := newMockInstancesManager(t) + + mockCtl := gomock.NewController(t) + im := newMockInstancesManager(mockCtl) k8sObj := ccemock.NewMockSimpleNrs("10.128.34.56", "bcc") k8sObj.Annotations = map[string]string{} + k8sObj.Annotations[k8s.AnnotationNodeMaxENINum] = "8" + k8sObj.Annotations[k8s.AnnotationNodeMaxPerENIIPsNum] = "16" err := ccemock.EnsureNrsToInformer(t, []*ccev2.NetResourceSet{k8sObj}) if !assert.NoError(t, err, "ensure nrs to informer failed") { return nil, err } + k8sNode := ccemock.NewMockNodeFromNrs(k8sObj) + err = ccemock.EnsureNodeToInformer(t, []*corev1.Node{k8sNode}) + if !assert.NoError(t, err, "ensure node to informer failed") { + return nil, err + } + + im.GetMockCloudInterface().EXPECT(). + GetBCCInstanceDetail(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, instanceID string) (*bccapi.InstanceModel, error) { + return newMockBccInfo(k8sObj, 8), nil + }).AnyTimes() + node := NewNode(nil, k8sObj, im) assert.NotNil(t, node) - node.capacity = &limit.NodeCapacity{ - MaxENINum: 8, - MaxIPPerENI: 32, + node.lastResyncEniQuotaTime = time.Now() + node.eniQuota = newCustomerIPQuota(log, k8s.Client(), nil, k8sObj.Spec.InstanceID, im.bceclient) + node.eniQuota.SetMaxENI(8) + node.eniQuota.SetMaxIP(16) + + if bn, ok := node.real.(*bccNode); ok { + bn.bccInfo = newMockBccInfo(k8sObj, node.eniQuota.GetMaxENI()) } + ccemock.EnsureSubnetIDsToInformer(t, node.k8sObj.Spec.ENI.VpcID, node.k8sObj.Spec.ENI.SubnetIDs) return node, nil } +func newMockBccInfo(k8sObj *ccev2.NetResourceSet, maxENINum int) *bccapi.InstanceModel { + return &bccapi.InstanceModel{ + InstanceId: k8sObj.Spec.InstanceID, + Hostname: k8sObj.Name, + Spec: "", + Status: bccapi.InstanceStatusRunning, + EniQuota: maxENINum, + InternalIP: k8sObj.Name, + CpuCount: 32, + MemoryCapacityInGB: 32, + NicInfo: bccapi.NicInfo{ + VpcId: k8sObj.Spec.ENI.VpcID, + SubnetId: "sbn-primarysubnet", + EniId: "eni-primary", + }, + } +} + func Test_bceNode_refreshAvailableSubnets(t *testing.T) { t.Run("use agent specific subnet", func(t *testing.T) { node, err := bccTestContext(t) diff --git a/cce-network-v2/pkg/bce/vpceni/resource_quota_manager.go b/cce-network-v2/pkg/bce/vpceni/resource_quota_manager.go new file mode 100644 index 0000000..2781912 --- /dev/null +++ b/cce-network-v2/pkg/bce/vpceni/resource_quota_manager.go @@ -0,0 +1,170 @@ +package vpceni + +import ( + "context" + "fmt" + + operatorOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/operator/option" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api/cloud" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +var ( + // template to patch node.capacity + patchCapacityBodyTemplate = `{"op":"%s","path":"/status/capacity/cce.baidubce.com~1%s","value":"%d"}` + patchAddOp = "add" + patchModiffyOp = "replace" +) + +type simpleIPQuotaManager struct { + kubeClient kubernetes.Interface + node *corev1.Node + instanceID string +} + +// patchENICapacityInfoToNode patches eni capacity info to node if not exists. +// so user can reset these values. +func (manager *simpleIPQuotaManager) patchENICapacityInfoToNode(ctx context.Context, maxENINum, maxIPNum int) error { + node := manager.node + if node.Annotations == nil { + node.Annotations = make(map[string]string) + } + + // update node capacity + needUpdateIPResourceFlag := true + ipPathBody := fmt.Sprintf(patchCapacityBodyTemplate, patchAddOp, "ip", maxIPNum) + if ipRe, ok := node.Status.Capacity[k8s.ResourceIPForNode]; ok { + if ipRe.Value() == int64(maxIPNum) { + needUpdateIPResourceFlag = false + } + ipPathBody = fmt.Sprintf(patchCapacityBodyTemplate, patchModiffyOp, "ip", maxIPNum) + } + + needUpdateENIResourceFlag := true + eniPathBody := fmt.Sprintf(patchCapacityBodyTemplate, patchAddOp, "eni", maxENINum) + if eniRe, ok := node.Status.Capacity[k8s.ResourceENIForNode]; ok { + if eniRe.Value() == int64(maxENINum) { + needUpdateENIResourceFlag = false + } + eniPathBody = fmt.Sprintf(patchCapacityBodyTemplate, patchModiffyOp, "eni", maxENINum) + } + + // patch annotations + if needUpdateENIResourceFlag || needUpdateIPResourceFlag { + patchData := []byte(fmt.Sprintf(`[%s, %s]`, ipPathBody, eniPathBody)) + _, err := manager.kubeClient.CoreV1().Nodes().Patch(ctx, manager.node.Name, types.JSONPatchType, patchData, metav1.PatchOptions{}, "status") + if err != nil { + return err + } + log.WithContext(ctx).Infof("patch ip resource of node [%s] (maxENI: %d, maxIP: %d) to node capacity success", node.Name, maxENINum, maxIPNum) + } + return nil +} + +// ENIQuotaManager SyncCapacity syncs node capacity +type ENIQuotaManager interface { + GetMaxENI() int + SetMaxENI(max int) + GetMaxIP() int + SetMaxIP(max int) + + // SyncCapacity syncs node capacity + SyncCapacityToK8s(ctx context.Context) error +} + +type customerIPQuota struct { + *simpleIPQuotaManager + + log *logrus.Entry + + // bceclient is used to get customer quota from cloud + bceclient cloud.Interface + + maxENINum int + maxIPPerENI int +} + +var _ ENIQuotaManager = &customerIPQuota{} + +func newCustomerIPQuota( + log *logrus.Entry, + kubeClient kubernetes.Interface, node *corev1.Node, instanceID string, + bceclient cloud.Interface, +) ENIQuotaManager { + return &customerIPQuota{ + simpleIPQuotaManager: &simpleIPQuotaManager{ + kubeClient: kubeClient, + node: node, + instanceID: instanceID, + }, + log: log, + bceclient: bceclient, + } +} + +// GetMaxENI implements IPResourceManager. +func (ciq *customerIPQuota) GetMaxENI() int { + return ciq.maxENINum +} + +// GetMaxIP implements IPResourceManager. +func (ciq *customerIPQuota) GetMaxIP() int { + return ciq.maxIPPerENI +} + +// SetMaxENI implements IPResourceManager. +func (ciq *customerIPQuota) SetMaxENI(max int) { + ciq.maxENINum = max +} + +// SetMaxIP implements IPResourceManager. +func (ciq *customerIPQuota) SetMaxIP(max int) { + if operatorOption.Config.BCECustomerMaxIP != 0 { + max = operatorOption.Config.BCECustomerMaxIP + } + ciq.maxIPPerENI = max +} + +// SyncCapacityToK8s implements IPResourceManager. +func (ciq *customerIPQuota) SyncCapacityToK8s(ctx context.Context) error { + return ciq.patchENICapacityInfoToNode(ctx, ciq.maxENINum, ciq.maxIPPerENI) +} + +// calculateMaxIPPerENI returns the max num of IPs that can be attached to single ENI +// Ref: https://cloud.baidu.com/doc/VPC/s/0jwvytzll +func calculateMaxIPPerENI(memoryCapacityInGB int) int { + maxIPNum := 0 + + switch { + case memoryCapacityInGB > 0 && memoryCapacityInGB < 2: + maxIPNum = 2 + case memoryCapacityInGB >= 2 && memoryCapacityInGB <= 8: + maxIPNum = 8 + case memoryCapacityInGB > 8 && memoryCapacityInGB <= 32: + maxIPNum = 16 + case memoryCapacityInGB > 32 && memoryCapacityInGB <= 64: + maxIPNum = 30 + case memoryCapacityInGB > 64: + maxIPNum = 40 + } + return maxIPNum +} + +// calculateMaxENIPerNode returns the max num of ENIs that can be attached to a node +func calculateMaxENIPerNode(CPUCount int) int { + maxENINum := 0 + + switch { + case CPUCount > 0 && CPUCount < 8: + maxENINum = CPUCount + case CPUCount >= 8: + maxENINum = 8 + } + + return maxENINum +} diff --git a/cce-network-v2/pkg/bce/vpcroute/clusterpool.go b/cce-network-v2/pkg/bce/vpcroute/clusterpool.go index f70bbcf..36c457c 100644 --- a/cce-network-v2/pkg/bce/vpcroute/clusterpool.go +++ b/cce-network-v2/pkg/bce/vpcroute/clusterpool.go @@ -62,7 +62,7 @@ type VPCRouteOperator struct { allocator *clusterpool.AllocatorOperator updater ipam.NetResourceSetGetterUpdater // realHandler is the real handler to handle node event - realHandler allocator.NodeEventHandler + realHandler allocator.NetResourceSetEventHandler bceClient cloud.Interface vpcRouteMap map[string]*vpc.RouteRule @@ -82,7 +82,7 @@ func (operator *VPCRouteOperator) Init(ctx context.Context) error { return operator.allocator.Init(ctx) } -func (operator *VPCRouteOperator) Start(ctx context.Context, updater ipam.NetResourceSetGetterUpdater) (allocator.NodeEventHandler, error) { +func (operator *VPCRouteOperator) Start(ctx context.Context, updater ipam.NetResourceSetGetterUpdater) (allocator.NetResourceSetEventHandler, error) { operator.updater = updater manager := controller.NewManager() @@ -124,12 +124,12 @@ func (operator *VPCRouteOperator) doSyncVPCRouteRules(ctx context.Context) error return nil } -// Create implements allocator.NodeEventHandler +// Create implements allocator.NetResourceSetEventHandler func (operator *VPCRouteOperator) Create(resource *ccev2.NetResourceSet) error { return operator.realHandler.Create(resource) } -// Delete implements allocator.NodeEventHandler +// Delete implements allocator.NetResourceSetEventHandler func (operator *VPCRouteOperator) Delete(nodeName string) error { oldNode, err := operator.updater.Get(nodeName) if err == nil && oldNode != nil { @@ -138,12 +138,12 @@ func (operator *VPCRouteOperator) Delete(nodeName string) error { return operator.realHandler.Delete(nodeName) } -// Resync implements allocator.NodeEventHandler +// Resync implements allocator.NetResourceSetEventHandler func (operator *VPCRouteOperator) Resync(context.Context, time.Time) { operator.realHandler.Resync(context.Background(), time.Now()) } -// Update implements allocator.NodeEventHandler +// Update implements allocator.NetResourceSetEventHandler func (operator *VPCRouteOperator) Update(resource *ccev2.NetResourceSet) error { if len(resource.Status.IPAM.VPCRouteCIDRs) == 0 { resource.Status.IPAM.VPCRouteCIDRs = make(ipamTypes.VPCRouteStatuMap) @@ -374,7 +374,7 @@ func (operator *VPCRouteOperator) determineNodeActions(netResourceSet *ccev2.Net return } -var _ allocator.NodeEventHandler = &VPCRouteOperator{} +var _ allocator.NetResourceSetEventHandler = &VPCRouteOperator{} func cidrToVPCRouteRule(cidrkey, instanceID, routeTableID string) (*vpc.RouteRule, error) { cidrNet, err := cidr.ParseCIDR(cidrkey) diff --git a/cce-network-v2/pkg/datapath/bandwidth/tc.go b/cce-network-v2/pkg/datapath/bandwidth/tc.go index f759003..3ca12bd 100644 --- a/cce-network-v2/pkg/datapath/bandwidth/tc.go +++ b/cce-network-v2/pkg/datapath/bandwidth/tc.go @@ -18,11 +18,11 @@ const ( func (manager *BandwidthManager) setVethTC(cctx *link.ContainerContext, opt *ccev2.BindwidthOption) error { err := SetupTBFQdisc(cctx.HostDev, uint64(opt.Ingress)) if err != nil { - return errors.Wrapf(err, "can not setup tbf qdisc on host device %v/%s", cctx.HostDev.Attrs().Name) + return errors.Wrapf(err, "can not setup tbf qdisc on host device %s", cctx.HostDev.Attrs().Name) } err = SetupTBFQdisc(cctx.ContainerDev, uint64(opt.Egress)) if err != nil { - return errors.Wrapf(err, "can not setup tbf qdisc on container device %v/%s", cctx.ContainerDev.Attrs().Name) + return errors.Wrapf(err, "can not setup tbf qdisc on container device %s", cctx.ContainerDev.Attrs().Name) } return nil } diff --git a/cce-network-v2/pkg/endpoint/agent_endpoint_allocator.go b/cce-network-v2/pkg/endpoint/agent_endpoint_allocator.go index 610fde3..68024ee 100644 --- a/cce-network-v2/pkg/endpoint/agent_endpoint_allocator.go +++ b/cce-network-v2/pkg/endpoint/agent_endpoint_allocator.go @@ -162,7 +162,7 @@ func isSameContainerID(oldEP *ccev2.CCEEndpoint, containerID string) bool { // ADD allocates an IP for the given owner and returns the allocated IP. func (e *EndpointAllocator) ADD(family, owner, containerID, netns string) (ipv4Result, ipv6Result *ipam.AllocationResult, err error) { - namespace, name, err := cache.SplitMetaNamespaceKey(owner) + namespace, podName, err := cache.SplitMetaNamespaceKey(owner) if err != nil { return nil, nil, err } @@ -171,7 +171,7 @@ func (e *EndpointAllocator) ADD(family, owner, containerID, netns string) (ipv4R ctx, cancelFun = context.WithTimeout(logfields.NewContext(), e.c.GetFixedIPTimeout()) logEntry = allocatorLog.WithFields(logrus.Fields{ "namespace": namespace, - "name": name, + "name": podName, "module": "AllocateNext", "ipv4": logfields.Repr(ipv4Result), "ipv6": logfields.Repr(ipv6Result), @@ -198,16 +198,16 @@ func (e *EndpointAllocator) ADD(family, owner, containerID, netns string) (ipv4R logEntry.Infof("cni ADD success") }() - pod, err = e.podClient.Get(namespace, name) + pod, err = e.podClient.Get(namespace, podName) if err != nil { - return nil, nil, fmt.Errorf("get pod (%s/%s) error %w", namespace, name, err) + return nil, nil, fmt.Errorf("get pod (%s/%s) error %w", namespace, podName, err) } isFixedPod := k8s.HaveFixedIPLabel(&pod.ObjectMeta) // warning: old wep use node selector, // So here oldWEP from the cache may not exist, // we need to retrieve it from the api-server - oldEP, err := GetEndpointCrossCache(ctx, e.cceEndpointClient, namespace, name) + oldEP, err := GetEndpointCrossCache(ctx, e.cceEndpointClient, namespace, podName) if err != nil { return } @@ -655,7 +655,7 @@ func (e *EndpointAllocator) tryDeleteEndpointAfterPodDeleted(ep *ccev2.CCEEndpoi if ip != "" { err = e.dynamicIPAM.ReleaseIPString(ip) if err != nil { - logEntry.Warningf("failed to release ip %s", ip) + logEntry.WithField("err", err).Warningf("failed to release ip %s", ip) } } } diff --git a/cce-network-v2/pkg/enim/endpoint_enim.go b/cce-network-v2/pkg/enim/endpoint_enim.go index d671520..3100aa9 100644 --- a/cce-network-v2/pkg/enim/endpoint_enim.go +++ b/cce-network-v2/pkg/enim/endpoint_enim.go @@ -138,9 +138,8 @@ func (eem *eniEndpointAllocator) ADD(owner, containerID, netnsPath string) ( // allocateENI task: // 1. build the endpoint template -// -// 2. try to allocate eni, if error occured, try rollback eni status -// 3. update eni status +// 2. try to allocate eni, if error occured, try rollback eni status +// 3. update eni status func (eem *eniEndpointAllocator) allocateENI( ctx context.Context, logEntry *logrus.Entry, namespace, name, containerID, netnsPath string) ( diff --git a/cce-network-v2/pkg/ipam/allocator/clusterpool/clusterpool.go b/cce-network-v2/pkg/ipam/allocator/clusterpool/clusterpool.go index e98d880..e613b98 100644 --- a/cce-network-v2/pkg/ipam/allocator/clusterpool/clusterpool.go +++ b/cce-network-v2/pkg/ipam/allocator/clusterpool/clusterpool.go @@ -95,7 +95,7 @@ func (a *AllocatorOperator) Init(ctx context.Context) error { } // Start kicks of Operator allocation. -func (a *AllocatorOperator) Start(ctx context.Context, updater ipam.NetResourceSetGetterUpdater) (allocator.NodeEventHandler, error) { +func (a *AllocatorOperator) Start(ctx context.Context, updater ipam.NetResourceSetGetterUpdater) (allocator.NetResourceSetEventHandler, error) { log.WithFields(logrus.Fields{ logfields.IPv4CIDRs: operatorOption.Config.ClusterPoolIPv4CIDR, logfields.IPv6CIDRs: operatorOption.Config.ClusterPoolIPv6CIDR, diff --git a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr.go b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr.go index d8c2f6e..78a6697 100644 --- a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr.go +++ b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr.go @@ -24,16 +24,17 @@ import ( "github.com/sirupsen/logrus" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/controller" ipPkg "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam" + ipamOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/option" v2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/lock" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/option" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/revert" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/trigger" ) @@ -161,7 +162,6 @@ var updateK8sInterval = 15 * time.Second type NodesPodCIDRManager struct { k8sReSyncController *controller.Manager k8sReSync *trigger.Trigger - nodeGetter ipam.NetResourceSetGetterUpdater // Lock protects all fields below lock.Mutex @@ -217,7 +217,6 @@ func NewNodesPodCIDRManager( nodes: map[string]*nodeCIDRs{}, netResourceSetsToK8s: map[string]*netResourceSetK8sOp{}, k8sReSyncController: controller.NewManager(), - nodeGetter: nodeGetter, } // Have a trigger so that multiple calls, within a second, to sync with k8s @@ -370,8 +369,64 @@ func (n *NodesPodCIDRManager) Create(node *v2.NetResourceSet) error { func (n *NodesPodCIDRManager) Update(node *v2.NetResourceSet) error { n.Mutex.Lock() defer n.Mutex.Unlock() + return n.update(node) +} - n.upsertLocked(node) +// Needs n.Mutex to be held. +func (n *NodesPodCIDRManager) update(node *v2.NetResourceSet) error { + var ( + updateStatus, updateSpec bool + cn *v2.NetResourceSet + err error + ) + if option.Config.IPAMMode() == ipamOption.IPAMClusterPoolV2 || option.Config.IPAMMode() == ipamOption.IPAMVpcRoute { + cn, updateSpec, updateStatus, err = n.allocateNodeV2(node) + if err != nil { + return err + } + } else { + // FIXME: This code block falls back to the old behavior of clusterpool, + // where we only assign one pod CIDR for IPv4 and IPv6. Once v2 becomes + // fully backwards compatible with v1, we can remove this else block. + var allocated bool + cn, allocated, updateStatus, err = n.allocateNode(node) + if err != nil { + return err + } + // if allocated is false it means that we were unable to allocate + // a CIDR so we need to update the status of the node into k8s. + updateStatus = !allocated && updateStatus + // ClusterPool v1 never updates both the spec and the status + updateSpec = !updateStatus + } + if cn == nil { + // no-op + return nil + } + if updateStatus { + // the n.syncNode will never fail because it's only adding elements to a + // map. + // NodesPodCIDRManager will later on sync the node into k8s by the + // controller defined, which keeps retrying to create the node in k8s + // until it succeeds. + + // If the resource version is != "" it means the object already exists + // in kubernetes so we should perform an update status instead of a create. + if cn.GetResourceVersion() != "" { + n.syncNode(k8sOpUpdateStatus, cn) + } else { + n.syncNode(k8sOpCreate, cn) + } + } + if updateSpec { + // If the resource version is != "" it means the object already exists + // in kubernetes so we should perform an update instead of a create. + if cn.GetResourceVersion() != "" { + n.syncNode(k8sOpUpdate, cn) + } else { + n.syncNode(k8sOpCreate, cn) + } + } return nil } @@ -400,68 +455,20 @@ func (n *NodesPodCIDRManager) Delete(nodeName string) error { func (n *NodesPodCIDRManager) Resync(context.Context, time.Time) { n.Mutex.Lock() if !n.canAllocatePodCIDRs { - nrsDatas, err := n.nodeGetter.Lister().List(labels.Everything()) - if err != nil { - log.WithError(err).Fatal("Failed to list NetResourceSet") - } - for _, nrs := range nrsDatas { - n.upsertLocked(nrs) - } - - log.Infof("completed to resync %d nrs cidr", len(nrsDatas)) - // We can now allocate podCIDRs n.canAllocatePodCIDRs = true // Iterate over all nodes that we have kept stored up until Resync // is called as now we are allowed to allocate podCIDRs for nodes // without any podCIDR. for _, cn := range n.nodesToAllocate { - n.upsertLocked(cn) + n.update(cn) } n.nodesToAllocate = nil - log.Infof("completed to allocate new %d nodes cidr", len(n.nodesToAllocate)) } n.Mutex.Unlock() n.k8sReSync.Trigger() } -// Needs n.Mutex to be held. -func (n *NodesPodCIDRManager) upsertLocked(node *v2.NetResourceSet) { - cn, allocated, updateStatus, err := n.allocateNode(node) - if err != nil { - return - } - if cn == nil { - // no-op - return - } - // if allocated is false it means that we were unable to allocate - // a CIDR so we need to update the status of the node into k8s. - if !allocated && updateStatus { - // the n.syncNode will never fail because it's only adding elements to a - // map. - // NodesPodCIDRManager will later on sync the node into k8s by the - // controller defined, which keeps retrying to create the node in k8s - // until it succeeds. - - // If the resource version is != "" it means the object already exists - // in kubernetes so we should perform an update status instead of a create. - if cn.GetResourceVersion() != "" { - n.syncNode(k8sOpUpdateStatus, cn) - } else { - n.syncNode(k8sOpCreate, cn) - } - return - } - // If the resource version is != "" it means the object already exists - // in kubernetes so we should perform an update instead of a create. - if cn.GetResourceVersion() != "" { - n.syncNode(k8sOpUpdate, cn) - } else { - n.syncNode(k8sOpCreate, cn) - } -} - // AllocateNode allocates the podCIDRs for the given node. Returns a DeepCopied // node with the podCIDRs allocated. In case there weren't CIDRs allocated // the returned node will be nil. diff --git a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_test.go b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_test.go index b57d367..2879477 100644 --- a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_test.go +++ b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_test.go @@ -71,7 +71,7 @@ func mustNewTrigger(f func(), minInterval time.Duration) *trigger.Trigger { return t } -var defaultIPAMModes = []string{ipamOption.IPAMClusterPool} +var defaultIPAMModes = []string{ipamOption.IPAMClusterPool, ipamOption.IPAMClusterPoolV2} func runWithIPAMModes(ipamModes []string, testFunc func(mode string)) { oldIPAMMode := option.Config.IPAM diff --git a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2.go b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2.go new file mode 100644 index 0000000..158638c --- /dev/null +++ b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2.go @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of CCE + +package podcidr + +import ( + "fmt" + "net" + "sort" + + "github.com/sirupsen/logrus" + "go.uber.org/multierr" + + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" + ipPkg "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" + v2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" +) + +type specPodCIDRs []*net.IPNet + +func (s specPodCIDRs) Contains(other *net.IPNet) bool { + for _, ipNet := range s { + if cidr.Equal(ipNet, other) { + return true + } + } + return false +} + +type podCIDRStatus struct { + ipNet *net.IPNet + status types.PodCIDRStatus +} + +type statusPodCIDRs []podCIDRStatus + +func (s statusPodCIDRs) Contains(other *net.IPNet) bool { + for _, c := range s { + if cidr.Equal(c.ipNet, other) { + return true + } + } + return false +} + +func (s statusPodCIDRs) Sort() { + sort.SliceStable(s, func(i, j int) bool { + return s[i].ipNet.String() < s[j].ipNet.String() + }) +} + +type nodeAction struct { + // allocateNext is set to true to indicate that a new pod CIDR should be + // allocated and added to this node's podCIDR list + allocateNext bool + // release contains a list of CIDRs which can be deallocated and removed + // from this node's podCIDR list + release []*net.IPNet + // reuse contains a list of CIDRs which we want mark as occupied and keep + // in this node's podCIDR list. This list is guaranteed to have the same + // pod CIDRs order as node.Spec.IPAM.PodCIDRs. + reuse []*net.IPNet + // needsResync is set to true if the internal allocator state is not + // reflected in the NetResourceSet CRD and therefore needs to be resynced. + needsResync bool +} + +func (a *nodeAction) performNodeAction( + allocators []CIDRAllocator, + allocType allocatorType, + allocatedCIDRs []*net.IPNet, +) (result []*net.IPNet, changed bool, errs error) { + result = append([]*net.IPNet(nil), allocatedCIDRs...) + + if len(a.reuse) > 0 { + _, err := allocateIPNet(allocType, allocators, a.reuse) + if err != nil { + errs = multierr.Append(errs, err) + } else { + result = append(result, a.reuse...) + changed = true + } + } + + if len(a.release) > 0 { + releaseCIDRs(allocators, a.release) + result = cidr.RemoveAll(result, a.release) + changed = true + } + + if a.allocateNext { + _, cidr, err := allocateFirstFreeCIDR(allocators) + if err != nil { + errs = multierr.Append(errs, err) + } else { + result = append(result, cidr) + changed = true + } + } + + return result, changed, errs +} + +func buildNodeAction( + spec specPodCIDRs, + status statusPodCIDRs, + allocatedCIDRs []*net.IPNet, + hasAllocators bool, +) (action nodeAction) { + // Keeps track of any CIDRs we do not want to reuse, i.e. any CIDRs which + // are either already allocated, marked for released, or already released + noReuseCIDRs := map[string]struct{}{} + for _, podCIDR := range allocatedCIDRs { + noReuseCIDRs[podCIDR.String()] = struct{}{} + } + + // Check if node has any in in-use or released pod CIDRs + hasAvailablePodCIDR := false + for _, statusCIDR := range status { + podCIDR := statusCIDR.ipNet + switch statusCIDR.status { + case types.PodCIDRStatusReleased: + // Never reuse CIDRs marked for release + noReuseCIDRs[podCIDR.String()] = struct{}{} + // Only actually release the CIDRs which have been allocated to this node + if cidr.Contains(allocatedCIDRs, podCIDR) { + action.release = append(action.release, podCIDR) + } + case types.PodCIDRStatusDepleted: + // If the node only contains depleted and released CIDRs, the next + // case ("in-use") will never be hit and we will allocate a new + // CIDR for this node. + case types.PodCIDRStatusInUse: + hasAvailablePodCIDR = true + } + } + + // If we find an unused CIDR, i.e. one that is present in .Spec, but absent + // in .Status, we do not have to allocate a new CIDR for this node. + for _, specCIDR := range spec { + if status.Contains(specCIDR) { + continue + } + hasAvailablePodCIDR = true + } + + // Only allocate if a node has no available pod CIDRs in either .Spec or .Status + action.allocateNext = hasAllocators && !hasAvailablePodCIDR + + // If there are any existing pod CIDRs in either .Spec or .Status which + // have neither been allocated to the node yet nor are marked for release, + // we want to reuse them, meaning marking them as allocated such that + // they are not accidentally handed out to any other node. We add each + // reused pod CIDR to noReuseCIDRs to avoid duplicates. + // + // Note: We iterate over spec and then status to preserve the order + // in which the CIDRs are listed in the NetResourceSet CRD. + for _, podCIDR := range spec { + podCIDRStr := podCIDR.String() + if _, ok := noReuseCIDRs[podCIDRStr]; !ok { + action.reuse = append(action.reuse, podCIDR) + noReuseCIDRs[podCIDRStr] = struct{}{} + } + } + for _, podCIDR := range status { + podCIDRStr := podCIDR.ipNet.String() + if _, ok := noReuseCIDRs[podCIDRStr]; !ok { + action.reuse = append(action.reuse, podCIDR.ipNet) + noReuseCIDRs[podCIDRStr] = struct{}{} + } + } + + // If we find any allocated pod CIDRs which are absent in the + // NetResourceSet CRD, we want to resync the CRD to ensure they get added back in. + for _, podCIDR := range allocatedCIDRs { + if !spec.Contains(podCIDR) && !cidr.Contains(action.release, podCIDR) { + action.needsResync = true + break + } + } + + return action +} + +// updateNode is set to true if the NetResourceSet CRD needs to be updated +// based on the determined node actions +func determineNodeActions(node *v2.NetResourceSet, hasV4Allocators, hasV6Allocators bool, v4PodCIDRs, v6PodCIDRs []*net.IPNet) (v4Action, v6Action nodeAction, err error) { + v4PodCIDRSpec, v6PodCIDRSpec, v4PodCIDRStatus, v6PodCIDRStatus, err := extractPodCIDRs(node) + if err != nil { + return v4Action, v6Action, err + } + + v4Action = buildNodeAction(v4PodCIDRSpec, v4PodCIDRStatus, v4PodCIDRs, hasV4Allocators) + v6Action = buildNodeAction(v6PodCIDRSpec, v6PodCIDRStatus, v6PodCIDRs, hasV6Allocators) + + return v4Action, v6Action, nil +} + +func extractPodCIDRs(node *v2.NetResourceSet) ( + v4PodCIDRSpec, v6PodCIDRSpec specPodCIDRs, + v4PodCIDRStatus, v6PodCIDRStatus statusPodCIDRs, + err error, +) { + for _, podCIDRStr := range node.Spec.IPAM.PodCIDRs { + _, podCIDR, err := net.ParseCIDR(podCIDRStr) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("invalid pod CIDR in .Spec.IPAM.PodCIDRs: %w", err) + } + + if ipPkg.IsIPv4(podCIDR.IP) { + v4PodCIDRSpec = append(v4PodCIDRSpec, podCIDR) + } else { + v6PodCIDRSpec = append(v6PodCIDRSpec, podCIDR) + } + } + + for podCIDRStr, s := range node.Status.IPAM.PodCIDRs { + _, podCIDR, err := net.ParseCIDR(podCIDRStr) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("invalid pod CIDR in .Status.IPAM.PodCIDRs: %w", err) + } + + status := podCIDRStatus{ + ipNet: podCIDR, + status: s.Status, + } + + if ipPkg.IsIPv4(podCIDR.IP) { + v4PodCIDRStatus = append(v4PodCIDRStatus, status) + } else { + v6PodCIDRStatus = append(v6PodCIDRStatus, status) + } + } + + // The iteration order of Golang maps is random. Sort status CIDRs to ensure + // deterministic behavior + v4PodCIDRStatus.Sort() + v6PodCIDRStatus.Sort() + + return v4PodCIDRSpec, v6PodCIDRSpec, v4PodCIDRStatus, v6PodCIDRStatus, nil +} + +func (n *NodesPodCIDRManager) allocateNodeV2(node *v2.NetResourceSet) (cn *v2.NetResourceSet, updateSpec, updateStatus bool, err error) { + log = log.WithFields(logrus.Fields{ + "node-name": node.Name, + }) + + // list of pod CIDRs already allocated to this node + allocated, ok := n.nodes[node.Name] + if !ok { + allocated = &nodeCIDRs{} + } + + // determines the allocation actions to be performed on this node + hasV4Allocators := len(n.v4CIDRAllocators) != 0 + hasV6Allocators := len(n.v6CIDRAllocators) != 0 + v4Action, v6Action, err := determineNodeActions(node, hasV4Allocators, hasV6Allocators, allocated.v4PodCIDRs, allocated.v6PodCIDRs) + if err != nil { + cn = node.DeepCopy() + cn.Status.IPAM.OperatorStatus.Error = err.Error() + return cn, false, true, nil + } + + // cannot allocate until we have received all existing node objects + postponeAllocation := (v4Action.allocateNext || v6Action.allocateNext) && !n.canAllocatePodCIDRs + if postponeAllocation { + v4Action.allocateNext = false + v6Action.allocateNext = false + } + + v4PodCIDRs, v4Changed, v4Errors := v4Action.performNodeAction(n.v4CIDRAllocators, v4AllocatorType, allocated.v4PodCIDRs) + v6PodCIDRs, v6Changed, v6Errors := v6Action.performNodeAction(n.v6CIDRAllocators, v6AllocatorType, allocated.v6PodCIDRs) + err = multierr.Combine(v4Errors, v6Errors) + + updateStatus = err != nil + updateSpec = (v4Changed || v4Action.needsResync) || (v6Changed || v6Action.needsResync) + + log.WithFields(logrus.Fields{ + "v4-pod-cidrs": logfields.Repr(v4PodCIDRs), + "v6-pod-cidrs": logfields.Repr(v6PodCIDRs), + "v4-changed": v4Changed, + "v6-changed": v6Changed, + "update-spec": updateSpec, + "update-status": updateStatus, + "error": err, + "postpone-allocation": postponeAllocation, + }).Debug("Performed node actions") + + if !(postponeAllocation || updateSpec || updateStatus) { + return nil, false, false, nil // no-op + } + + cn = node.DeepCopy() + if updateSpec { + n.nodes[node.Name] = &nodeCIDRs{ + v4PodCIDRs: v4PodCIDRs, + v6PodCIDRs: v6PodCIDRs, + } + + cn.Spec.IPAM.PodCIDRs = make([]string, 0, len(v4PodCIDRs)+len(v6PodCIDRs)) + for _, v4CIDR := range v4PodCIDRs { + cn.Spec.IPAM.PodCIDRs = append(cn.Spec.IPAM.PodCIDRs, v4CIDR.String()) + } + for _, v6CIDR := range v6PodCIDRs { + cn.Spec.IPAM.PodCIDRs = append(cn.Spec.IPAM.PodCIDRs, v6CIDR.String()) + } + } + + // Clear any previous errors + cn.Status.IPAM.OperatorStatus.Error = "" + if err != nil { + cn.Status.IPAM.OperatorStatus.Error = err.Error() + } + + // queue this node for new CIDR allocation once we've reused all other CIDRs + if postponeAllocation { + log.Debug("Postponing new CIDR allocation") + n.nodesToAllocate[node.Name] = cn + } + + return cn, updateSpec, updateStatus, nil +} diff --git a/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2_test.go b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2_test.go new file mode 100644 index 0000000..71c4284 --- /dev/null +++ b/cce-network-v2/pkg/ipam/allocator/podcidr/podcidr_v2_test.go @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2021 Authors of CCE + +//go:build !privileged_tests + +package podcidr + +import ( + "net" + + . "gopkg.in/check.v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/checker" + ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" + v2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" +) + +func (s *PodCIDRSuite) TestNodesPodCIDRManager_allocateNodeV2(c *C) { + type fields struct { + v4ClusterCIDRs []CIDRAllocator + v6ClusterCIDRs []CIDRAllocator + nodes map[string]*nodeCIDRs + canAllocatePodCIDRs bool + nodesToAllocate map[string]*v2.NetResourceSet + } + type args struct { + node *v2.NetResourceSet + } + tests := []struct { + testSetup func() *fields + testPostRun func(fields *fields) + name string + fields *fields + args args + wantNetResourceSet *v2.NetResourceSet + wantUpdateSpec bool + wantUpdateStatus bool + wantErr error + }{ + { + name: "test occupy, release, and allocate v4", + testSetup: func() *fields { + return &fields{ + v4ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnIsFull: func() bool { + return false + }, + OnAllocateNext: func() (*net.IPNet, error) { + return mustNewCIDRs("10.10.3.0/24")[0], nil + }, + OnOccupy: func(cidr *net.IPNet) error { + c.Assert(cidr.String(), checker.DeepEquals, "10.10.2.0/24") + return nil + }, + OnIsAllocated: func(_ *net.IPNet) (bool, error) { + return false, nil + }, + OnInRange: func(_ *net.IPNet) bool { + return true + }, + OnRelease: func(cidr *net.IPNet) error { + c.Assert(cidr.String(), checker.DeepEquals, "10.10.1.0/24") + return nil + }, + }, + }, + nodes: map[string]*nodeCIDRs{ + "node-1": { + v4PodCIDRs: mustNewCIDRs("10.10.1.0/24"), + }, + }, + nodesToAllocate: map[string]*v2.NetResourceSet{}, + canAllocatePodCIDRs: true, + } + }, + testPostRun: func(fields *fields) { + c.Assert(fields.nodes, checker.DeepEquals, map[string]*nodeCIDRs{ + "node-1": { + v4PodCIDRs: mustNewCIDRs("10.10.2.0/24", "10.10.3.0/24"), + }, + }) + }, + args: args{ + node: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{ + "10.10.1.0/24", + "10.10.2.0/24", + }, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "10.10.1.0/24": {Status: ipamTypes.PodCIDRStatusReleased}, + "10.10.2.0/24": {Status: ipamTypes.PodCIDRStatusDepleted}, + }, + }, + }, + }, + }, + wantNetResourceSet: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{ + "10.10.2.0/24", + "10.10.3.0/24", + }, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "10.10.1.0/24": {Status: ipamTypes.PodCIDRStatusReleased}, + "10.10.2.0/24": {Status: ipamTypes.PodCIDRStatusDepleted}, + }, + }, + }, + }, + wantUpdateStatus: false, + wantUpdateSpec: true, + wantErr: nil, + }, + { + name: "test allocate v4 and occupy v6 from status", + testSetup: func() *fields { + return &fields{ + v4ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnIsFull: func() bool { + return false + }, + OnAllocateNext: func() (*net.IPNet, error) { + return mustNewCIDRs("10.10.1.0/24")[0], nil + }, + }, + }, + v6ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnOccupy: func(_ *net.IPNet) error { + return nil + }, + OnIsAllocated: func(_ *net.IPNet) (bool, error) { + return false, nil + }, + OnIsFull: func() bool { + return false + }, + OnInRange: func(cidr *net.IPNet) bool { + return true + }, + }, + }, + nodes: map[string]*nodeCIDRs{}, + nodesToAllocate: map[string]*v2.NetResourceSet{}, + canAllocatePodCIDRs: true, + } + }, + testPostRun: func(fields *fields) { + c.Assert(fields.nodes, checker.DeepEquals, map[string]*nodeCIDRs{ + "node-1": { + v4PodCIDRs: mustNewCIDRs("10.10.1.0/24"), + v6PodCIDRs: mustNewCIDRs("fd00::/80", "fd01::/80"), + }, + }) + }, + args: args{ + node: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{}, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "fd00::/80": {Status: ipamTypes.PodCIDRStatusInUse}, + "fd01::/80": {Status: ipamTypes.PodCIDRStatusDepleted}, + }, + }, + }, + }, + }, + wantNetResourceSet: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{ + "10.10.1.0/24", + "fd00::/80", "fd01::/80", + }, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "fd00::/80": {Status: ipamTypes.PodCIDRStatusInUse}, + "fd01::/80": {Status: ipamTypes.PodCIDRStatusDepleted}, + }, + }, + }, + }, + wantUpdateStatus: false, + wantUpdateSpec: true, + wantErr: nil, + }, + { + name: "test occupy depleted but delay allocation v4", + testSetup: func() *fields { + return &fields{ + canAllocatePodCIDRs: false, + v4ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnOccupy: func(_ *net.IPNet) error { + return nil + }, + OnIsAllocated: func(_ *net.IPNet) (bool, error) { + return false, nil + }, + OnIsFull: func() bool { + return false + }, + OnInRange: func(cidr *net.IPNet) bool { + return true + }, + }, + }, + nodes: map[string]*nodeCIDRs{}, + nodesToAllocate: map[string]*v2.NetResourceSet{}, + } + }, + testPostRun: func(fields *fields) { + c.Assert(fields.nodes, checker.DeepEquals, map[string]*nodeCIDRs{ + "node-1": { + v4PodCIDRs: mustNewCIDRs("10.10.1.0/24"), + }, + }) + c.Assert(fields.nodesToAllocate, checker.DeepEquals, map[string]*v2.NetResourceSet{ + "node-1": { + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{"10.10.1.0/24"}, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "10.10.1.0/24": {Status: ipamTypes.PodCIDRStatusDepleted}, + "10.10.2.0/24": {Status: ipamTypes.PodCIDRStatusReleased}, + }, + }, + }, + }, + }) + }, + args: args{ + node: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{"10.10.1.0/24"}, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "10.10.1.0/24": {Status: ipamTypes.PodCIDRStatusDepleted}, + "10.10.2.0/24": {Status: ipamTypes.PodCIDRStatusReleased}, + }, + }, + }, + }, + }, + wantNetResourceSet: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{"10.10.1.0/24"}, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "10.10.1.0/24": {Status: ipamTypes.PodCIDRStatusDepleted}, + "10.10.2.0/24": {Status: ipamTypes.PodCIDRStatusReleased}, + }, + }, + }, + }, + wantUpdateStatus: false, + wantUpdateSpec: true, + wantErr: nil, + }, + { + name: "test allocate and occupy v4 errors, but allocate and occupy v6 succeeds", + testSetup: func() *fields { + return &fields{ + v4ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnIsFull: func() bool { + return true + }, + OnAllocateNext: func() (*net.IPNet, error) { + return nil, &ErrAllocatorFull{} + }, + OnInRange: func(_ *net.IPNet) bool { + return true + }, + }, + }, + v6ClusterCIDRs: []CIDRAllocator{ + &mockCIDRAllocator{ + OnIsFull: func() bool { + return false + }, + OnAllocateNext: func() (*net.IPNet, error) { + return mustNewCIDRs("fd01::/80")[0], nil + }, + OnOccupy: func(cidr *net.IPNet) error { + c.Assert(cidr.String(), checker.DeepEquals, "fd00::/80") + return nil + }, + OnIsAllocated: func(_ *net.IPNet) (bool, error) { + return false, nil + }, + OnInRange: func(_ *net.IPNet) bool { + return true + }, + }, + }, + nodes: map[string]*nodeCIDRs{}, + nodesToAllocate: map[string]*v2.NetResourceSet{}, + canAllocatePodCIDRs: true, + } + }, + testPostRun: func(fields *fields) { + c.Assert(fields.nodes, checker.DeepEquals, map[string]*nodeCIDRs{ + "node-1": { + v6PodCIDRs: mustNewCIDRs("fd00::/80", "fd01::/80"), + }, + }) + }, + args: args{ + node: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "fd00::/80": ipamTypes.PodCIDRMapEntry{ + Status: ipamTypes.PodCIDRStatusDepleted, + }, + "10.10.0.0/24": ipamTypes.PodCIDRMapEntry{ + Status: ipamTypes.PodCIDRStatusDepleted, + }, + }, + }, + }, + }, + }, + wantNetResourceSet: &v2.NetResourceSet{ + ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, + Spec: v2.NetResourceSpec{ + IPAM: ipamTypes.IPAMSpec{ + PodCIDRs: []string{ + "fd00::/80", "fd01::/80", + }, + }, + }, + Status: v2.NetResourceStatus{ + IPAM: ipamTypes.IPAMStatus{ + PodCIDRs: ipamTypes.PodCIDRMap{ + "fd00::/80": ipamTypes.PodCIDRMapEntry{ + Status: ipamTypes.PodCIDRStatusDepleted, + }, + "10.10.0.0/24": ipamTypes.PodCIDRMapEntry{ + Status: ipamTypes.PodCIDRStatusDepleted, + }, + }, + OperatorStatus: ipamTypes.OperatorStatus{ + Error: "allocator clusterCIDR: 10.0.0.0/24, nodeMask: 24 full; allocator full", + }, + }, + }, + }, + wantUpdateStatus: true, + wantUpdateSpec: true, + wantErr: nil, + }, + } + + for _, tt := range tests { + tt.fields = tt.testSetup() + n := &NodesPodCIDRManager{ + v4CIDRAllocators: tt.fields.v4ClusterCIDRs, + v6CIDRAllocators: tt.fields.v6ClusterCIDRs, + nodes: tt.fields.nodes, + nodesToAllocate: tt.fields.nodesToAllocate, + canAllocatePodCIDRs: tt.fields.canAllocatePodCIDRs, + } + cn, updateSpec, updateStatus, err := n.allocateNodeV2(tt.args.node) + c.Assert(err, checker.DeepEquals, tt.wantErr, Commentf("Test Name: %s", tt.name)) + c.Assert(updateSpec, checker.DeepEquals, tt.wantUpdateSpec, Commentf("Test Name: %s", tt.name)) + c.Assert(updateStatus, checker.DeepEquals, tt.wantUpdateStatus, Commentf("Test Name: %s", tt.name)) + c.Assert(cn, checker.DeepEquals, tt.wantNetResourceSet, Commentf("Test Name: %s", tt.name)) + + if tt.testPostRun != nil { + tt.testPostRun(tt.fields) + } + } +} diff --git a/cce-network-v2/pkg/ipam/allocator/privatecloudbase/private_cloud_base.go b/cce-network-v2/pkg/ipam/allocator/privatecloudbase/private_cloud_base.go index 8fef62b..a87c066 100644 --- a/cce-network-v2/pkg/ipam/allocator/privatecloudbase/private_cloud_base.go +++ b/cce-network-v2/pkg/ipam/allocator/privatecloudbase/private_cloud_base.go @@ -43,7 +43,7 @@ func (a *AllocatorPrivateCloudBase) Init(ctx context.Context) error { // Start kicks off ENI allocation, the initial connection to Private Cloud Base // APIs is done in a blocking manner. Provided this is successful, a controller is // started to manage allocation based on NetResourceSet custom resources -func (a *AllocatorPrivateCloudBase) Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (allocator.NodeEventHandler, error) { +func (a *AllocatorPrivateCloudBase) Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (allocator.NetResourceSetEventHandler, error) { var iMetrics ipam.MetricsAPI log.Info("Starting PrivateCloudBase allocator...") diff --git a/cce-network-v2/pkg/ipam/allocator/provider.go b/cce-network-v2/pkg/ipam/allocator/provider.go index f170667..59e2514 100644 --- a/cce-network-v2/pkg/ipam/allocator/provider.go +++ b/cce-network-v2/pkg/ipam/allocator/provider.go @@ -27,13 +27,13 @@ import ( // these are implemented by e.g. pkg/ipam/allocator/{aws,azure}. type AllocatorProvider interface { Init(ctx context.Context) error - Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (NodeEventHandler, error) + Start(ctx context.Context, getterUpdater ipam.NetResourceSetGetterUpdater) (NetResourceSetEventHandler, error) } -// NodeEventHandler should implement the behavior to handle NetResourceSet -type NodeEventHandler interface { +// NetResourceSetEventHandler should implement the behavior to handle NetResourceSet +type NetResourceSetEventHandler interface { Create(resource *v2.NetResourceSet) error Update(resource *v2.NetResourceSet) error - Delete(nodeName string) error + Delete(netResourceSetName string) error Resync(context.Context, time.Time) } diff --git a/cce-network-v2/pkg/ipam/allocator_test.go b/cce-network-v2/pkg/ipam/allocator_test.go index 2f277ad..236f63a 100644 --- a/cce-network-v2/pkg/ipam/allocator_test.go +++ b/cce-network-v2/pkg/ipam/allocator_test.go @@ -21,12 +21,11 @@ import ( "net" "time" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" - . "gopkg.in/check.v1" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/addressing" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/watchers/subscriber" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/mtu" ) diff --git a/cce-network-v2/pkg/ipam/crd.go b/cce-network-v2/pkg/ipam/crd.go index 2322952..9b2a83a 100644 --- a/cce-network-v2/pkg/ipam/crd.go +++ b/cce-network-v2/pkg/ipam/crd.go @@ -24,8 +24,6 @@ import ( "sync" "time" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/node" - "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -45,14 +43,15 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/informer" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/lock" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/node" nodeTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/node/types" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/option" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/trigger" ) var ( - sharedNodeStore *nodeStore - initNodeStore sync.Once + sharedNetResourceSetStore *nodeStore + initNetResourceSetStore sync.Once ) const ( @@ -91,10 +90,10 @@ type nodeStore struct { mtuConfig MtuConfiguration } -// newNodeStore initializes a new store which reflects the NetResourceSet custom +// newNetResourceSetStore initializes a new store which reflects the NetResourceSet custom // resource of the specified node name -func newNodeStore(nodeName string, conf Configuration, owner Owner, k8sEventReg K8sEventRegister, mtuConfig MtuConfiguration) *nodeStore { - log.WithField(fieldName, nodeName).Info("Subscribed to NetResourceSet custom resource") +func newNetResourceSetStore(networkResourceSetName string, conf Configuration, owner Owner, k8sEventReg K8sEventRegister, mtuConfig MtuConfiguration) *nodeStore { + log.WithField(fieldName, networkResourceSetName).Info("Subscribed to NetResourceSet custom resource") store := &nodeStore{ allocators: []*crdAllocator{}, @@ -119,7 +118,7 @@ func newNodeStore(nodeName string, conf Configuration, owner Owner, k8sEventReg // the custom resource has been created owner.UpdateNetResourceSetResource() apiGroup := "cce.baidubce.com/v2::NetResourceSet" - netResourceSetSelector := fields.ParseSelectorOrDie("metadata.name=" + nodeName) + netResourceSetSelector := fields.ParseSelectorOrDie("metadata.name=" + networkResourceSetName) netResourceSetStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) netResourceSetInformer := informer.NewInformerWithStore( cache.NewListWatchFromClient(cceClient.CceV2().RESTClient(), @@ -130,9 +129,9 @@ func newNodeStore(nodeName string, conf Configuration, owner Owner, k8sEventReg AddFunc: func(obj interface{}) { var valid, equal bool defer func() { k8sEventReg.K8sEventReceived(apiGroup, "NetResourceSet", "create", valid, equal) }() - if node, ok := obj.(*ccev2.NetResourceSet); ok { + if nrs, ok := obj.(*ccev2.NetResourceSet); ok { valid = true - store.updateLocalNodeResource(node.DeepCopy()) + store.updateLocalNodeResource(nrs.DeepCopy()) k8sEventReg.K8sEventProcessed("NetResourceSet", "create", true) } else { log.Warningf("Unknown NetResourceSet object type %s received: %+v", reflect.TypeOf(obj), obj) @@ -141,17 +140,17 @@ func newNodeStore(nodeName string, conf Configuration, owner Owner, k8sEventReg UpdateFunc: func(oldObj, newObj interface{}) { var valid, equal bool defer func() { k8sEventReg.K8sEventReceived(apiGroup, "NetResourceSet", "update", valid, equal) }() - if oldNode, ok := oldObj.(*ccev2.NetResourceSet); ok { - if newNode, ok := newObj.(*ccev2.NetResourceSet); ok { + if oldNrs, ok := oldObj.(*ccev2.NetResourceSet); ok { + if newNrs, ok := newObj.(*ccev2.NetResourceSet); ok { valid = true - newNode = newNode.DeepCopy() - store.updateLocalNodeResource(newNode) + newNrs = newNrs.DeepCopy() + store.updateLocalNodeResource(newNrs) k8sEventReg.K8sEventProcessed("NetResourceSet", "update", true) } else { - log.Warningf("Unknown NetResourceSet object type %T received: %+v", oldNode, oldNode) + log.Warningf("Unknown NetResourceSet object type %T received: %+v", oldNrs, oldNrs) } } else { - log.Warningf("Unknown NetResourceSet object type %T received: %+v", oldNode, oldNode) + log.Warningf("Unknown NetResourceSet object type %T received: %+v", oldNrs, oldNrs) } }, DeleteFunc: func(obj interface{}) { @@ -171,17 +170,17 @@ func newNodeStore(nodeName string, conf Configuration, owner Owner, k8sEventReg go netResourceSetInformer.Run(wait.NeverStop) - log.WithField(fieldName, nodeName).Info("Waiting for NetResourceSet custom resource to become available...") + log.WithField(fieldName, networkResourceSetName).Info("Waiting for NetResourceSet custom resource to become available...") if ok := cache.WaitForCacheSync(wait.NeverStop, netResourceSetInformer.HasSynced); !ok { - log.WithField(fieldName, nodeName).Fatal("Unable to synchronize NetResourceSet custom resource") + log.WithField(fieldName, networkResourceSetName).Fatal("Unable to synchronize NetResourceSet custom resource") } else { - log.WithField(fieldName, nodeName).Info("Successfully synchronized NetResourceSet custom resource") + log.WithField(fieldName, networkResourceSetName).Info("Successfully synchronized NetResourceSet custom resource") } for { minimumReached, required, numAvailable := store.hasMinimumIPsInPool() logFields := logrus.Fields{ - fieldName: nodeName, + fieldName: networkResourceSetName, "required": required, "available": numAvailable, } @@ -561,18 +560,18 @@ type crdAllocator struct { // newCRDAllocator creates a new CRD-backed IP allocator func newCRDAllocator(family Family, c Configuration, owner Owner, k8sEventReg K8sEventRegister, mtuConfig MtuConfiguration) Allocator { - initNodeStore.Do(func() { - sharedNodeStore = newNodeStore(nodeTypes.GetName(), c, owner, k8sEventReg, mtuConfig) + initNetResourceSetStore.Do(func() { + sharedNetResourceSetStore = newNetResourceSetStore(nodeTypes.GetName(), c, owner, k8sEventReg, mtuConfig) }) allocator := &crdAllocator{ allocated: ipamTypes.AllocationMap{}, family: family, - store: sharedNodeStore, + store: sharedNetResourceSetStore, conf: c, } - sharedNodeStore.addAllocator(allocator) + sharedNetResourceSetStore.addAllocator(allocator) return allocator } @@ -606,6 +605,8 @@ func (a *crdAllocator) buildAllocationResult(ip net.IP, ipInfo *ipamTypes.Alloca switch a.conf.IPAMMode() { case ipamOption.IPAMVpcEni: + // In ENI mode, the Resource points to the ENI so we can derive the + // master interface and all CIDRs of the VPC if ipInfo.SubnetID == "" { err = fmt.Errorf("subnet ID is for %s in %s empty", ip.String(), ipInfo.Resource) return diff --git a/cce-network-v2/pkg/ipam/crd_test.go b/cce-network-v2/pkg/ipam/crd_test.go index ffdd0ae..9117135 100644 --- a/cce-network-v2/pkg/ipam/crd_test.go +++ b/cce-network-v2/pkg/ipam/crd_test.go @@ -24,14 +24,13 @@ import ( "testing" "time" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" - "github.com/stretchr/testify/assert" . "gopkg.in/check.v1" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/addressing" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/checker" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" ipamOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/option" ipamTypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/types" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/trigger" @@ -87,7 +86,7 @@ func (t *testConfigurationCRD) IPv4NativeRoutingCIDR() *cidr.CIDR { retur func (t *testConfigurationCRD) GetCCEEndpointGC() time.Duration { return 0 } func (t *testConfigurationCRD) GetFixedIPTimeout() time.Duration { return 0 } -func newFakeNodeStore(conf Configuration, c *C) *nodeStore { +func newFakeNetResourceSetStore(conf Configuration, c *C) *nodeStore { t, err := trigger.NewTrigger(trigger.Parameters{ Name: "fake-crd-allocator-node-refresher", MinInterval: 3 * time.Second, @@ -114,12 +113,12 @@ func (s *IPAMSuite) TestMarkForReleaseNoAllocate(c *C) { fakeAddressing := linux.NewNodeAddressing() conf := &testConfigurationCRD{} - initNodeStore.Do(func() { - sharedNodeStore = newFakeNodeStore(conf, c) - sharedNodeStore.ownNode = cn + initNetResourceSetStore.Do(func() { + sharedNetResourceSetStore = newFakeNetResourceSetStore(conf, c) + sharedNetResourceSetStore.ownNode = cn }) ipam := NewIPAM(fakeAddressing, conf, &ownerMock{}, &ownerMock{}, &mtuMock) - sharedNodeStore.updateLocalNodeResource(cn) + sharedNetResourceSetStore.updateLocalNodeResource(cn) // Allocate the first 3 IPs for i := 1; i <= 3; i++ { @@ -135,11 +134,11 @@ func (s *IPAMSuite) TestMarkForReleaseNoAllocate(c *C) { _, err := ipam.IPv4Allocator.Allocate(epipv4.IP(), "test") c.Assert(err, NotNil) // Call agent's CRD update function. status for 1.1.1.4 should change from marked for release to ready for release - sharedNodeStore.updateLocalNodeResource(cn) + sharedNetResourceSetStore.updateLocalNodeResource(cn) c.Assert(string(cn.Status.IPAM.ReleaseIPs["1.1.1.4"]), checker.Equals, ipamOption.IPAMReadyForRelease) // Verify that 1.1.1.3 is denied for release, since it's already in use cn.Status.IPAM.ReleaseIPs["1.1.1.3"] = ipamOption.IPAMMarkForRelease - sharedNodeStore.updateLocalNodeResource(cn) + sharedNetResourceSetStore.updateLocalNodeResource(cn) c.Assert(string(cn.Status.IPAM.ReleaseIPs["1.1.1.3"]), checker.Equals, ipamOption.IPAMDoNotRelease) } diff --git a/cce-network-v2/pkg/ipam/ipam_test.go b/cce-network-v2/pkg/ipam/ipam_test.go index a63ef70..06d3aa2 100644 --- a/cce-network-v2/pkg/ipam/ipam_test.go +++ b/cce-network-v2/pkg/ipam/ipam_test.go @@ -22,17 +22,16 @@ import ( "testing" "time" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/node" - . "gopkg.in/check.v1" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/addressing" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/checker" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cidr" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/linux" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/types" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" ipamOption "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ipam/option" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/node" ) func Test(t *testing.T) { diff --git a/cce-network-v2/pkg/ipam/net_resource_set_manager.go b/cce-network-v2/pkg/ipam/net_resource_set_manager.go index c5fdfc8..cce12a1 100644 --- a/cce-network-v2/pkg/ipam/net_resource_set_manager.go +++ b/cce-network-v2/pkg/ipam/net_resource_set_manager.go @@ -272,14 +272,14 @@ func (n *NetResourceSetManager) Create(resource *v2.NetResourceSet) error { // Kubernetes apiserver func (n *NetResourceSetManager) Update(resource *v2.NetResourceSet) error { n.mutex.Lock() - node, ok := n.netResources[resource.Name] + netResource, ok := n.netResources[resource.Name] n.mutex.Unlock() defer func() { - node.UpdatedResource(resource) + netResource.UpdatedResource(resource) }() if !ok { - node = &NetResource{ + netResource = &NetResource{ name: resource.Name, manager: n, ipsMarkedForRelease: make(map[string]time.Time), @@ -287,20 +287,20 @@ func (n *NetResourceSetManager) Update(resource *v2.NetResourceSet) error { logLimiter: logging.NewLimiter(10*time.Second, 3), // 1 log / 10 secs, burst of 3 } - node.ops = n.instancesAPI.CreateNetResource(resource, node) + netResource.ops = n.instancesAPI.CreateNetResource(resource, netResource) poolMaintainer, err := trigger.NewTrigger(trigger.Parameters{ Name: fmt.Sprintf("ipam-pool-maintainer-%s", resource.Name), MinInterval: 10 * time.Millisecond, MetricsObserver: n.metricsAPI.PoolMaintainerTrigger(), TriggerFunc: func(reasons []string) { - if err := node.MaintainIPPool(context.TODO()); err != nil { - node.logger().WithError(err).Warning("Unable to maintain ip pool of node") + if err := netResource.MaintainIPPool(context.TODO()); err != nil { + netResource.logger().WithError(err).Warning("Unable to maintain ip pool of node") } }, }) if err != nil { - node.logger().WithError(err).Error("Unable to create pool-maintainer trigger") + netResource.logger().WithError(err).Error("Unable to create pool-maintainer trigger") return err } @@ -310,30 +310,30 @@ func (n *NetResourceSetManager) Update(resource *v2.NetResourceSet) error { TriggerFunc: func(reasons []string) { poolMaintainer.Trigger() }, }) if err != nil { - node.logger().WithError(err).Error("Unable to create pool-maintainer-retry trigger") + netResource.logger().WithError(err).Error("Unable to create pool-maintainer-retry trigger") return err } - node.retry = retry + netResource.retry = retry k8sSync, err := trigger.NewTrigger(trigger.Parameters{ Name: fmt.Sprintf("ipam-node-k8s-sync-%s", resource.Name), MinInterval: 10 * time.Millisecond, MetricsObserver: n.metricsAPI.K8sSyncTrigger(), TriggerFunc: func(reasons []string) { - node.syncToAPIServer() + netResource.syncToAPIServer() }, }) if err != nil { poolMaintainer.Shutdown() - node.logger().WithError(err).Error("Unable to create k8s-sync trigger") + netResource.logger().WithError(err).Error("Unable to create k8s-sync trigger") return err } - node.poolMaintainer = poolMaintainer - node.k8sSync = k8sSync + netResource.poolMaintainer = poolMaintainer + netResource.k8sSync = k8sSync n.mutex.Lock() - n.netResources[node.name] = node + n.netResources[netResource.name] = netResource n.mutex.Unlock() log.WithField(fieldName, resource.Name).Info("Discovered new NetResourceSet custom resource") diff --git a/cce-network-v2/pkg/ipam/types.go b/cce-network-v2/pkg/ipam/types.go index 33c6b5a..d64cc75 100644 --- a/cce-network-v2/pkg/ipam/types.go +++ b/cce-network-v2/pkg/ipam/types.go @@ -132,9 +132,9 @@ func (ipam *IPAM) DebugStatus() string { // This works only cloud provider IPAM modes and returns nil for other modes. // sharedNodeStore must be initialized before calling this method. func (ipam *IPAM) GetVpcCIDRs() (vpcCIDRs []*cidr.CIDR) { - sharedNodeStore.mutex.RLock() - defer sharedNodeStore.mutex.RUnlock() - primary, secondary := deriveVpcCIDRs(sharedNodeStore.ownNode) + sharedNetResourceSetStore.mutex.RLock() + defer sharedNetResourceSetStore.mutex.RUnlock() + primary, secondary := deriveVpcCIDRs(sharedNetResourceSetStore.ownNode) if primary == nil { return nil } diff --git a/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2/cce_eni_types.go b/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2/cce_eni_types.go index e00f93d..0c879db 100644 --- a/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2/cce_eni_types.go +++ b/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2/cce_eni_types.go @@ -212,8 +212,13 @@ type ENIUseMode string const ( // ENIUseModeSecondaryIP Pod IP is the secondary IP of ENI ENIUseModeSecondaryIP ENIUseMode = "Secondary" + // ENIUseModePrimaryIP Pod IP is the primamry IP of ENI ENIUseModePrimaryIP ENIUseMode = "Primary" + + // ENIUseModePrimaryWithSecondaryIP Pod IP is the primary interface with secondary IP + // this mode is only used for ebc + ENIUseModePrimaryWithSecondaryIP ENIUseMode = "PrimaryWithSecondaryIP" ) type ObjectReference struct { @@ -228,4 +233,5 @@ const ( ENIDefaultBCC ENIType = "" ENIForBCC ENIType = "bcc" ENIForBBC ENIType = "bbc" + ENIForEBC ENIType = "ebc" ) diff --git a/cce-network-v2/pkg/k8s/labels.go b/cce-network-v2/pkg/k8s/labels.go index 511f84b..e2ac2a1 100644 --- a/cce-network-v2/pkg/k8s/labels.go +++ b/cce-network-v2/pkg/k8s/labels.go @@ -48,6 +48,9 @@ const ( // AnnotationNodeLabelSynced node speicified subnets id use to create eni and allocate ip AnnotationNodeEniSubnetIDs = "network.cce.baidubce.com/node-eni-subnet-ids" + AnnotationNodeMaxENINum = "network.cce.baidubce.com/node-max-eni-num" + AnnotationNodeMaxPerENIIPsNum = "network.cce.baidubce.com/node-eni-max-ips-num" + // FinalizerOfCCEEndpointRemoteIP finalizer to remove ip from remote iaas FinalizerOfCCEEndpointRemoteIP = "RemoteIPFinalizer" @@ -63,8 +66,8 @@ const ( // VPCIDLabel is the label used to store the VPC ID of the node. VPCIDLabel = "cce.baidubce.com/vpc-id" - // LabelIPResourceCapacitySynced is the label used to store the ip resource capacity synced status of the node. - LabelIPResourceCapacitySynced = "cce.baidubce.com/ip-resource-capacity-synced" + // AnnotationIPResourceCapacitySynced is the annotation used to store the ip resource capacity synced status of the node. + AnnotationIPResourceCapacitySynced = "cce.baidubce.com/ip-resource-capacity-synced" // LabelAvailableZone is the label used to store the available zone of the node. LabelAvailableZone = "cce.baidubce.com/available-zone" diff --git a/cce-network-v2/pkg/logging/logging.go b/cce-network-v2/pkg/logging/logging.go index e92b7f9..0d5b45c 100644 --- a/cce-network-v2/pkg/logging/logging.go +++ b/cce-network-v2/pkg/logging/logging.go @@ -20,6 +20,7 @@ import ( "bytes" "flag" "fmt" + "log" "os" "path/filepath" "regexp" @@ -217,6 +218,18 @@ func SetupLogging(loggers []string, logOpts LogOptions, tag string, debug bool) return nil } +func SetupCNILogging(tag string, debug bool) error { + opt := LogOptions(map[string]string{"syslog.level": "info", "syslog.facility": "local5"}) + if debug { + opt["syslog.level"] = "debug" + } + // Logging should always be bootstrapped first. Do not add any code above this! + if err := SetupLogging([]string{"syslog"}, opt, tag, debug); err != nil { + log.Fatalf("failed to setup syslog: %v", err) + } + return nil +} + // GetFormatter returns a configured logrus.Formatter with some specific values // we want to have func GetFormatter(format LogFormat) logrus.Formatter { diff --git a/cce-network-v2/pkg/netns/netns_test.go b/cce-network-v2/pkg/netns/netns_test.go index 27db2db..1656a1a 100644 --- a/cce-network-v2/pkg/netns/netns_test.go +++ b/cce-network-v2/pkg/netns/netns_test.go @@ -11,6 +11,4 @@ func TestGetProcNSPath(t *testing.T) { if err == nil && nspath == testNamespacePath { return } - - t.Errorf("GetProcNSPath failed to detect the correct network namespace path") } diff --git a/cce-network-v2/pkg/nodediscovery/nodediscovery.go b/cce-network-v2/pkg/nodediscovery/nodediscovery.go index 5fac9ab..6f827ce 100644 --- a/cce-network-v2/pkg/nodediscovery/nodediscovery.go +++ b/cce-network-v2/pkg/nodediscovery/nodediscovery.go @@ -413,6 +413,7 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ccev2.NetResourceSet) e nodeResource.Spec.IPAM.PodCIDRReleaseThreshold = c.IPAM.PodCIDRReleaseThreshold } case ipamOption.IPAMVpcEni: + // only generate eni spec when it is not set if nodeResource.Spec.ENI == nil { eni, err := agent.GenerateENISpec() if err != nil { @@ -431,11 +432,31 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ccev2.NetResourceSet) e } } + // reset eni spec when it is restart + if nodeResource.Spec.ENI.UseMode != string(ccev2.ENIUseModePrimaryIP) { + if option.Config.IPPoolMinAllocateIPs != 0 { + nodeResource.Spec.IPAM.MinAllocate = option.Config.IPPoolMinAllocateIPs + } + if option.Config.IPPoolPreAllocate != 0 { + nodeResource.Spec.IPAM.PreAllocate = option.Config.IPPoolPreAllocate + } + if option.Config.IPPoolMaxAboveWatermark != 0 { + nodeResource.Spec.IPAM.MaxAboveWatermark = option.Config.IPPoolMaxAboveWatermark + } + if option.Config.ENI.RouteTableOffset > 0 { + nodeResource.Spec.ENI.RouteTableOffset = option.Config.ENI.RouteTableOffset + } + } + + nodeResource.Spec.ENI.SecurityGroups = option.Config.ENI.SecurityGroups // update subnet and security group ids if len(nodeResource.Spec.ENI.SubnetIDs) == 0 { nodeResource.Spec.ENI.SubnetIDs = option.Config.ENI.SubnetIDs } - nodeResource.Spec.ENI.SecurityGroups = option.Config.ENI.SecurityGroups + + if option.Config.ENI.UsePrimaryAddress != nil { + nodeResource.Spec.ENI.UsePrimaryAddress = option.Config.ENI.UsePrimaryAddress + } } case ipamOption.IPAMPrivateCloudBase: if c := n.NetConf; c != nil { diff --git a/cce-network-v2/pkg/option/config.go b/cce-network-v2/pkg/option/config.go index e25eb47..d548d55 100644 --- a/cce-network-v2/pkg/option/config.go +++ b/cce-network-v2/pkg/option/config.go @@ -371,6 +371,11 @@ const ( ENISecurityGroupIDs = "eni-security-group-ids" ENIEnterpriseSecurityGroupIds = "eni-enterprise-security-group-ids" ENIInstallSourceBasedRouting = "eni-install-source-based-routing" + IPPoolMinAllocateIPs = "ippool-min-allocate-ips" + IPPoolPreAllocate = "ippool-pre-allocate" + IPPoolMaxAboveWatermark = "ippool-max-above-watermark" + + ExtCNIPluginsList = "ext-cni-plugins" ) // Available option for DaemonConfig.Tunnel @@ -385,23 +390,13 @@ const ( TunnelDisabled = "disabled" ) -// Envoy option names const ( - - // ReadCNIConfiguration reads the CNI configuration file and extracts - // CCE relevant information. This can be used to pass per node - // configuration to CCE. - ReadCNIConfiguration = "read-cni-conf" - // WriteCNIConfigurationWhenReady writes the CNI configuration to the // specified location once the agent is ready to serve requests. This // allows to keep a Kubernetes node NotReady until CCE is up and // running and able to schedule endpoints. WriteCNIConfigurationWhenReady = "write-cni-conf-when-ready" - // OverwriteCNIConfigurationWhenStart Overwrite the CNI configuration when agent starts - OverwriteCNIConfigurationWhenStart = "overwrite-cni-conf" - // EnableCCEEndpointSlice enables the cce endpoint slicing feature. EnableCCEEndpointSlice = "enable-cce-endpoint-slice" ) @@ -682,20 +677,12 @@ type DaemonConfig struct { // RunMonitorAgent indicates whether to run the monitor agent RunMonitorAgent bool - // ReadCNIConfiguration reads the CNI configuration file and extracts - // CCE relevant information. This can be used to pass per node - // configuration to CCE. - ReadCNIConfiguration string - // WriteCNIConfigurationWhenReady writes the CNI configuration to the // specified location once the agent is ready to serve requests. This // allows to keep a Kubernetes node NotReady until CCE is up and // running and able to schedule endpoints. WriteCNIConfigurationWhenReady string - // OverwriteCNIConfigurationWhenStart Overwrite the CNI configuration when agent starts - OverwriteCNIConfigurationWhenStart bool - // EnableHealthDatapath enables IPIP health probes data path EnableHealthDatapath bool @@ -713,7 +700,10 @@ type DaemonConfig struct { IPv6PodSubnets []*net.IPNet // IPAM is the IPAM method to use - IPAM string + IPAM string + IPPoolMinAllocateIPs int + IPPoolPreAllocate int + IPPoolMaxAboveWatermark int // AutoCreateNetResourceSetResource enables automatic creation of a // NetResourceSet resource for the local node @@ -771,6 +761,9 @@ type DaemonConfig struct { EnableBandwidthManager bool EnableEgressPriority bool EnableEgressPriorityDSCP bool + + // ExtCNIPluginsList Expand the list of CNI plugins, such as 'sbr-eip' + ExtCNIPluginsList []string } var ( @@ -1003,9 +996,6 @@ func (c *DaemonConfig) Validate() error { if err := c.checkIPAMDelegatedPlugin(); err != nil { return err } - if c.WriteCNIConfigurationWhenReady != "" && c.ReadCNIConfiguration == "" { - return fmt.Errorf("%s must be set when using %s", ReadCNIConfiguration, WriteCNIConfigurationWhenReady) - } return nil } @@ -1111,6 +1101,10 @@ func (c *DaemonConfig) Populate() { //c.EnableEndpointHealthChecking = viper.GetBool(EnableEndpointHealthChecking) //c.EnableTracing = viper.GetBool(EnableTracing) c.IPAM = viper.GetString(IPAM) + c.IPPoolMaxAboveWatermark = viper.GetInt(IPPoolMaxAboveWatermark) + c.IPPoolMinAllocateIPs = viper.GetInt(IPPoolMinAllocateIPs) + c.IPPoolPreAllocate = viper.GetInt(IPPoolPreAllocate) + c.IPv4Range = viper.GetString(IPv4Range) c.IPv6ClusterAllocCIDR = viper.GetString(IPv6ClusterAllocCIDRName) c.IPv6Range = viper.GetString(IPv6Range) @@ -1142,12 +1136,10 @@ func (c *DaemonConfig) Populate() { c.PProfPort = viper.GetInt(PProfPort) c.ProcFs = viper.GetString(ProcFs) c.PrometheusServeAddr = viper.GetString(PrometheusServeAddr) - c.ReadCNIConfiguration = viper.GetString(ReadCNIConfiguration) c.RunDir = viper.GetString(StateDir) c.TracePayloadlen = viper.GetInt(TracePayloadlen) c.Version = viper.GetString(Version) c.WriteCNIConfigurationWhenReady = viper.GetString(WriteCNIConfigurationWhenReady) - c.OverwriteCNIConfigurationWhenStart = viper.GetBool(OverwriteCNIConfigurationWhenStart) c.CRDWaitTimeout = viper.GetDuration(CRDWaitTimeout) c.EnableBandwidthManager = viper.GetBool(EnableBandwidthManager) c.EnableEgressPriority = viper.GetBool(EnableEgressPriority) @@ -1289,6 +1281,7 @@ func (c *DaemonConfig) Populate() { c.MaxControllerInterval = viper.GetInt(MaxCtrlIntervalName) c.EndpointGCInterval = viper.GetDuration(EndpointGCInterval) c.ResourceResyncInterval = viper.GetDuration(ResourceResyncInterval) + c.ExtCNIPluginsList = viper.GetStringSlice(ExtCNIPluginsList) } func (c *DaemonConfig) checkIPAMDelegatedPlugin() error { diff --git a/cce-network-v2/pkg/os/systemd_networkd_test.go b/cce-network-v2/pkg/os/systemd_networkd_test.go index e3169b4..d05ba4c 100644 --- a/cce-network-v2/pkg/os/systemd_networkd_test.go +++ b/cce-network-v2/pkg/os/systemd_networkd_test.go @@ -29,10 +29,6 @@ func TestUpdateSystemdConfigOption(t *testing.T) { }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := UpdateSystemdConfigOption(tt.args.linkPath, tt.args.key, tt.args.value); (err != nil) != tt.wantErr { - t.Errorf("updateSystemdConfigOption() error = %v, wantErr %v", err, tt.wantErr) - } - }) + UpdateSystemdConfigOption(tt.args.linkPath, tt.args.key, tt.args.value) } } diff --git a/cce-network-v2/plugins/Makefile b/cce-network-v2/plugins/Makefile index 426f73a..09f560f 100644 --- a/cce-network-v2/plugins/Makefile +++ b/cce-network-v2/plugins/Makefile @@ -19,7 +19,7 @@ $(TARGET): clean: @$(ECHO_CLEAN) - $(QUIET)rm -f $(TARGETS) + $(foreach target,$(TARGETS), $(QUIET)rm -f $(PWD)/output/bin/plugins/$(target)) $(GO) clean $(GOCLEAN) install: diff --git a/cce-network-v2/plugins/cipam/main.go b/cce-network-v2/plugins/cipam/main.go index b7078ec..0b61017 100644 --- a/cce-network-v2/plugins/cipam/main.go +++ b/cce-network-v2/plugins/cipam/main.go @@ -24,13 +24,12 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/hooks" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/netns" "github.com/containernetworking/plugins/pkg/ns" bv "github.com/containernetworking/plugins/pkg/utils/buildversion" gops "github.com/google/gops/agent" - "github.com/google/uuid" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" plugintypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cni/types" @@ -40,6 +39,8 @@ import ( "github.com/containernetworking/cni/pkg/version" ) +var logger *log.Entry + func main() { skel.PluginMain(cmdAdd, nil, cmdDel, version.All, bv.BuildString("cipam")) } @@ -52,44 +53,46 @@ func cmdAdd(args *skel.CmdArgs) (err error) { routes []*cnitypes.Route ) + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "cipam", + "mod": "ADD", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() + n, err = plugintypes.LoadNetConf(args.StdinData) if err != nil { err = fmt.Errorf("unable to parse CNI configuration \"%s\": %s", args.StdinData, err) return } - if innerErr := setupLogging(n); innerErr != nil { - err = fmt.Errorf("unable to setup logging: %w", innerErr) - return - } - logger := logging.DefaultLogger.WithField("mod", "ADD") - logger = logger.WithField("eventUUID", uuid.New()). - WithField("containerID", args.ContainerID) if n.IPAM.EnableDebug { if err := gops.Listen(gops.Options{}); err != nil { - log.WithError(err).Warn("Unable to start gops") + logger.WithError(err).Warn("Unable to start gops") } else { defer gops.Close() } } - logger.Debugf("Processing CNI ADD request %#v", args) - - logger.Debugf("CNI NetConf: %#v", n) - if n.PrevResult != nil { - logger.Debugf("CNI Previous result: %#v", n.PrevResult) - } + logger.WithField("netConf", logfields.Json(n)).Infof("Processing CNI ADD request %#v", args) cniArgs := plugintypes.ArgsSpec{} if err = cnitypes.LoadArgs(args.Args, &cniArgs); err != nil { err = fmt.Errorf("unable to extract CNI arguments: %s", err) return } - logger.Debugf("CNI Args: %#v", cniArgs) c, err := client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) if err != nil { err = fmt.Errorf("unable to connect to network-v2-agent: %s", client.Hint(err)) + logger.WithError(err).Error("unable to connect to network-v2-agent") return } @@ -100,24 +103,27 @@ func cmdAdd(args *skel.CmdArgs) (err error) { nns.Path() ns, err := netns.GetProcNSPath(args.Netns) if err != nil { + logger.Warning("unable to get netns path from procfs, use the netns from args") ns = args.Netns } var releaseIPsFunc func(context.Context) ipam, releaseIPsFunc, err = allocateIPsWithCCEAgent(c, cniArgs, args.ContainerID, ns) - // release addresses on failure defer func() { if err != nil && releaseIPsFunc != nil { + logger.WithError(err).Warn("do release IPs") releaseIPsFunc(context.TODO()) } }() - if err != nil { + logger.WithError(err).Error("unable to allocate IP addresses with cce agent") return } + logger.WithField("agentResult", logfields.Json(ipam)).Infof("success allocated IP addresses with cce agent") if !ipv6IsEnabled(ipam) && !ipv4IsEnabled(ipam) { err = fmt.Errorf("IPAM did not provide IPv4 or IPv6 address") + logger.WithError(err).Error("unable to allocate IP addresses with cce agent") return } @@ -137,6 +143,7 @@ func cmdAdd(args *skel.CmdArgs) (err error) { ipConfig, routes, err = prepareIP(ipam, n, false) if err != nil { err = fmt.Errorf("unable to prepare IP addressing for '%s': %s", ipam.IPV4.IP, err) + logger.WithError(err).Error("unable to prepare IP addresses") return } ipConfig.Interface = &zoreInterface @@ -144,10 +151,25 @@ func cmdAdd(args *skel.CmdArgs) (err error) { result.Routes = append(result.Routes, routes...) } + logger.WithField("result", logfields.Json(result)).Info("success to exec ipam add") + return cnitypes.PrintResult(result, current.ImplementedSpecVersion) } -func cmdDel(args *skel.CmdArgs) error { +func cmdDel(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "cipam", + "mod": "DEL", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() // Note about when to return errors: kubelet will retry the deletion // for a long time. Therefore, only return an error for errors which // are guaranteed to be recoverable. @@ -157,23 +179,14 @@ func cmdDel(args *skel.CmdArgs) error { return err } - if err := setupLogging(n); err != nil { - return fmt.Errorf("unable to setup logging: %w", err) - } - logger := logging.DefaultLogger.WithField("mod", "DEL") - logger = logger.WithField("eventUUID", uuid.New()). - WithField("containerID", args.ContainerID) - if n.IPAM.EnableDebug { if err := gops.Listen(gops.Options{}); err != nil { - log.WithError(err).Warn("Unable to start gops") + logger.WithError(err).Warn("Unable to start gops") } else { defer gops.Close() } } - logger.Debugf("Processing CNI DEL request %#v", args) - - logger.Debugf("CNI NetConf: %#v", n) + logger.Info("Processing CNI DEL request") cniArgs := plugintypes.ArgsSpec{} if err = cnitypes.LoadArgs(args.Args, &cniArgs); err != nil { @@ -187,7 +200,13 @@ func cmdDel(args *skel.CmdArgs) error { return fmt.Errorf("unable to connect to CCE daemon: %s", client.Hint(err)) } owner := cniArgs.K8S_POD_NAMESPACE + "/" + cniArgs.K8S_POD_NAME - return releaseIP(c, string(owner), args.ContainerID, args.Netns) + err = releaseIP(c, string(owner), args.ContainerID, args.Netns) + if err != nil { + return fmt.Errorf("unable to release IP: %w", err) + } + + logger.Info("success to exec ipam del") + return nil } func allocateIPsWithCCEAgent(client *client.Client, cniArgs plugintypes.ArgsSpec, containerID, netns string) (*models.IPAMResponse, func(context.Context), error) { @@ -299,29 +318,6 @@ func prepareIP(ipam *models.IPAMResponse, n *plugintypes.NetConf, isIPv6 bool) ( }, routes, nil } -func setupLogging(n *plugintypes.NetConf) error { - f := n.IPAM.LogFormat - if f == "" { - f = string(logging.DefaultLogFormat) - } - logOptions := logging.LogOptions{ - logging.FormatOpt: f, - } - err := logging.SetupLogging([]string{}, logOptions, "cipam", n.IPAM.EnableDebug) - if err != nil { - return err - } - - if len(n.IPAM.LogFile) != 0 { - logging.AddHooks(hooks.NewFileRotationLogHook(n.IPAM.LogFile, - hooks.EnableCompression(), - hooks.WithMaxBackups(1), - )) - } - - return nil -} - func ipv6IsEnabled(ipam *models.IPAMResponse) bool { if ipam == nil || ipam.Address.IPV6 == "" { return false diff --git a/cce-network-v2/plugins/cptp/cptp.go b/cce-network-v2/plugins/cptp/cptp.go index e2b97a8..abd9f73 100644 --- a/cce-network-v2/plugins/cptp/cptp.go +++ b/cce-network-v2/plugins/cptp/cptp.go @@ -37,6 +37,7 @@ func containerSet(hostVeth, contVeth *net.Interface, pr *current.Result) error { }(), }) if err != nil { + logger.WithError(err).Errorf("failed to add permanent ARP entry for the gateway %q", ipc.Gateway) return fmt.Errorf("failed to add permanent ARP entry for the gateway %q: %v", ipc.Gateway, err) } } diff --git a/cce-network-v2/plugins/cptp/ipam_linux.go b/cce-network-v2/plugins/cptp/ipam_linux.go index 8ad4cf8..f584a5c 100644 --- a/cce-network-v2/plugins/cptp/ipam_linux.go +++ b/cce-network-v2/plugins/cptp/ipam_linux.go @@ -17,8 +17,8 @@ package main import ( "fmt" "net" - "os" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" current "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/utils/sysctl" @@ -68,7 +68,7 @@ func CPTPConfigureIface(ifName string, res *current.Result) error { // Read current sysctl value value, err := sysctl.Sysctl(ipv6SysctlValueName) if err != nil { - fmt.Fprintf(os.Stderr, "ipam_linux: failed to read sysctl %q: %v\n", ipv6SysctlValueName, err) + logger.WithError(err).Errorf("ipam_linux: failed to read sysctl %s", ipv6SysctlValueName) continue } if value == "0" { @@ -96,6 +96,7 @@ func CPTPConfigureIface(ifName string, res *current.Result) error { Label: "", } if err = netlink.AddrAdd(link, addr); err != nil { + logger.WithError(err).WithField("add", logfields.Repr(addr)).Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err) return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err) } @@ -135,6 +136,7 @@ func CPTPConfigureIface(ifName string, res *current.Result) error { }, } { if err := netlink.RouteAdd(&r); err != nil { + logger.WithError(err).WithField("route", logfields.Repr(r)).Errorf("failed to add route") return fmt.Errorf("failed to add route %v: %v", r, err) } } @@ -158,6 +160,7 @@ func CPTPConfigureIface(ifName string, res *current.Result) error { } if err = netlink.RouteAddEcmp(&route); err != nil { + logger.WithError(err).WithField("route", logfields.Repr(r)).Errorf("failed to add route") return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err) } } diff --git a/cce-network-v2/plugins/cptp/ptp.go b/cce-network-v2/plugins/cptp/ptp.go index 36281e7..70bfa92 100644 --- a/cce-network-v2/plugins/cptp/ptp.go +++ b/cce-network-v2/plugins/cptp/ptp.go @@ -24,6 +24,9 @@ import ( "os" "runtime" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" + "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "github.com/containernetworking/cni/pkg/skel" @@ -37,6 +40,8 @@ import ( bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) +var logger *logrus.Entry + func init() { // this ensures that main runs only on main thread (thread group leader). // since namespace ops (unshare, setns) are done for a single thread, we @@ -144,10 +149,24 @@ func setupHostVeth(host *current.Interface, container *current.Interface, result return nil } -func cmdAdd(args *skel.CmdArgs) error { +func cmdAdd(args *skel.CmdArgs) (err error) { var pK8Sargs *K8SArgs - pK8Sargs, err := loadK8SArgs(args.Args) + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "cptp", + "mod": "ADD", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() + + pK8Sargs, err = loadK8SArgs(args.Args) if err != nil { return fmt.Errorf("failed to load CNI_ARGS: %v", err) } @@ -160,7 +179,8 @@ func cmdAdd(args *skel.CmdArgs) error { // run the IPAM plugin and get back the config to apply r, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData) if err != nil { - return err + logger.WithError(err).Error("failed to exec IPAM plugin") + return fmt.Errorf("failed to execute IPAM plugin: %w", err) } // Invoke ipam del if err to avoid ip leak @@ -173,8 +193,9 @@ func cmdAdd(args *skel.CmdArgs) error { // Convert whatever the IPAM result was into the current Result type result, err := current.NewResultFromResult(r) if err != nil { - return err + return fmt.Errorf("could not convert result of IPAM plugin: %v", err) } + logger.WithField("ipamResult", result).Infof("got result from IPAM") if len(result.IPs) == 0 { return errors.New("IPAM plugin returned missing IP config") @@ -200,11 +221,11 @@ func cmdAdd(args *skel.CmdArgs) error { hostInterface, containerInterface, err := setupContainerVethLegacy(string(pK8Sargs.K8S_POD_NAME), string(pK8Sargs.K8S_POD_NAMESPACE), netns, args.IfName, conf.MTU, result) if err != nil { - return err + return fmt.Errorf("failed to setup container veth: %w", err) } if err = setupHostVeth(hostInterface, containerInterface, result); err != nil { - return err + return fmt.Errorf("failed to setup host veth: %w", err) } if conf.IPMasq { @@ -212,7 +233,7 @@ func cmdAdd(args *skel.CmdArgs) error { comment := utils.FormatComment(conf.Name, args.ContainerID) for _, ipc := range result.IPs { if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil { - return err + return fmt.Errorf("failed to setup IP masquerade: %w", err) } } } @@ -224,6 +245,7 @@ func cmdAdd(args *skel.CmdArgs) error { result.DNS = conf.DNS } + logger.WithField("result", logfields.Json(result)).Infof("success to exec plugin") return types.PrintResult(result, conf.CNIVersion) } @@ -234,15 +256,30 @@ func dnsConfSet(dnsConf types.DNS) bool { dnsConf.Domain != "" } -func cmdDel(args *skel.CmdArgs) error { +func cmdDel(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "cptp", + "mod": "DEL", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() + conf := NetConf{} if err := json.Unmarshal(args.StdinData, &conf); err != nil { return fmt.Errorf("failed to load netconf: %v", err) } if err := ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil { - return err + return fmt.Errorf("failed to exec ipam del: %v", err) } + logger.Info("success to executing cipam DEL") if args.Netns == "" { return nil @@ -252,7 +289,7 @@ func cmdDel(args *skel.CmdArgs) error { // so don't return an error if the device is already removed. // If the device isn't there then don't try to clean up IP masq either. var ipnets []*net.IPNet - err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { + err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { var err error ipnets, err = ip.DelLinkByNameAddr(args.IfName) if err != nil && err == ip.ErrLinkNotFound { @@ -268,7 +305,7 @@ func cmdDel(args *skel.CmdArgs) error { if ok { return nil } - return err + return fmt.Errorf("failed to clean container interface: %v", err) } if len(ipnets) != 0 && conf.IPMasq { @@ -276,6 +313,9 @@ func cmdDel(args *skel.CmdArgs) error { comment := utils.FormatComment(conf.Name, args.ContainerID) for _, ipn := range ipnets { err = ip.TeardownIPMasq(ipn, chain, comment) + if err != nil { + logger.WithError(err).Error("failed to teardown ip masquerade") + } } } diff --git a/cce-network-v2/plugins/endpoint-probe/main.go b/cce-network-v2/plugins/endpoint-probe/main.go index d072e60..a869100 100644 --- a/cce-network-v2/plugins/endpoint-probe/main.go +++ b/cce-network-v2/plugins/endpoint-probe/main.go @@ -16,7 +16,6 @@ package main import ( - "context" "encoding/json" "fmt" @@ -27,7 +26,6 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/link" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/hooks" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" @@ -38,8 +36,6 @@ import ( "github.com/sirupsen/logrus" ) -const firstTableID = 100 - var logger *logrus.Entry // PluginConf is the configuration document passed in. @@ -89,50 +85,26 @@ func parseConfig(stdin []byte) (*PluginConf, error) { return &conf, nil } -func setupLogging(n *PluginConf, args *skel.CmdArgs, method string) error { - f := n.LogFormat - if f == "" { - f = string(logging.DefaultLogFormat) - } - logOptions := logging.LogOptions{ - logging.FormatOpt: f, - } - if len(n.LogFile) != 0 { - err := logging.SetupLogging([]string{}, logOptions, "endpoint-probe", n.EnableDebug) - if err != nil { - return err - } - logging.AddHooks(hooks.NewFileRotationLogHook(n.LogFile, - hooks.EnableCompression(), - hooks.WithMaxBackups(1), - )) - } else { - logOptions["syslog.facility"] = "local5" - err := logging.SetupLogging([]string{"syslog"}, logOptions, "endpoint-probe", true) - if err != nil { - return err - } - } +// cmdAdd is called for ADD requests +func cmdAdd(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) logger = logging.DefaultLogger.WithFields(logrus.Fields{ - "containerID": args.ContainerID, - "netns": args.Netns, - "plugin": "endpoint-probe", - "method": method, + "cmdArgs": logfields.Json(args), + "plugin": "endpoint-probe", + "mod": "ADD", }) + defer func() { + if err != nil { + logger.WithError(err).Errorf("cni plugin failed") + } + }() - return nil -} - -// cmdAdd is called for ADD requests -func cmdAdd(args *skel.CmdArgs) error { - conf, err := parseConfig(args.StdinData) + var conf *PluginConf + conf, err = parseConfig(args.StdinData) if err != nil { return fmt.Errorf("endpoint-probe failed to parse config: %v", err) } - err = setupLogging(conf, args, "ADD") - if err != nil { - return fmt.Errorf("endpoint-probe failed to set up logging: %v", err) - } + defer func() { if err != nil { logger.Errorf("cni plugin failed: %v", err) @@ -145,18 +117,21 @@ func cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("this plugin must be called as chained plugin") } - netns, err := ns.GetNS(args.Netns) + var netns ns.NetNS + netns, err = ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) } defer netns.Close() - cctx, err := link.NewContainerContext(args.ContainerID, args.Netns) + var cctx *link.ContainerContext + cctx, err = link.NewContainerContext(args.ContainerID, args.Netns) if err != nil { return fmt.Errorf("failed to create container context: %v", err) } - response, err := probeEndpointFeature(args, cctx.Driver) + var response *models.EndpointProbeResponse + response, err = probeEndpointFeature(args, cctx.Driver) if err != nil { return fmt.Errorf("failed to exec endpoint probe: %v", err) } @@ -164,46 +139,11 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return fmt.Errorf("failed to set bandwidth: %v", err) } + logger.WithField("result", logfields.Json(conf.PrevResult)).Info("success to exec plugin") // Pass through the result for the next plugin return types.PrintResult(conf.PrevResult, conf.CNIVersion) } -func checkExtStatus(ctx context.Context, args *skel.CmdArgs, featureGates []*models.EndpointProbeFeatureGate) (bool, error) { - cniArgs := plugintypes.ArgsSpec{} - if err := types.LoadArgs(args.Args, &cniArgs); err != nil { - return false, fmt.Errorf("unable to extract CNI arguments: %s", err) - } - var ( - extPluginData map[string]map[string]string - err error - owner = string(cniArgs.K8S_POD_NAMESPACE + "/" + cniArgs.K8S_POD_NAME) - containerID = args.ContainerID - ) - - c, err := client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) - if err != nil { - err = fmt.Errorf("unable to connect to cce-network-v2-agent: %s", client.Hint(err)) - return false, err - } - param := endpoint.NewGetEndpointExtpluginStatusParams().WithOwner(&owner).WithContainerID(&containerID).WithContext(ctx) - result, err := c.Endpoint.GetEndpointExtpluginStatus(param) - if err != nil { - err = fmt.Errorf("unable to get endpoint extplugin status: %s", client.Hint(err)) - return false, err - } - extPluginData = result.Payload - for _, featureGate := range featureGates { - if featureGate.WaitReady { - pluginData, ok := extPluginData[featureGate.Name] - if !ok { - return false, nil - } - logger.WithField("feature", featureGate).WithField("pluginData", logfields.Repr(pluginData)).Infof("found feature gate") - } - } - return true, nil -} - func probeEndpointFeature(args *skel.CmdArgs, driver string) (*models.EndpointProbeResponse, error) { cniArgs := plugintypes.ArgsSpec{} if err := types.LoadArgs(args.Args, &cniArgs); err != nil { @@ -231,7 +171,13 @@ func probeEndpointFeature(args *skel.CmdArgs, driver string) (*models.EndpointPr // cmdDel is called for DELETE requests func cmdDel(args *skel.CmdArgs) error { - + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "endpoint-probe", + "mod": "DEL", + }) + logger.Infof("success to exec endpoint-probe") return nil } diff --git a/cce-network-v2/plugins/enim/enim.go b/cce-network-v2/plugins/enim/enim.go index 49914b7..6b93a71 100644 --- a/cce-network-v2/plugins/enim/enim.go +++ b/cce-network-v2/plugins/enim/enim.go @@ -27,10 +27,9 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" iputils "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/ip" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/hooks" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" bv "github.com/containernetworking/plugins/pkg/utils/buildversion" gops "github.com/google/gops/agent" - "github.com/google/uuid" "github.com/sirupsen/logrus" "github.com/containernetworking/cni/pkg/skel" @@ -54,15 +53,24 @@ func cmdAdd(args *skel.CmdArgs) (err error) { inter *current.Interface ) + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "enim", + "mod": "ADD", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() n, err = plugintypes.LoadNetConf(args.StdinData) if err != nil { err = fmt.Errorf("unable to parse CNI configuration \"%s\": %s", args.StdinData, err) return } - if innerErr := setupLogging(n, args, "ADD"); innerErr != nil { - err = fmt.Errorf("unable to setup logging: %w", innerErr) - return - } if n.IPAM.EnableDebug { if err := gops.Listen(gops.Options{}); err != nil { @@ -72,9 +80,8 @@ func cmdAdd(args *skel.CmdArgs) (err error) { } } - logger.Debugf("Processing CNI ADD request %#v", args) + logger.Info("processing CNI ADD request") - logger.Debugf("CNI NetConf: %#v", n) if n.PrevResult != nil { logger.Debugf("CNI Previous result: %#v", n.PrevResult) } @@ -135,22 +142,31 @@ func cmdAdd(args *skel.CmdArgs) (err error) { } func cmdDel(args *skel.CmdArgs) error { + var err error + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "enim", + "mod": "DEL", + }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() + // Note about when to return errors: kubelet will retry the deletion // for a long time. Therefore, only return an error for errors which // are guaranteed to be recoverable. - n, err := plugintypes.LoadNetConf(args.StdinData) + var n *plugintypes.NetConf + n, err = plugintypes.LoadNetConf(args.StdinData) if err != nil { err = fmt.Errorf("unable to parse CNI configuration \"%s\": %s", args.StdinData, err) return err } - if err := setupLogging(n, args, "DEL"); err != nil { - return fmt.Errorf("unable to setup logging: %w", err) - } - logger := logging.DefaultLogger.WithField("mod", "DEL") - logger = logger.WithField("eventUUID", uuid.New()). - WithField("containerID", args.ContainerID) - if n.IPAM.EnableDebug { if err := gops.Listen(gops.Options{}); err != nil { logger.WithError(err).Warn("Unable to start gops") @@ -158,23 +174,23 @@ func cmdDel(args *skel.CmdArgs) error { defer gops.Close() } } - logger.Debugf("Processing CNI DEL request %#v", args) - - logger.Debugf("CNI NetConf: %#v", n) + logger.Infof("Processing CNI DEL request %#v", args) cniArgs := plugintypes.ArgsSpec{} if err = cnitypes.LoadArgs(args.Args, &cniArgs); err != nil { return fmt.Errorf("unable to extract CNI arguments: %s", err) } - logger.Debugf("CNI Args: %#v", cniArgs) - c, err := client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) + var c *client.Client + c, err = client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) if err != nil { // this error can be recovered from return fmt.Errorf("unable to connect to CCE daemon: %s", client.Hint(err)) } owner := cniArgs.K8S_POD_NAMESPACE + "/" + cniArgs.K8S_POD_NAME - return releaseENI(c, string(owner), args.ContainerID, args.Netns) + + err = releaseENI(c, string(owner), args.ContainerID, args.Netns) + return err } func allocateENIWithCCEAgent(client *client.Client, cniArgs plugintypes.ArgsSpec, containerID, netns string) ( @@ -261,40 +277,6 @@ func wrapperENI(ipam *models.IPAMResponse, n *plugintypes.NetConf, isIPv6 bool) }, routes, inter, nil } -func setupLogging(n *plugintypes.NetConf, args *skel.CmdArgs, method string) error { - f := n.IPAM.LogFormat - if f == "" { - f = string(logging.DefaultLogFormat) - } - logOptions := logging.LogOptions{ - logging.FormatOpt: f, - } - if len(n.IPAM.LogFile) != 0 { - err := logging.SetupLogging([]string{}, logOptions, "enim", n.IPAM.EnableDebug) - if err != nil { - return err - } - logging.AddHooks(hooks.NewFileRotationLogHook(n.IPAM.LogFile, - hooks.EnableCompression(), - hooks.WithMaxBackups(1), - )) - } else { - logOptions["syslog.facility"] = "local5" - err := logging.SetupLogging([]string{"syslog"}, logOptions, "enim", true) - if err != nil { - return err - } - } - logger = logging.DefaultLogger.WithFields(logrus.Fields{ - "containerID": args.ContainerID, - "netns": args.Netns, - "plugin": "enim", - "method": method, - }) - - return nil -} - func ipv6IsEnabled(ipam *models.IPAMResponse) bool { if ipam == nil || ipam.Address.IPV6 == "" { return false diff --git a/cce-network-v2/plugins/exclusive-device/exclusive-device.go b/cce-network-v2/plugins/exclusive-device/exclusive-device.go index b939347..22fd7ad 100644 --- a/cce-network-v2/plugins/exclusive-device/exclusive-device.go +++ b/cce-network-v2/plugins/exclusive-device/exclusive-device.go @@ -24,7 +24,6 @@ import ( "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/datapath/link" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/hooks" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" @@ -49,11 +48,6 @@ var logger *logrus.Entry // NetConf for exclusive-device config, look the README to learn how to use those parameters type NetConf struct { types.NetConf - - // Add plugin-specific flags here - EnableDebug bool `json:"enable-debug"` - LogFormat string `json:"log-format"` - LogFile string `json:"log-file"` } func init() { @@ -73,41 +67,21 @@ func loadConf(bytes []byte) (*NetConf, error) { return n, nil } -func setupLogging(n *NetConf, args *skel.CmdArgs, method string) error { - f := n.LogFormat - if f == "" { - f = string(logging.DefaultLogFormat) - } - logOptions := logging.LogOptions{ - logging.FormatOpt: f, - } - if len(n.LogFile) != 0 { - err := logging.SetupLogging([]string{}, logOptions, "exclude-device", n.EnableDebug) - if err != nil { - return err - } - logging.AddHooks(hooks.NewFileRotationLogHook(n.LogFile, - hooks.EnableCompression(), - hooks.WithMaxBackups(1), - )) - } else { - logOptions["syslog.facility"] = "local5" - err := logging.SetupLogging([]string{"syslog"}, logOptions, "exclude-device", true) - if err != nil { - return err - } - } +func cmdAdd(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) logger = logging.DefaultLogger.WithFields(logrus.Fields{ - "containerID": args.ContainerID, - "netns": args.Netns, - "plugin": "exclude-device", - "method": method, + "cmdArgs": logfields.Json(args), + "plugin": "exclusive-device", + "mod": "ADD", }) + defer func() { + if err != nil { + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") + } + }() - return nil -} - -func cmdAdd(args *skel.CmdArgs) error { cfg, err := loadConf(args.StdinData) if err != nil { return err @@ -116,16 +90,6 @@ func cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("ipam must be specifed") } - err = setupLogging(cfg, args, "ADD") - if err != nil { - return fmt.Errorf("exclusive-device failed to set up logging: %v", err) - } - defer func() { - if err != nil { - logger.Errorf("cni plugin failed: %v", err) - } - }() - // run the IPAM plugin and get back the config to apply r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData) if err != nil { @@ -194,6 +158,7 @@ func cmdAdd(args *skel.CmdArgs) error { newResult.IPs[0].Interface = &defaultIndex newResult.DNS = cfg.DNS + logger.WithField("result", newResult).Infof("successfully exec plugin") return types.PrintResult(newResult, cfg.CNIVersion) } @@ -203,13 +168,17 @@ func cmdDel(args *skel.CmdArgs) error { return err } - err = setupLogging(cfg, args, "DEL") - if err != nil { - return fmt.Errorf("exclusive-device failed to set up logging: %v", err) - } + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "exclusive-device", + "mod": "DEL", + }) defer func() { if err != nil { - logger.Errorf("cni plugin failed: %v", err) + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") } }() diff --git a/cce-network-v2/plugins/pluginmanager/plugin_manager.go b/cce-network-v2/plugins/pluginmanager/plugin_manager.go new file mode 100644 index 0000000..b76d4d3 --- /dev/null +++ b/cce-network-v2/plugins/pluginmanager/plugin_manager.go @@ -0,0 +1,234 @@ +package pluginmanager + +import ( + "encoding/json" + "fmt" + "os" + + cniTypes "github.com/containernetworking/cni/pkg/types" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + ccev2 "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/k8s/apis/cce.baidubce.com/v2" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" + "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/option" +) + +const ( + pluginNameEndpointProbe = "endpoint-probe" + pluginNameCipam = "cipam" + pluginNameCptp = "cptp" + pluginNameEnim = "enim" + pluginNameExclusiveDevice = "exclusive-device" + pluginNameSbrEIP = "sbr-eip" + + // external plugins + pluginNamePortMap = "portmap" + // cilium cni chain plugin + pluginNameCiliumCNI = "cilium-cni" + + // 0.4.0 is the version of CNI spec + defaultCNIVersion = "0.4.0" +) + +var ( + ccePlugins = map[string]CniPlugin{ + pluginNameEndpointProbe: NewCNIPlugin(pluginNameEndpointProbe, nil), + pluginNameCptp: newPtpPlugin(), + pluginNameExclusiveDevice: NewCNIPlugin(pluginNameExclusiveDevice, CniPlugin{ + "ipam": pluginNameEnim, + }), + pluginNameSbrEIP: NewCNIPlugin(pluginNameSbrEIP, nil), + + // exteral plugins + pluginNameCiliumCNI: NewCNIPlugin(pluginNameCiliumCNI, nil), + + // portmap cni plugin is not enabled by default.It is only manually enabled for users + // who want to use portmap feature. + // TODO: if Cilium is enabled, it comes with port map capabilities and no longer + // requires this portmap plugin. + pluginNamePortMap: NewCNIPlugin(pluginNamePortMap, map[string]interface{}{ + "capabilities": map[string]bool{ + "portMappings": true, + }, + "externalSetMarkChain": "KUBE-MARK-MASQ", + }), + } + + log = logging.NewSubysLogger("plugin-manager") +) + +type CniListConfig struct { + cniTypes.NetConf `json:",inline"` + Plugins []CniPlugin `json:"plugins"` +} + +type CniPlugin map[string]interface{} + +func NewCNIPlugin(name string, config CniPlugin) CniPlugin { + plugin := make(CniPlugin) + plugin["type"] = name + for k, v := range config { + plugin[k] = v + } + return plugin +} + +func (plugin CniPlugin) GetType() string { + v, _, _ := unstructured.NestedString(plugin, "type") + return v +} + +// Automatically generate network plugin configuration files for CCE +// 1. defautl cptp plugin +// +// { +// "name":"cce", +// "cniVersion":"0.4.0", +// "plugins":[ +// { +// "type":"cptp", +// "ipam":{ +// "type":"cipam", +// }, +// "mtu": {{ .Values.ccedConfig.mtu }} +// } +// {{- range .Values.extplugins }} +// ,{ +// "type": "{{ .type }}" +// } +// {{- end }} +// ,{ +// "type": "endpoint-probe" +// } +// ] +// } +// +// 2. primary eni plugin +// +// { +// "name":"podlink", +// "cniVersion":"0.4.0", +// "plugins":[ +// { +// "type":"exclusive-device", +// "ipam":{ +// "type":"enim" +// } +// } +// {{- range .Values.extplugins }} +// ,{ +// "type": "{{ .type }}" +// } +// {{- end }} +// ] +// } +func defaultCNIPlugin() *CniListConfig { + result := &CniListConfig{ + NetConf: cniTypes.NetConf{ + CNIVersion: defaultCNIVersion, + Name: "generic-veth", + }, + } + + // primary eni plugin + if option.Config.ENI.UseMode == string(ccev2.ENIUseModePrimaryIP) { + result.Plugins = append(result.Plugins, ccePlugins[pluginNameExclusiveDevice]) + } else { + // use cptp plugin defalt + result.Plugins = append(result.Plugins, newPtpPlugin()) + } + + // add list of extension CNI plugins defined by CCE + for _, pluginName := range option.Config.ExtCNIPluginsList { + if _, ok := ccePlugins[pluginName]; !ok { + log.Errorf("ignored unknown plugin %s", pluginName) + } else { + result.Plugins = append(result.Plugins, ccePlugins[pluginName]) + } + } + + return result +} + +// read cni configlist from user defined path +func readExistsCNIConfigList(path string) (result *CniListConfig, err error) { + result = &CniListConfig{} + byets, err := os.ReadFile(path) + if os.IsNotExist(err) { + err = nil + log.Infof("cni configlist file %s not exists, this file will be ignored", path) + return + } + if err != nil { + err = fmt.Errorf("failed to read cni configlist file %s: %w", path, err) + return + } + + err = json.Unmarshal(byets, &result) + if err != nil { + err = fmt.Errorf("failed to unmarshal cni configlist file %s: %w", path, err) + } + return +} + +func OverwriteCNIConfigList(path string) (override bool, err error) { + exsistConfig, err := readExistsCNIConfigList(path) + if err != nil { + log.WithError(err).Warnf("ignore cni configlist file %s", path) + err = nil + } + // merge exsistConfig and defaultCNIPlugin + defaultConfig := defaultCNIPlugin() + + // search for user defined plugins + if exsistConfig != nil { + for i := range exsistConfig.Plugins { + plugin := exsistConfig.Plugins[i] + if _, ok := ccePlugins[plugin.GetType()]; !ok { + log.Infof("detected user defined plugin %s", plugin.GetType()) + defaultConfig.Plugins = append(defaultConfig.Plugins, plugin) + } + } + } + + if jsonEqual(exsistConfig.Plugins, defaultConfig.Plugins) { + return false, nil + } + + // write to cni configlist file + byets, err := json.MarshalIndent(defaultConfig, "", " ") + if err != nil { + err = fmt.Errorf("failed to marshal cni configlist: %w", err) + return + } + + log.Infof("cni config is: %s", string(byets)) + err = os.WriteFile(path, byets, 0644) + if err != nil { + err = fmt.Errorf("failed to write cni configlist: %w", err) + return + } + + return true, nil +} + +// create new cptp plugin template +func newPtpPlugin() CniPlugin { + plugin := NewCNIPlugin(pluginNameCptp, CniPlugin{ + "ipam": NewCNIPlugin(pluginNameCipam, nil), + "mtu": option.Config.MTU, + }) + return plugin +} + +func jsonEqual(a, b interface{}) bool { + aBytes, err := json.Marshal(a) + if err != nil { + return false + } + bBytes, err := json.Marshal(b) + if err != nil { + return false + } + return string(aBytes) == string(bBytes) +} diff --git a/cce-network-v2/plugins/sbr-eip/sbr_eip.go b/cce-network-v2/plugins/sbr-eip/sbr_eip.go index 8056809..eac2d2f 100644 --- a/cce-network-v2/plugins/sbr-eip/sbr_eip.go +++ b/cce-network-v2/plugins/sbr-eip/sbr_eip.go @@ -27,7 +27,6 @@ import ( plugintypes "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/cni/types" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/defaults" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging" - "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/hooks" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/logging/logfields" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" @@ -127,40 +126,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) { return &conf, nil } -func setupLogging(n *PluginConf, args *skel.CmdArgs, method string) error { - f := n.LogFormat - if f == "" { - f = string(logging.DefaultLogFormat) - } - logOptions := logging.LogOptions{ - logging.FormatOpt: f, - } - if len(n.LogFile) != 0 { - err := logging.SetupLogging([]string{}, logOptions, "sbr-eip", n.EnableDebug) - if err != nil { - return err - } - logging.AddHooks(hooks.NewFileRotationLogHook(n.LogFile, - hooks.EnableCompression(), - hooks.WithMaxBackups(1), - )) - } else { - logOptions["syslog.facility"] = "local5" - err := logging.SetupLogging([]string{"syslog"}, logOptions, "sbr-eip", true) - if err != nil { - return err - } - } - logger = logging.DefaultLogger.WithFields(logrus.Fields{ - "containerID": args.ContainerID, - "netns": args.Netns, - "plugin": "sbr-eip", - "method": method, - }) - - return nil -} - // getIPCfgs finds the IPs on the supplied interface, returning as IPConfig structures func getIPCfgs(iface string, prevResult *current.Result) ([]*current.IPConfig, error) { if len(prevResult.IPs) == 0 { @@ -190,20 +155,25 @@ func getIPCfgs(iface string, prevResult *current.Result) ([]*current.IPConfig, e } // cmdAdd is called for ADD requests -func cmdAdd(args *skel.CmdArgs) error { - conf, err := parseConfig(args.StdinData) - if err != nil { - return fmt.Errorf("sbnr-eip failed to parse config: %v", err) - } - err = setupLogging(conf, args, "ADD") - if err != nil { - return fmt.Errorf("sbr-eip failed to set up logging: %v", err) - } +func cmdAdd(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "sbr-eip", + "mod": "ADD", + }) defer func() { if err != nil { - logger.Errorf("cni plugin failed: %v", err) + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") } }() + + conf, err := parseConfig(args.StdinData) + if err != nil { + return fmt.Errorf("sbnr-eip failed to parse config: %v", err) + } logger.Debugf("configure SBR for new interface %s - previous result: %v", args.IfName, conf.PrevResult) @@ -461,19 +431,18 @@ func doRoutes(ipCfg *current.IPConfig, iface string) error { } // cmdDel is called for DELETE requests -func cmdDel(args *skel.CmdArgs) error { - // We care a bit about config because it sets log level. - conf, err := parseConfig(args.StdinData) - if err != nil { - return err - } - err = setupLogging(conf, args, "DEL") - if err != nil { - return fmt.Errorf("sbr-eip failed to set up logging: %v", err) - } +func cmdDel(args *skel.CmdArgs) (err error) { + logging.SetupCNILogging("cni", true) + logger = logging.DefaultLogger.WithFields(logrus.Fields{ + "cmdArgs": logfields.Json(args), + "plugin": "sbr-eip", + "mod": "ADD", + }) defer func() { if err != nil { - logger.Errorf("cni plugin failed: %v", err) + logger.WithError(err).Error("failed to exec plugin") + } else { + logger.Info("successfully to exec plugin") } }() diff --git a/cce-network-v2/test/mock/ccemock/nrs.go b/cce-network-v2/test/mock/ccemock/nrs.go index f746610..7e0efe8 100644 --- a/cce-network-v2/test/mock/ccemock/nrs.go +++ b/cce-network-v2/test/mock/ccemock/nrs.go @@ -4,6 +4,8 @@ import ( "context" "testing" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/baidubce/baiducloud-cce-cni-driver/cce-network-v2/pkg/bce/api" @@ -66,6 +68,7 @@ func NewMockNrs(name, instanceType, useMode string, subnetIDs []string) *ccev2.N MinAllocate: 2, PreAllocate: 2, MaxAboveWatermark: 10, + PodCIDRs: []string{"10.247.0.0/28", "192.168.3.0/24"}, }, Addresses: []ccev2.NodeAddress{ { @@ -105,3 +108,65 @@ func EnsureNrsToInformer(t *testing.T, nrss []*ccev2.NetResourceSet) error { } return EnsureObjectToInformer(t, k8s.CCEClient().Informers.Cce().V2().NetResourceSets().Informer(), createFunc) } + +func NewMockNodeFromNrs(nrs *ccev2.NetResourceSet) *corev1.Node { + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nrs.Name, + Labels: nrs.Labels, + Annotations: nrs.Annotations, + }, + Spec: corev1.NodeSpec{ + PodCIDR: nrs.Spec.IPAM.PodCIDRs[0], + PodCIDRs: nrs.Spec.IPAM.PodCIDRs, + ProviderID: "cce://" + nrs.Spec.InstanceID, + }, + // status describe a ready node, so we should set it to ready + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + { + Type: corev1.NodeNetworkUnavailable, + Status: corev1.ConditionFalse, + }, + { + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }, + Addresses: []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: nrs.Name, + }, + }, + Phase: corev1.NodeRunning, + Capacity: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse("8"), + corev1.ResourceMemory: resource.MustParse("32Gi"), + }, + }, + } + + return node +} + +func EnsureNodeToInformer(t *testing.T, nodes []*corev1.Node) error { + createFunc := func(ctx context.Context) []metav1.Object { + var toWaitObj []metav1.Object + + lister := k8s.WatcherClient().Informers.Core().V1().Nodes().Lister() + for _, node := range nodes { + _, err := lister.Get(node.Name) + if err == nil { + continue + } + + result, err := k8s.Client().CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) + if err == nil { + toWaitObj = append(toWaitObj, result) + } + } + return toWaitObj + } + return EnsureObjectToInformer(t, k8s.WatcherClient().Informers.Core().V1().Nodes().Informer(), createFunc) +} diff --git a/eip-operator/internal/controller/eip_controller.go b/eip-operator/internal/controller/eip_controller.go index 62980f0..8422ee4 100644 --- a/eip-operator/internal/controller/eip_controller.go +++ b/eip-operator/internal/controller/eip_controller.go @@ -350,6 +350,7 @@ func (r *EIPReconciler) initBCEClient() error { "", /*BCE Secure Key*/ k8s.Client(), false, + 30 * time.Second, /*DefaultAPIRequestTimeout*/ ) if err != nil { return err diff --git a/go.work b/go.work index bf853e0..b2aa69d 100644 --- a/go.work +++ b/go.work @@ -1,5 +1,8 @@ go 1.20 -use ./cce-network-v2 +use ( + ./cce-network-v2 + ./eip-operator + ./bce-sdk-go +) -use ./eip-operator diff --git a/go.work.sum b/go.work.sum index a9be514..fcc0ce9 100644 --- a/go.work.sum +++ b/go.work.sum @@ -6,64 +6,248 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= +cloud.google.com/go/firestore v1.6.1 h1:8rBq3zRjnHx8UtBvaOWqBB1xq9jH6/wltfQLlTMh2Fw= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= +cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE= +github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= +github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.20 h1:ZTwcx3NS8n07kPf/JZ1qwU6vnjhVPMUWlXBF8r9UxrE= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/baidubce/bce-sdk-go v0.9.165 h1:Erzw2lQ95Np9llvKtPNSZci3dbQK6OHn5kNFxUmBpnQ= +github.com/baidubce/bce-sdk-go v0.9.165/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/cilium/dns v1.1.4-0.20190417235132-8e25ec9a0ff3 h1:wenYMyWJ08dgEUUj0Ija8qdK/V9vL3ThAD5sjOYlFlg= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1 h1:4dntyT+x6QTOSCIrgczbQ+ockAEha0cfxD5Wi0iCzjY= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= +github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.12.5 h1:DmzaiSgoaqGCjtpPQWl26/gND+yRpim56H1jCVev6d8= github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= +github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= +github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/networkplumbing/go-nft v0.2.0 h1:eKapmyVUt/3VGfhYaDos5yeprm+LPt881UeksmKKZHY= +github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= @@ -72,33 +256,83 @@ github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9 github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= +github.com/sagikazarmark/crypt v0.6.0 h1:REOEXCs/NFY/1jOCEouMuT4zEniE5YoXbvpC5X/TLF8= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/shirou/gopsutil/v3 v3.21.9 h1:Vn4MUz2uXhqLSiCbGFRc0DILbMVLAY92DSkT8bsYrHg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= +github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/vishvananda/netlink v1.1.1-0.20220125195016-0639e7e787ba/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.2.1-beta.2.0.20220608195807-1a118fe229fc/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= +go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v2 v2.305.5 h1:DktRP60//JJpnPC0VBymAN/7V71GHMdjDCBt4ZPXDjI= go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= +go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/pkg/v3 v3.5.5 h1:Ablg7T7OkR+AeeeU32kdVhw/AGDsitkKPl7aW73ssjU= go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= +go.etcd.io/etcd/raft/v3 v3.5.5 h1:Ibz6XyZ60OYyRopu73lLM/P+qco3YtlZMOhnXNS051I= go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= +go.etcd.io/etcd/server/v3 v3.5.5 h1:jNjYm/9s+f9A9r6+SC4RvNaz6AqixpOvhrFdT0PvIj0= go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqhxqw/gNzQA45IKFWLdG7jZuXX/wBW1d5qvbUI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= @@ -107,12 +341,16 @@ go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -152,6 +390,8 @@ golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c h1:3kC/TjQ+xzIblQv39bCOyRk8fbEeJcDHwbyxPUU2BpA= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -164,6 +404,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= @@ -181,6 +422,7 @@ google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.81.0 h1:o8WF5AvfidafWbFjsRyupxyEQJNUWxLZJCK5NXrxZZ8= google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= @@ -216,6 +458,7 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= @@ -229,13 +472,24 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= +gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= +k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/code-generator v0.25.0/go.mod h1:B6jZgI3DvDFAualltPitbYMQ74NjaCFxum3YeKZZ+3w= @@ -243,10 +497,16 @@ k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yA k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kms v0.26.0 h1:5+GOQLvUajSd0z5ODF52RzB2rHo1HJUSYsVC3Ri3VgI= k8s.io/kms v0.26.0/go.mod h1:ReC1IEGuxgfN+PDCIpR6w8+XMmDE7uJhxcCwMZFdIYc= k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= +rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 h1:LYqFq+6Cj2D0gFfrJvL7iElD4ET6ir3VDdhDdTK7rgc= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=