Navigation Menu

Skip to content

Commit

Permalink
Added regex matching to storage pool selection as well as a new exclu…
Browse files Browse the repository at this point in the history
…deStoragePools option
  • Loading branch information
ntap-rippy committed Jun 28, 2018
1 parent f9fe626 commit b161ca4
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 37 deletions.
7 changes: 6 additions & 1 deletion docs/kubernetes/concepts/objects.rst
Expand Up @@ -179,6 +179,7 @@ Attribute Type Required Description
attributes map[string]string no See the attributes section below
storagePools map[string]StringList no Map of backend names to lists of storage pools within
additionalStoragePools map[string]StringList no Map of backend names to lists of storage pools within
excludeStoragePools map[string]StringList no Map of backend names to lists of storage pools within
======================= ===================== ======== =====================================================

Storage attributes and their possible values can be classified into two groups:
Expand Down Expand Up @@ -226,11 +227,15 @@ The ``additionalStoragePools`` parameter is used to extend the set of pools
that Trident will use for provisioning, regardless of any pools selected by
the ``attributes`` and ``storagePools`` parameters.

The ``excludeStoragePools`` parameter is used to filter the set of pools
that Trident will use for provisioning and will remove any pools that match.

In the ``storagePools`` and ``additionalStoragePools`` parameters, each entry
takes the form ``<backend>:<storagePoolList>``, where ``<storagePoolList>`` is
a comma-separated list of storage pools for the specified backend. For example,
a value for ``additionalStoragePools`` might look like
``ontapnas_192.168.1.100:aggr1,aggr2;solidfire_192.168.1.101:bronze``. You can
``ontapnas_192.168.1.100:aggr1,aggr2;solidfire_192.168.1.101:bronze``. These lists
will accept regex values for both the backend and list values. You can
use ``tridentctl get backend`` to get the list of backends and their pools.

2. Kubernetes attributes: These attributes have no impact on the selection of
Expand Down
23 changes: 12 additions & 11 deletions docs/kubernetes/concepts/provisioning.rst
Expand Up @@ -11,17 +11,18 @@ of these phases and the considerations involved in them, so that users can
better understand how Trident handles their storage.

Associating backend storage pools with a storage class relies on both the
storage class's requested attributes and its ``storagePools`` and
``additionalStoragePools`` lists. When a user creates a storage class, Trident
compares the attributes and pools offered by each of its backends to those
requested by the storage class. If a storage pool's attributes and name match
all of the requested attributes and pool names, Trident adds that storage
pool to the set of suitable storage pools for that storage class. In addition,
Trident adds all storage pools listed in the ``additionalStoragePools`` list to
that set, even if their attributes do not fulfill all or any of the storage
class's requested attributes. Trident performs a similar process every time a
user adds a new backend, checking whether its storage pools satisfy those of
the existing storage classes.
storage class's requested attributes and its ``storagePools``,
``additionalStoragePools``, and ``excludeStoragePools`` lists. When a user
creates a storage class, Trident compares the attributes and pools offered by
each of its backends to those requested by the storage class. If a storage pool's
attributes and name match all of the requested attributes and pool names, Trident
adds that storage pool to the set of suitable storage pools for that storage class.
In addition, Trident adds all storage pools listed in the ``additionalStoragePools``
list to that set, even if their attributes do not fulfill all or any of the storage
class's requested attributes. Use the ``excludeStoragePools`` list to override and
remove storage pools from use for a storage class. Trident performs a similar process
every time a user adds a new backend, checking whether its storage pools satisfy
those of the existing storage classes and removing any that have been marked as excluded.

Trident then uses the associations between storage classes and storage pools to
determine where to provision volumes. When a user creates a volume, Trident
Expand Down
13 changes: 13 additions & 0 deletions frontend/kubernetes/plugin.go
Expand Up @@ -1188,6 +1188,19 @@ func (p *Plugin) processAddedClass(class *k8sstoragev1.StorageClass) {
}
scConfig.AdditionalPools = additionalPools

case storageattribute.ExcludeStoragePools:
// format: excludeStoragePools: "backend1:pool1,pool2;backend2:pool1"
excludeStoragePools, err := storageattribute.CreateBackendStoragePoolsMapFromEncodedString(v)
if err != nil {
log.WithFields(log.Fields{
"storageClass": class.Name,
"storageClass_provisioner": class.Provisioner,
"storageClass_parameters": class.Parameters,
"error": err,
}).Errorf("Kubernetes frontend couldn't process the storage class parameter %s", k)
}
scConfig.ExcludePools = excludeStoragePools

case storageattribute.StoragePools:
// format: storagePools: "backend1:pool1,pool2;backend2:pool1"
pools, err := storageattribute.CreateBackendStoragePoolsMapFromEncodedString(v)
Expand Down
1 change: 1 addition & 0 deletions storage_attribute/common_attributes.go
Expand Up @@ -30,6 +30,7 @@ const (
RequiredStorage = "requiredStorage" // deprecated, use additionalStoragePools
StoragePools = "storagePools"
AdditionalStoragePools = "additionalStoragePools"
ExcludeStoragePools = "excludeStoragePools"
)

