Skip to content

Commit

Permalink
Tagging support (#1146)
Browse files Browse the repository at this point in the history
* tagging support

* use parsed id

* use attachTag

* using categorys and tags

* update snapshot list

* using list instead

* add login method

* revamped tagging

* adding support to delete tags

* fix lint issues

* Unit tests

* after carls review

* Use a parse method
  • Loading branch information
bathina2 authored Dec 7, 2021
1 parent 60bbe03 commit 6cbf03e
Show file tree
Hide file tree
Showing 4 changed files with 465 additions and 10 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/softlayer/softlayer-go v0.0.0-20190615201252-ba6e7f295217 // indirect
github.com/spf13/cobra v1.1.3
github.com/vmware/govmomi v0.21.1-0.20191008161538-40aebf13ba45
github.com/vmware/govmomi v0.22.2-0.20200329013745-f2eef8fc745f
github.com/zeebo/blake3 v0.1.2 // indirect
go.mongodb.org/mongo-driver v1.5.1 // indirect
go.uber.org/zap v1.17.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1038,8 +1038,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmware/govmomi v0.21.1-0.20191008161538-40aebf13ba45 h1:zpQBW+l4uPQTfTOxedN5GEcSONhabbCf3X+5+P/H4Jk=
github.com/vmware/govmomi v0.21.1-0.20191008161538-40aebf13ba45/go.mod h1:zbnFoBQ9GIjs2RVETy8CNEpb+L+Lwkjs3XZUL0B3/m0=
github.com/vmware/govmomi v0.22.2-0.20200329013745-f2eef8fc745f h1:6LIYlihC1/LDUhZ7zYVp1WOEY5owzsvogiaHBqvBzPU=
github.com/vmware/govmomi v0.22.2-0.20200329013745-f2eef8fc745f/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc=
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
Expand Down
222 changes: 215 additions & 7 deletions pkg/blockstorage/vmware/vmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (
"net/url"
"os"
"strconv"
"strings"
"time"

"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"github.com/vmware/govmomi/cns"
"github.com/vmware/govmomi/vapi/rest"
vapitags "github.com/vmware/govmomi/vapi/tags"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/soap"
Expand Down Expand Up @@ -52,8 +55,11 @@ var (

// FcdProvider provides blockstorage.Provider
type FcdProvider struct {
Gom *vslm.GlobalObjectManager
Cns *cns.Client
Gom *vslm.GlobalObjectManager
Cns *cns.Client
TagsSvc *vapitags.Manager
tagManager tagManager
categoryID string
}

// NewProvider creates new VMWare FCD provider with the config.
Expand Down Expand Up @@ -96,10 +102,21 @@ func NewProvider(config map[string]string) (blockstorage.Provider, error) {
if err != nil {
return nil, errors.Wrap(err, "Failed to create VSLM client")
}
c := rest.NewClient(cli)
err = c.Login(ctx, url.UserPassword(username, password))
if err != nil {
return nil, errors.Wrap(err, "Failed to login to VAPI rest client")
}
tm := vapitags.NewManager(c)
if err != nil {
return nil, errors.Wrap(err, "Failed to create tag manager")
}
gom := vslm.NewGlobalObjectManager(vslmCli)
return &FcdProvider{
Cns: cnsCli,
Gom: gom,
Cns: cnsCli,
Gom: gom,
TagsSvc: tm,
tagManager: tm,
}, nil
}

Expand All @@ -108,6 +125,11 @@ func (p *FcdProvider) Type() blockstorage.Type {
return blockstorage.TypeFCD
}

// Type is part of blockstorage.Provider
func (p *FcdProvider) SetCategoryID(categoryID string) {
p.categoryID = categoryID
}

// VolumeCreate is part of blockstorage.Provider
func (p *FcdProvider) VolumeCreate(ctx context.Context, volume blockstorage.Volume) (*blockstorage.Volume, error) {
return nil, errors.New("Not implemented")
Expand Down Expand Up @@ -237,8 +259,12 @@ func (p *FcdProvider) SnapshotCreate(ctx context.Context, volume blockstorage.Vo
if snap.SizeInBytes == 0 {
snap.SizeInBytes = volume.SizeInBytes
}

snap.Volume = &volume

if err = p.SetTags(ctx, snap, tags); err != nil {
return nil, errors.Wrap(err, "Failed to set tags")
}

return snap, nil
}

Expand Down Expand Up @@ -294,6 +320,10 @@ func (p *FcdProvider) SnapshotDelete(ctx context.Context, snapshot *blockstorage
return false, errors.Wrap(lerr, "Failed to wait on task")
}
log.Debug().Print("SnapshotDelete task complete", field.M{"VolumeID": volID, "SnapshotID": snapshotID})
err = p.deleteSnapshotTags(ctx, snapshot)
if err != nil {
return false, errors.Wrap(err, "Failed to delete snapshot tags")
}
return true, nil
})
}
Expand Down Expand Up @@ -337,7 +367,7 @@ func (p *FcdProvider) SetTags(ctx context.Context, resource interface{}, tags ma
case *blockstorage.Volume:
return p.setTagsVolume(ctx, r, tags)
case *blockstorage.Snapshot:
return nil
return p.setSnapshotTags(ctx, r, tags)
default:
return errors.New("Unsupported type for resource")
}
Expand All @@ -358,14 +388,184 @@ func (p *FcdProvider) setTagsVolume(ctx context.Context, volume *blockstorage.Vo
return nil
}

// GetOrCreateCategory takes a category name and attempts to get or create the category
// it returns the category ID.
func (p *FcdProvider) GetOrCreateCategory(ctx context.Context, categoryName string) (string, error) {
id, err := p.GetCategoryID(ctx, categoryName)
if err != nil {
if strings.Contains(err.Error(), "404 Not Found") {
id, err := p.tagManager.CreateCategory(ctx, &vapitags.Category{
Name: categoryName,
Cardinality: "SINGLE",
})
if err != nil {
return "", errors.Wrap(err, "Failed to create category")
}
return id, nil
}
return "", err
}
return id, nil
}

// GetCategoryID takes a category name and returns the category ID if it finds it.
func (p *FcdProvider) GetCategoryID(ctx context.Context, categoryName string) (string, error) {
cat, err := p.tagManager.GetCategory(ctx, categoryName)
if err != nil {
return "", errors.Wrap(err, "Failed to find category")
}
return cat.ID, nil
}

// snapshotTag is the struct that will be used to create vmware tags
// the tags are of the form volid:snapid:tag:value
// these tags are assigned to a predefined category that is initialized by the FcdProvider
type snapshotTag struct {
volid string
snapid string
key string
value string
}

func (t *snapshotTag) String() string {
volid := strings.ReplaceAll(t.volid, ":", "-")
snapid := strings.ReplaceAll(t.snapid, ":", "-")
key := strings.ReplaceAll(t.key, ":", "-")
value := strings.ReplaceAll(t.value, ":", "-")
return fmt.Sprintf("%s:%s:%s:%s", volid, snapid, key, value)
}

func (t *snapshotTag) Parse(tag string) error {
parts := strings.Split(tag, ":")
if len(parts) != 4 {
return errors.Errorf("Malformed tag (%s)", tag)
}
t.volid, t.snapid, t.key, t.value = parts[0], parts[1], parts[2], parts[3]
return nil
}

// setSnapshotTags sets tags for a snapshot
func (p *FcdProvider) setSnapshotTags(ctx context.Context, snapshot *blockstorage.Snapshot, tags map[string]string) error {
if p.categoryID == "" {
log.Debug().Print("vSphere snapshot tagging is disabled")
return nil
}
if snapshot == nil {
return errors.New("Empty snapshot")
}
volID, snapID, err := SplitSnapshotFullID(snapshot.ID)
if err != nil {
return errors.Wrap(err, "Cannot infer volumeID and snapshotID from full snapshot ID")
}

for k, v := range tags {
tag := &snapshotTag{volID, snapID, k, v}
_, err = p.tagManager.CreateTag(ctx, &vapitags.Tag{
CategoryID: p.categoryID,
Name: tag.String(),
})
if err != nil && !strings.Contains(err.Error(), "ALREADY_EXISTS") {
return errors.Wrapf(err, "Failed to create tag (%s) for categoryID (%s) ", tag, p.categoryID)
}
}
return nil
}

func (p *FcdProvider) deleteSnapshotTags(ctx context.Context, snapshot *blockstorage.Snapshot) error {
if p.categoryID == "" {
log.Debug().Print("vSphere snapshot tagging is disabled (categoryID not set). Cannot list snapshots")
return nil
}
if snapshot == nil {
return errors.New("Empty snapshot")
}
volID, snapID, err := SplitSnapshotFullID(snapshot.ID)
if err != nil {
return errors.Wrap(err, "Cannot infer volumeID and snapshotID from full snapshot ID")
}
categoryTags, err := p.tagManager.GetTagsForCategory(ctx, p.categoryID)
if err != nil {
return errors.Wrap(err, "Failed to list tags")
}
for _, tag := range categoryTags {
parsedTag := &snapshotTag{}
err := parsedTag.Parse(tag.Name)
if err != nil {
return errors.Wrapf(err, "Failed to parse tag (%s)", tag.Name)
}
if parsedTag.snapid == snapID && parsedTag.volid == volID {
err := p.tagManager.DeleteTag(ctx, &tag)
if err != nil {
return errors.Wrapf(err, "Failed to delete tag (%s)", tag.Name)
}
}
}
return nil
}

// VolumesList is part of blockstorage.Provider
func (p *FcdProvider) VolumesList(ctx context.Context, tags map[string]string, zone string) ([]*blockstorage.Volume, error) {
return nil, errors.New("Not implemented")
}

// SnapshotsList is part of blockstorage.Provider
func (p *FcdProvider) SnapshotsList(ctx context.Context, tags map[string]string) ([]*blockstorage.Snapshot, error) {
return nil, errors.New("Not implemented")
if p.categoryID == "" {
log.Debug().Print("vSphere snapshot tagging is disabled (categoryID not set). Cannot list snapshots")
return nil, nil
}

categoryTags, err := p.tagManager.GetTagsForCategory(ctx, p.categoryID)
if err != nil {
return nil, errors.Wrap(err, "Failed to list tags")
}

snapshotIDs, err := p.getSnapshotIDsFromTags(categoryTags, tags)
if err != nil {
return nil, errors.Wrap(err, "Failed to get snapshotIDs from tags")
}

var snapshots []*blockstorage.Snapshot
if len(snapshotIDs) > 0 {
for _, snapshotID := range snapshotIDs {
snapshot, err := p.SnapshotGet(ctx, snapshotID)
if err != nil {
return nil, err
}
snapshots = append(snapshots, snapshot)
}
}
return snapshots, nil
}

func (p *FcdProvider) getSnapshotIDsFromTags(categoryTags []vapitags.Tag, tags map[string]string) ([]string, error) {
snapshotTagMap := map[string]map[string]string{}
for _, catTag := range categoryTags {
parsedTag := &snapshotTag{}
err := parsedTag.Parse(catTag.Name)
if err != nil {
return nil, errors.Wrapf(err, "Failed to parse tag")
}
if _, ok := snapshotTagMap[parsedTag.snapid]; !ok {
snapshotTagMap[parsedTag.snapid] = map[string]string{}
}
snapshotTagMap[parsedTag.snapid][parsedTag.key] = parsedTag.value
}

snapshotIDs := []string{}
for snapshotID, snapshotTags := range snapshotTagMap {
tagsMatch := true
for k, v := range tags {
if val, ok := snapshotTags[k]; !ok || val != v {
tagsMatch = false
break
}
}
if tagsMatch {
snapshotIDs = append(snapshotIDs, snapshotID)
}
}
return snapshotIDs, nil
}

func getEnvAsIntOrDefault(envKey string, def int) int {
Expand All @@ -379,3 +579,11 @@ func getEnvAsIntOrDefault(envKey string, def int) int {

return def
}

type tagManager interface {
GetCategory(ctx context.Context, id string) (*vapitags.Category, error)
CreateCategory(ctx context.Context, category *vapitags.Category) (string, error)
CreateTag(ctx context.Context, tag *vapitags.Tag) (string, error)
GetTagsForCategory(ctx context.Context, id string) ([]vapitags.Tag, error)
DeleteTag(ctx context.Context, tag *vapitags.Tag) error
}
Loading

0 comments on commit 6cbf03e

Please sign in to comment.