Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added feast Go operator db stores support #4771

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

tmihalac
Copy link
Contributor

@tmihalac tmihalac commented Nov 19, 2024

What this PR does / why we need it:

This PR adds db stores persistence configuration options to the FeatureStore CRD.
DB store para

  • Support is provided to the Online service, Offline service and Registry service.
  • Follows definitions in RFC #42
  • Configuration examples are provided in config/samples
kind: FeatureStore
metadata:
  name: sample-s3-registry
spec:
  feastProject: my_project
  services:
    onlineStore:
      persistence:
        store:
          type: postgres
          secretRef: 
            name: _SECRET_NAME_ # Secret keys should be placed as-is under the `type` (e.g postgres) key or under the secretKeyName if specified
          secretKeyName: my-postgres # The secret key the db parameters are in (optional parameter)
    offlineStore:
      persistence:
        store:
          type: postgres
          secretRef: 
            name: _SECRET_NAME_ # Secret keys should be placed as-is under the `type` (e.g postgres) key or under the secretKeyName if specified
          secretKeyName: my-postgres # The secret key the db parameters are in (optional parameter)
    registry:
      local:
        persistence:
          store:
            type: sql
            secretRef: 
              name: _SECRET_NAME_ # Secret keys should be placed as-is under the `type` (e.g sql) key or under the secretKeyName if specified
            secretKeyName: my-postgres # The secret key the db parameters are in (optional parameter)
kind: Secret
apiVersion: v1
metadata:
  name: my-secret
  namespace: test
  uid: b410b5e5-857b-4611-acc3-1302b8c6c68c
  resourceVersion: '62030'
  creationTimestamp: '2024-11-19T20:25:15Z'
  managedFields:
    - manager: kubectl-create
      operation: Update
      apiVersion: v1
      time: '2024-11-19T20:25:15Z'
      fieldsType: FieldsV1
      fieldsV1:
        'f:data':
          .: {}
          'f:mykey': {}
        'f:type': {}
data:
  postgres: |
    host: DB_HOST
    port: DB_PORT
    database: DB_NAME
    db_schema: DB_SCHEMA
    user: DB_USERNAME
    password: DB_PASSWORD
    sslmode: verify-ca
    sslkey_path: /path/to/client-key.pem
    sslcert_path: /path/to/client-cert.pem
    sslrootcert_path: /path/to/server-ca.pem
  sql: |
    path: postgresql://postgres:[email protected]:55001/feast
    cache_ttl_seconds: 60
    sqlalchemy_config_kwargs:
        echo: false
        pool_pre_ping: true
type: Opaque

Which issue(s) this PR fixes:

Relates to #4783

@tmihalac tmihalac requested a review from a team as a code owner November 19, 2024 21:12
Comment on lines 166 to 198

