Skip to content

Commit 3c0cfae

Browse files
committed
roachprod: implement GetVMSpecs for IBM
Implementation for previously unimplemented interface method `GetVMSpecs()` for IBM. This will allow for cluster information to be fetched in roachtest via `FetchVMSpecs`. Informs #146202 Epic: None Release note: None
1 parent a4e8d27 commit 3c0cfae

File tree

4 files changed

+99
-12
lines changed

4 files changed

+99
-12
lines changed

pkg/roachprod/vm/ibm/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ go_library(
1818
"//pkg/roachprod/logger",
1919
"//pkg/roachprod/vm",
2020
"//pkg/roachprod/vm/flagstub",
21+
"//pkg/util/ctxgroup",
2122
"//pkg/util/randutil",
2223
"//pkg/util/retry",
2324
"//pkg/util/syncutil",

pkg/roachprod/vm/ibm/account_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func (p *Provider) configureCloudAccountFull(cc *ibmCloudConfig) error {
121121
// Create a transit gateway for the account in the first region.
122122
tgID, err := p.getOrCreateTransitGateway(supportedRegions[0])
123123
if err != nil {
124-
return errors.Wrap(err, "failed to create transit gateway")
124+
return errors.Wrap(err, "failed to get or create transit gateway")
125125
}
126126

127127
for region := range p.regions {

pkg/roachprod/vm/ibm/helpers.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,16 +1259,23 @@ type parsedCRN struct {
12591259
id string
12601260
}
12611261

1262+
// parseCRN takes an IBM Cloud Resource Name string and parses it into
1263+
// its components for easier use.
1264+
// We don't store the resource ID in vm.VM, we need to parse the CRN if
1265+
// we need the resource ID which is at index 9 after splitting.
1266+
// CRN format:
1267+
//
1268+
// crn:version:cname:ctype:service-name:location:scope:service-instance:resource-type:resource
1269+
//
1270+
// CRN e.g.
1271+
//
1272+
// crn:v1:bluemix:public:is:ca-tor-1:a/ba0325a6257b4dabb3a7aa149a6578ae::instance:02q7_1a2940f7-b058-4742-8ef1-b1ee08273cf0
12621273
func (p *Provider) parseCRN(crn string) (*parsedCRN, error) {
12631274

12641275
if crn == "" {
12651276
return nil, fmt.Errorf("empty CRN")
12661277
}
12671278

1268-
// CRN have the following format:
1269-
// crn:version:cname:ctype:service-name:location:scope:service-instance:resource-type:resource
1270-
// e.g.: crn:v1:bluemix:public:is:ca-tor-1:a/ba0325a6257b4dabb3a7aa149a6578ae::instance:02q7_1a2940f7-b058-4742-8ef1-b1ee08273cf0
1271-
// we're interested in the "resource" part, which is the 10th part
12721279
parts := strings.Split(crn, ":")
12731280

12741281
if len(parts) < 10 {
@@ -1279,6 +1286,9 @@ func (p *Provider) parseCRN(crn string) (*parsedCRN, error) {
12791286
id: parts[9],
12801287
}
12811288

1289+
// a CRN's location field can be a region or a zone, handle both cases
1290+
// region e.g. br-sao
1291+
// zone e.g. br-sao-1
12821292
splitsRegion := strings.Split(parts[5], "-")
12831293
if len(splitsRegion) < 3 {
12841294
c.region = parts[5]

pkg/roachprod/vm/ibm/provider.go

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package ibm
77

88
import (
9+
"context"
10+
"encoding/json"
911
"fmt"
1012
"os"
1113
"os/exec"
@@ -23,6 +25,7 @@ import (
2325
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
2426
"github.com/cockroachdb/cockroach/pkg/roachprod/vm"
2527
"github.com/cockroachdb/cockroach/pkg/roachprod/vm/flagstub"
28+
"github.com/cockroachdb/cockroach/pkg/util/ctxgroup"
2629
"github.com/cockroachdb/cockroach/pkg/util/randutil"
2730
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
2831
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
@@ -216,13 +219,29 @@ func NewProvider(options ...Option) (p *Provider, err error) {
216219

217220
// If an authenticator is not provided, create one from the environment.
218221
if p.authenticator == nil {
222+
// GetAuthenticatorFromEnvironment returns nil if IBM_APIKEY isn't set
223+
// 1) First tries to find a credential file
224+
// a) using IBM_CREDENTIALS_FILE which is not set
225+
// b) <user-home-dir>/ibm-credentials.env which is not currently used
226+
// c) <current-working-directory>/ibm-credentials.env which is not currently used
227+
// 2) Then uses the passed in string argument as a prefix and matches to
228+
// any env var with a matching prefix. If IBM_APIKEY isn't set then
229+
// this function continues.
230+
// 3) Finally, will try to load VCAP_SERVICES which is not currently used
231+
//
232+
// If all these fail to match, GetAuthenticatorFromEnvironment returns nil
233+
// without an error. We need to fail in that case, otherwise we will
234+
// continue on with a nil p.authenticator which will cause failures later
219235
p.authenticator, err = core.GetAuthenticatorFromEnvironment(defaultAPIKeyEnvVarPrefix)
220236
if err != nil {
221237
return nil, errors.Wrapf(
222238
err,
223239
"failed to create default IBM authenticator from environment variables",
224240
)
225241
}
242+
if p.authenticator == nil {
243+
return nil, errors.Newf("failed to create IBM authenticator. Is %s set?", expectedEnvVarIBMAPIKey)
244+
}
226245
}
227246

228247
// Build the list of supported regions if none are provided.
@@ -864,6 +883,70 @@ func (p *Provider) FindActiveAccount(l *logger.Logger) (string, error) {
864883
return username, nil
865884
}
866885

886+
// GetVMSpecs implements the vm.GetVMSpecs interface method which returns a
887+
// map from VM.Name to a map of VM attributes
888+
func (p *Provider) GetVMSpecs(
889+
l *logger.Logger, vms vm.List,
890+
) (map[string]map[string]interface{}, error) {
891+
// TODO replace background context with passed in context after *WithContext
892+
// interface methods are implemented for vm.Provider
893+
return p.GetVMSpecsWithContext(context.Background(), l, vms)
894+
}
895+
896+
func (p *Provider) GetVMSpecsWithContext(
897+
ctx context.Context, l *logger.Logger, vms vm.List,
898+
) (map[string]map[string]interface{}, error) {
899+
900+
// Extract the spec of all VMs and create a map from VM name to spec.
901+
vmSpecs := make(map[string]map[string]interface{})
902+
var mu syncutil.Mutex
903+
g := ctxgroup.WithContext(ctx)
904+
g.SetLimit(5)
905+
906+
for _, vmInstance := range vms {
907+
g.GoCtx(func(ctx context.Context) error {
908+
perCtx, cancel := context.WithTimeout(ctx, time.Minute)
909+
defer cancel()
910+
911+
region, err := p.zoneToRegion(vmInstance.Zone)
912+
if err != nil {
913+
return err
914+
}
915+
vpcService, err := p.getVpcService(region)
916+
if err != nil {
917+
return err
918+
}
919+
parsedCrn, err := p.parseCRN(vmInstance.ProviderID)
920+
if err != nil {
921+
return err
922+
}
923+
l.Printf("Fetching specs for instance %s", vmInstance.Name)
924+
result, _, err := vpcService.GetInstanceWithContext(perCtx, vpcService.NewGetInstanceOptions(parsedCrn.id))
925+
if err != nil {
926+
return err
927+
}
928+
// Marshaling & unmarshalling struct to match interface method return type
929+
jsonBytes, err := json.Marshal(result)
930+
if err != nil {
931+
return err
932+
}
933+
var vmSpec map[string]interface{}
934+
err = json.Unmarshal(jsonBytes, &vmSpec)
935+
if err != nil {
936+
return err
937+
}
938+
mu.Lock()
939+
vmSpecs[vmInstance.Name] = vmSpec
940+
mu.Unlock()
941+
return nil
942+
})
943+
}
944+
if err := g.Wait(); err != nil {
945+
return nil, err
946+
}
947+
return vmSpecs, nil
948+
}
949+
867950
//
868951
// Unimplemented methods, no plans to implement as of now.
869952
//
@@ -885,13 +968,6 @@ func (p *Provider) GetLiveMigrationVMs(
885968
return nil, nil
886969
}
887970

888-
// GetVMSpecs is part of the vm.Provider interface.
889-
func (p *Provider) GetVMSpecs(
890-
_ *logger.Logger, _ vm.List,
891-
) (map[string]map[string]interface{}, error) {
892-
return nil, nil
893-
}
894-
895971
// Grow is part of the vm.Provider interface.
896972
func (p *Provider) Grow(_ *logger.Logger, _ vm.List, _ string, _ []string) (vm.List, error) {
897973
return nil, vm.UnimplementedError

0 commit comments

Comments
 (0)