var attrTypes = map[string]Type{
Expand Down
5 changes: 5 additions & 0 deletions storage_class/config.go
Expand Up @@ -17,6 +17,7 @@ func (c *Config) UnmarshalJSON(data []byte) error {
Pools map[string][]string `json:"storagePools,omitempty"`
RequiredStorage map[string][]string `json:"requiredStorage,omitempty"`
AdditionalPools map[string][]string `json:"additionalStoragePools,omitempty"`
ExcludePools map[string][]string `json:"excludeStoragePools,omitempty"`
}
err := json.Unmarshal(data, &tmp)
if err != nil {
Expand All @@ -34,6 +35,8 @@ func (c *Config) UnmarshalJSON(data []byte) error {
c.AdditionalPools = tmp.AdditionalPools
}

c.ExcludePools = tmp.ExcludePools

return err
}

Expand All @@ -45,11 +48,13 @@ func (c *Config) MarshalJSON() ([]byte, error) {
Attributes json.RawMessage `json:"attributes,omitempty"`
Pools map[string][]string `json:"storagePools,omitempty"`
AdditionalPools map[string][]string `json:"additionalStoragePools,omitempty"`
ExcludePools map[string][]string `json:"excludeStoragePools,omitempty"`
}
tmp.Version = c.Version
tmp.Name = c.Name
tmp.Pools = c.Pools
tmp.AdditionalPools = c.AdditionalPools
tmp.ExcludePools = c.ExcludePools
attrs, err := storageattribute.MarshalRequestMap(c.Attributes)
if err != nil {
return nil, err
Expand Down
110 changes: 90 additions & 20 deletions storage_class/storage_class.go
Expand Up @@ -5,6 +5,7 @@ package storageclass
import (
"encoding/json"
"fmt"
"regexp"
"sort"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -37,20 +38,96 @@ func NewFromPersistent(persistent *Persistent) *StorageClass {
return New(persistent.Config)
}

func (s *StorageClass) regexMatcherImpl(storagePool *storage.Pool, storagePoolBackendName string, storagePoolList []string) bool {
if storagePool == nil {
return false
}
if storagePoolBackendName == "" {
return false
}
if storagePoolList == nil {
return false
}

poolsMatch := false
for _, storagePoolName := range storagePoolList {
backendMatch, err := regexp.MatchString(storagePoolBackendName, storagePool.Backend.Name)
if err != nil {
log.WithFields(log.Fields{
"storagePoolName": storagePoolName,
"storagePool.Name": storagePool.Name,
"storagePool.Backend.Name": storagePool.Backend.Name,
"storagePoolBackendName": storagePoolBackendName,
"err": err,
}).Warning("Error comparing backend names in regexMatcher.")
continue
}
log.WithFields(log.Fields{
"storagePool.Backend.Name": storagePool.Backend.Name,
"storagePoolBackendName": storagePoolBackendName,
"backendMatch": backendMatch,
}).Debug("Compared backend names in regexMatcher.")
if !backendMatch {
continue
}

matched, err := regexp.MatchString(storagePoolName, storagePool.Name)
if err != nil {
log.WithFields(log.Fields{
"storagePoolName": storagePoolName,
"storagePool.Name": storagePool.Name,
"storagePool.Backend.Name": storagePool.Backend.Name,
"poolsMatch": poolsMatch,
"err": err,
}).Warning("Error comparing pool names in regexMatcher.")
continue
}
if matched {
poolsMatch = true
}
log.WithFields(log.Fields{
"storagePoolName": storagePoolName,
"storagePool.Name": storagePool.Name,
"storagePool.Backend.Name": storagePool.Backend.Name,
"poolsMatch": poolsMatch,
}).Debug("Compared pool names in regexMatcher.")
}
return poolsMatch
}

func (s *StorageClass) regexMatcher(storagePool *storage.Pool, poolMap map[string][]string) bool {
poolsMatch := false
if len(poolMap) > 0 {
for storagePoolBackendName, storagePoolList := range poolMap {
poolsMatch = s.regexMatcherImpl(storagePool, storagePoolBackendName, storagePoolList)
if poolsMatch {
return true
}
}
}
return poolsMatch
}

func (s *StorageClass) Matches(storagePool *storage.Pool) bool {

// Check additionalStoragePools first, since it can yield a match result by itself
log.WithFields(log.Fields{
"storageClass": s.GetName(),
"config": s.config,
"pool": storagePool.Name,
"poolBackend": storagePool.Backend.Name,
}).Debug("Checking if storage pool matches.")

// Check excludeStoragePools first, since it can reject a match
if len(s.config.ExcludePools) > 0 {
if matches := s.regexMatcher(storagePool, s.config.ExcludePools); matches {
return false
}
}

// Check additionalStoragePools next, since it can yield a match result by itself
if len(s.config.AdditionalPools) > 0 {
if storagePoolList, ok := s.config.AdditionalPools[storagePool.Backend.Name]; ok {
for _, storagePoolName := range storagePoolList {
if storagePoolName == storagePool.Name {
log.WithFields(log.Fields{
"storageClass": s.GetName(),
"pool": storagePool.Name,
}).Debug("Matched by additionalStoragePools attribute.")
return true
}
}
if matches := s.regexMatcher(storagePool, s.config.AdditionalPools); matches {
return true
}

// Handle the sub-case where additionalStoragePools is specified (but didn't match) and
Expand Down Expand Up @@ -84,19 +161,12 @@ func (s *StorageClass) Matches(storagePool *storage.Pool) bool {
}
}

// The storagePools list is used to narrow the pool selection. Therefore if no pools are
// The storagePools list is used to narrow the pool selection. Therefore, if no pools are
// specified, then all pools can match. If one or more pools are listed in the storage
// class, then the pool must be in the list.
poolsMatch := true
if len(s.config.Pools) > 0 {
poolsMatch = false
if storagePoolList, ok := s.config.Pools[storagePool.Backend.Name]; ok {
for _, storagePoolName := range storagePoolList {
if storagePoolName == storagePool.Name {
poolsMatch = true
}
}
}
poolsMatch = s.regexMatcher(storagePool, s.config.Pools)
}

result := attributesMatch && poolsMatch
Expand Down

0 comments on commit b161ca4

Please sign in to comment.