if services.OfflineStore.Persistence != nil &&
services.OfflineStore.Persistence.FilePersistence != nil &&
len(services.OfflineStore.Persistence.FilePersistence.Type) > 0 {
persistenceType = services.OfflineStore.Persistence.FilePersistence.Type
} else if services.OfflineStore.Persistence != nil &&
services.OfflineStore.Persistence.DBPersistence != nil {
if len(services.OfflineStore.Persistence.DBPersistence.Type) > 0 {
var err error
persistenceType = services.OfflineStore.Persistence.DBPersistence.Type
secretKeyName := services.OfflineStore.Persistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err = secretExtractionFunc(services.OfflineStore.Persistence.DBPersistence.SecretRef.Name, secretKeyName)
if err != nil {
return err
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for these setRepoConfig functions, maybe we can clean it up a bit by consolidating?

Suggested change
if services.OfflineStore.Persistence != nil &&
services.OfflineStore.Persistence.FilePersistence != nil &&
len(services.OfflineStore.Persistence.FilePersistence.Type) > 0 {
persistenceType = services.OfflineStore.Persistence.FilePersistence.Type
} else if services.OfflineStore.Persistence != nil &&
services.OfflineStore.Persistence.DBPersistence != nil {
if len(services.OfflineStore.Persistence.DBPersistence.Type) > 0 {
var err error
persistenceType = services.OfflineStore.Persistence.DBPersistence.Type
secretKeyName := services.OfflineStore.Persistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err = secretExtractionFunc(services.OfflineStore.Persistence.DBPersistence.SecretRef.Name, secretKeyName)
if err != nil {
return err
}
}
}
offlinePersistence := services.OfflineStore.Persistence
if offlinePersistence != nil &&
offlinePersistence.FilePersistence != nil &&
len(offlinePersistence.FilePersistence.Type) > 0 {
persistenceType = offlinePersistence.FilePersistence.Type
} else if offlinePersistence != nil &&
offlinePersistence.DBPersistence != nil {
if len(offlinePersistence.DBPersistence.Type) > 0 {
var err error
persistenceType = offlinePersistence.DBPersistence.Type
secretKeyName := offlinePersistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err = secretExtractionFunc(offlinePersistence.DBPersistence.SecretRef.Name, secretKeyName)
if err != nil {
return err
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 104 to 111
var err error
filePath = ""
persistenceType = services.Registry.Local.Persistence.DBPersistence.Type
secretKeyName := services.Registry.Local.Persistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err = secretExtractionFunc(services.Registry.Local.Persistence.DBPersistence.SecretRef.Name, secretKeyName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to declare an error var... instead use a short assignment statement as we do elsewhere in the code.

Suggested change
var err error
filePath = ""
persistenceType = services.Registry.Local.Persistence.DBPersistence.Type
secretKeyName := services.Registry.Local.Persistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err = secretExtractionFunc(services.Registry.Local.Persistence.DBPersistence.SecretRef.Name, secretKeyName)
filePath = ""
persistenceType = services.Registry.Local.Persistence.DBPersistence.Type
secretKeyName := services.Registry.Local.Persistence.DBPersistence.SecretKeyName
if len(secretKeyName) == 0 {
secretKeyName = persistenceType
}
parametersMap, err := secretExtractionFunc(services.Registry.Local.Persistence.DBPersistence.SecretRef.Name, secretKeyName)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't since parametersMap is defined outside the if and is used outside the if and if I use := it shadows that value

repoConfig.Registry = RegistryConfig{
		RegistryType:       RegistryConfigType(persistenceType),
		Path:               filePath,
		S3AdditionalKwargs: s3AdditionalKwargs,
		DBParameters:       parametersMap,
}

Copy link
Contributor

@tchughesiv tchughesiv Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I use := it shadows that value

what does this mean?
fwiw, go will detect that parametersMap has already been declared and reassign it accordingly.

e.g. this will print 3 4 5 6

package main

import "fmt"

func main() {
	var i, j int = 1, 2
	k := 3
	i, j, k, l := 3, 4, 5, 6

	fmt.Println(i, j, k, l)
}

Copy link
Contributor Author

@tmihalac tmihalac Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I change that to := I am getting an error unused variable parametersMap
see https://go.dev/play/p/WDtgq1kGlFc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok... because its not being used until after the if statement... i see now.

} else if services.OnlineStore.Persistence != nil &&
services.OnlineStore.Persistence.DBPersistence != nil {
if len(services.OnlineStore.Persistence.DBPersistence.Type) > 0 {
var err error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my pervious comment

} else if services.OfflineStore.Persistence != nil &&
services.OfflineStore.Persistence.DBPersistence != nil {
if len(services.OfflineStore.Persistence.DBPersistence.Type) > 0 {
var err error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my pervious comment

objectKey := client.ObjectKeyFromObject(secret)
if err := feast.Client.Get(feast.Context, objectKey, secret); err != nil {
if apierrors.IsNotFound(err) || err != nil {
return nil, fmt.Errorf("invalid secret %s for offline store", secretRef)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just return the error here but log an Error prior to the return

Suggested change
return nil, fmt.Errorf("invalid secret %s for offline store", secretRef)
logger := log.FromContext(feast.Context)
logger.Error("invalid secret %s for offline store", secretRef)
return nil, err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@dmartinol
Copy link
Contributor

@tmihalac the issue you are referring to is already closed. @tchughesiv should we create a new one or reopen it?

@dmartinol
Copy link
Contributor

In the description, the postgres type is not supported by registry, pls change it to sql (and also add a sql key to the secret name)

    registry:
      local:
        persistence:
          store:
          type: postgres

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add a sample Secret (with postgres properties) to the same file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, an example of the sql registry type could be helpful, since it has some specific management of the db type in the path property:

registry:
    registry_type: sql
    path: postgresql://postgres:[email protected]:55001/feast
    cache_ttl_seconds: 60
    sqlalchemy_config_kwargs:
        echo: false
        pool_pre_ping: true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you commit the change? II can't see it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet still working on it, trying to find a fix for db parameters the same as the config object parameters


Expect(err.Error()).To(Equal("invalid secret invalid_secret for offline store"))

By("Referring to a secret that doesn't exist")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe change of of the two By("Referring to a secret that doesn't exist")?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add simple tests for invalid store type for each of the feast service?

@tchughesiv
Copy link
Contributor

@tmihalac @dmartinol we should be opening new issues for operator PRs

Comment on lines 117 to 122
repoConfig.Registry = RegistryConfig{
RegistryType: RegistryConfigType(persistenceType),
Path: filePath,
S3AdditionalKwargs: s3AdditionalKwargs,
DBParameters: parametersMap,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tested what happens if a user specificies a conflicting registry_type in their secret? we need to be sure the operator has final say on setting that field in the feature_store.yaml. Same goes for type in online/offlineStores. If you find it can be overwritten by the parametersMap, maybe we set RegistryType last? As a secondary line of code after this RegistryConfig declaration?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the same needs to be done for filePath and s3AdditionalKwargs ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and some unit tests around this phenomenon? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm realizing this is more important at the time of yaml marshaling... and i'm expecting that the defined fields should take precedence when that happens. it's possible all we really need here are good unit tests around the marshaled yaml to ensure this is the case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe test how setting a different registry_type in DBParameters map will effect the resulting yaml

Copy link
Contributor

@tchughesiv tchughesiv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmihalac please rebase on master branch

@tmihalac tmihalac force-pushed the go-operator-db-stores branch 2 times, most recently from 59a08d7 to 8a4fd21 Compare November 21, 2024 18:27
@tchughesiv
Copy link
Contributor

@tmihalac please open a new gh issue for this PR

S3AdditionalKwargs *map[string]string `json:"s3_additional_kwargs,omitempty"`
Path string `yaml:"path,omitempty"`
RegistryType RegistryConfigType `yaml:"registry_type,omitempty"`
S3AdditionalKwargs *map[string]string `json:"s3_additional_kwargs,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you fix this? its not related to your change but needs to be fixed.

Suggested change
S3AdditionalKwargs *map[string]string `json:"s3_additional_kwargs,omitempty"`
S3AdditionalKwargs *map[string]string `yaml:"s3_additional_kwargs,omitempty"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 253 to 258
//val := reflect.ValueOf(s).Field(i)

//// Check that the object is a pointer so we can modify it
//if val.Kind() != reflect.Ptr || val.IsNil() {
// return false, fmt.Errorf("expected a pointer to struct, got %v", val.Kind())
//}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be cleaned up?

Suggested change
//val := reflect.ValueOf(s).Field(i)
//// Check that the object is a pointer so we can modify it
//if val.Kind() != reflect.Ptr || val.IsNil() {
// return false, fmt.Errorf("expected a pointer to struct, got %v", val.Kind())
//}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 228 to 231
// Check if the value is a pointer and dereference it if necessary
//if t.Kind() == reflect.Ptr {
// t = t.Elem()
//}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@tchughesiv
Copy link
Contributor

@tmihalac should your descrip example be fixed? i'd imagine it will look like this? with the secretKeyName pulled back to under store?

kind: FeatureStore
metadata:
  name: sample-s3-registry
spec:
  feastProject: my_project
  services:
    onlineStore:
      persistence:
        store:
          type: postgres
          secretRef: 
            name: _SECRET_NAME_ # Secret keys should be placed as-is under the `type` (e.g postgres) key or under the secretKeyName if specified
          secretKeyName: my-postgres # The secret key the db parameters are in (optional parameter)

@tmihalac
Copy link
Contributor Author

@tmihalac should your descrip example be fixed? i'd imagine it will look like this? with the secretKeyName pulled back to under store?

kind: FeatureStore
metadata:
  name: sample-s3-registry
spec:
  feastProject: my_project
  services:
    onlineStore:
      persistence:
        store:
          type: postgres
          secretRef: 
            name: _SECRET_NAME_ # Secret keys should be placed as-is under the `type` (e.g postgres) key or under the secretKeyName if specified
          secretKeyName: my-postgres # The secret key the db parameters are in (optional parameter)

You are right fixing it now

@tmihalac
Copy link
Contributor Author

Done

Comment on lines +99 to +100
Type string `json:"type,omitempty"`
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these be required fields?

Suggested change
Type string `json:"type,omitempty"`
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
Type string `json:"type"`
SecretRef *corev1.LocalObjectReference `json:"secretRef"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmartinol @lokeshrangineni @redhatHameed
I am not sure, what do you think ? anyone ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmartinol gave a thumbs up. @tmihalac from a code & usability standpoint... aren't these 2 fields required if the user decides to configure a db store? (i believe they are) if so, we need to make them required in the CRD.

Comment on lines +139 to +141
Type string `json:"type,omitempty"`
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
SecretKeyName string `json:"secretKeyName,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Comment on lines +184 to +186
Type string `json:"type,omitempty"`
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
SecretKeyName string `json:"secretKeyName,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same?

// +kubebuilder:validation:Enum=snowflake.offline;bigquery;redshift;spark;postgres;feast_trino.trino.TrinoOfflineStore;redis
Type string `json:"type,omitempty"`
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
SecretKeyName string `json:"secretKeyName,omitempty"`
Copy link
Contributor

@tchughesiv tchughesiv Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment to SecretKeyName explaining what the default will be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tchughesiv
What do you mean by default ? there is no default , do you mean when that field is not specified in the CR ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean when that field is not specified in the CR ?

yes ... isn't the default here the db store type? i'm saying just add a comment above the field (which will add documentation to the CRD) so the user understands this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants