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

Add Datastore layer #73

Merged
merged 39 commits into from
Mar 1, 2023

Conversation

maxlambrecht
Copy link
Contributor

@maxlambrecht maxlambrecht commented Dec 15, 2022

Signed-off-by: Max Lambrecht [email protected]

Pull request check list

  • Proper tests/regressions included?
  • Documentation updated?

Affected functionality
Added a new Datastore implementation that supports Postgres

Description of change

  • This PR adds migration SQLs to create the schema based on the new data model, and also adds the SQL Queries.
  • The interfaces, model structs, and lower level DB operations were auto generated using sqlc which uses the SQL files from migrations and queries as input.
  • A new Datastore struct was added, that defines CRUD methods for all the entities of Galadriel.
  • When the Datastore is created and it connects to the configured DB, the schema is validated and migrations applied if needed, using golang-migrate.
  • Tests for the Datastore were implemented using dockertest, which starts a container from a Postgres image, runs the migrations, and then run the test.

Which issue this pull requests fixes

Signed-off-by: Max Lambrecht <[email protected]>
@maxlambrecht maxlambrecht marked this pull request as draft December 15, 2022 22:25
@maxlambrecht maxlambrecht changed the title Add Datastore layer [DRAFT] Add Datastore layer Dec 15, 2022
Copy link
Contributor

@jufantozzi jufantozzi left a comment

Choose a reason for hiding this comment

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

Great stuff! Did a first quick pass-through and got stuck thinking into two different things:

On a more high-level discussion, do you still see we having one interface to abstract the datastore layer, in which each of the different implementations (postgres, mysql, etc) will have to satisfy? I couldn't find that interface anymore, but the Datastore object looks like it would implement this interface.

and the 2nd topic is related to using a connection pool, can be discussed in the comment below.

db/schema.go Outdated
)

//go:embed migrations/*.sql
var fs embed.FS
Copy link
Contributor

Choose a reason for hiding this comment

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

this is new to me :D

Copy link
Collaborator

Choose a reason for hiding this comment

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

Embed go feature allows embedding files in compile time. However, some errors can only be caught in runtime. (ie. If a SQL file is not well formatted or even when there is no SQL in an empty file). Another similar feature is go:generate which allows running code at compile time. Take a look:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The embedded SQLs (for migrations) are being exercised in the CRUD tests.

db/datastore.go Outdated
return nil, fmt.Errorf("failed to parse Postgres Connection URL: %w", err)
}

db := stdlib.OpenDB(*c)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm: this does not use a connection pool, does 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.

This does use a connection pool:

// DB is a database handle representing a pool of zero or more
// underlying connections. It's safe for concurrent use by multiple
// goroutines.

Copy link
Collaborator

@Victorblsilveira Victorblsilveira left a comment

Choose a reason for hiding this comment

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

Amazing work!!! See if my comments make sense

@Victorblsilveira
Copy link
Collaborator

Great stuff! Did a first quick pass-through and got stuck thinking into two different things:

On a more high-level discussion, do you still see we having one interface to abstract the datastore layer, in which each of the different implementations (postgres, mysql, etc) will have to satisfy? I couldn't find that interface anymore, but the Datastore object looks like it would implement this interface.

and the 2nd topic is related to using a connection pool, can be discussed in the comment below.

About the abstract datastore layer, I think that is this one: querier.go

Max Lambrecht added 2 commits December 20, 2022 11:50
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);

CREATE TABLE join_tokens
Copy link
Contributor

Choose a reason for hiding this comment

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

Will join tokens not be related to harvesters in any way? Thinking about HA, does it matter if we're able to validate if the join token was for a specific harvester?

Copy link
Contributor

Choose a reason for hiding this comment

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

I know this has some impacts we would also have to think about, like how to deal when a harvester is deleted. This specifically is also something I was thinking for a bit and considering: wouldn't it be a better idea to instead of deleting harvesters to just disable them? is this data really not worth keeping?

Copy link
Collaborator

Choose a reason for hiding this comment

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

According to our data model, there is an implicit relation between join_token and harvesters through members relation, but not an explicit one. Would be enough for the validation to know that the join token is for a member that is related to the harvester, or you are thinking of a more restricted validation in which the token needs to be exactly issued for a harvester?

Copy link
Contributor Author

@maxlambrecht maxlambrecht Dec 20, 2022

Choose a reason for hiding this comment

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

Would be enough for the validation to know that the join token is for a member that is related to the harvester?

As I understand it so far, I think it would be enough for the validation knowing which member a token belongs to, whether it was used or not, and whether it didn't expire.

Copy link
Contributor

Choose a reason for hiding this comment

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

According to our data model, there is an implicit relation between join_token and harvesters through members relation, but not an explicit one

my bad on the wording, that's what I actually meant 😅

@maxlambrecht
Copy link
Contributor Author

Great stuff! Did a first quick pass-through and got stuck thinking into two different things:
On a more high-level discussion, do you still see we having one interface to abstract the datastore layer, in which each of the different implementations (postgres, mysql, etc) will have to satisfy? I couldn't find that interface anymore, but the Datastore object looks like it would implement this interface.
and the 2nd topic is related to using a connection pool, can be discussed in the comment below.

About the abstract datastore layer, I think that is this one: querier.go

We could still abstract the Datastore layer in an interface that uses entity objects from the API. I didn't think about that because this PR introduces a Datastore that just supports one dialect, and I still don't know if we'll need that abstraction anyway, even in the case of supporting multiple dialects.
The querier interface is auto-generated by the sqlc and it's a lower level interface that uses DB models.

@jufantozzi
Copy link
Contributor

We could still abstract the Datastore layer in an interface that uses entity objects from the API. I didn't think about that because this PR introduces a Datastore that just supports one dialect, and I still don't know if we'll need that abstraction anyway, even in the case of supporting multiple dialects.
The querier interface is auto-generated by the sqlc and it's a lower level interface that uses DB models.

Right, it makes sense. Yes, the querier interface looks good enough, at least for now. Although this higher level abstraction will still be implemented in some form, (at the handlers, most likely?) by having an interface we're better segregating this logic for our handlers. Anyways, not something for right now

Signed-off-by: Max Lambrecht <[email protected]>
Copy link
Contributor

@jufantozzi jufantozzi left a comment

Choose a reason for hiding this comment

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

LGTM!! Btw, you're not planning on incorporating these changes into galadriel on this PR, right? If not, I'm ready to approve.

@maxlambrecht
Copy link
Contributor Author

LGTM!! Btw, you're not planning on incorporating these changes into galadriel on this PR, right? If not, I'm ready to approve.

No need to approve just yet, I'm expecting more feedback from the rest of the team.

@jufantozzi
Copy link
Contributor

Btw, you're not planning on incorporating these changes into galadriel on this PR, right?

Just realized my last question was a bit ambiguous, I wanted to ask about replacing the current In memory datastore to use this implementation. This should happen in a separate PR right?

@maxlambrecht
Copy link
Contributor Author

Btw, you're not planning on incorporating these changes into galadriel on this PR, right?

Just realized my last question was a bit ambiguous, I wanted to ask about replacing the current In memory datastore to use this implementation. This should happen in a separate PR right?

IMO migrating Galadriel to the new Datastore is not worth doing right now, since the whole implementation of Galadriel will be mostly re-visited while moving from the POC to the MVP. Each new (re)implemented functionality in Galadriel Server will need to use the new Datastore.

@maxlambrecht maxlambrecht marked this pull request as ready for review January 17, 2023 18:08
@maxlambrecht maxlambrecht changed the title [DRAFT] Add Datastore layer Add Datastore layer Jan 17, 2023
Max Lambrecht added 4 commits January 17, 2023 12:11
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
@maxlambrecht maxlambrecht changed the title [DRAFT] Add Datastore layer Add Datastore layer Feb 27, 2023
@maxlambrecht maxlambrecht marked this pull request as ready for review February 27, 2023 14:48
e.handleError(w, errMsg)
return
}

e.Logger.Printf("Created member for trust domain: %s", memberReq.TrustDomain)
e.Logger.Printf("Created trustDomain for trust domain: %s", trustDomainReq.Name)

Check failure

Code scanning / CodeQL

Log entries created from user input

This log write receives unsanitized user input from [here](1).
Max Lambrecht added 2 commits February 27, 2023 10:02
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
return err
}

e.Logger.Debugf("Trust domain %s has been successfully updated", harvesterReq.TrustDomainName)

Check failure

Code scanning / CodeQL

Log entries created from user input

This log write receives unsanitized user input from [here](1).
Signed-off-by: Max Lambrecht <[email protected]>
@@ -215,11 +283,15 @@
e.Logger.Errorf(errMsg)

errBytes := []byte(errMsg)
w.WriteHeader(len(errBytes))
w.WriteHeader(500)
_, err := w.Write(errBytes)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1). Cross-site scripting vulnerability due to [user-provided value](2). Cross-site scripting vulnerability due to [user-provided value](3).
Copy link
Contributor

@jufantozzi jufantozzi left a comment

Choose a reason for hiding this comment

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

Amazing work @maxlambrecht ❤️

Did a code pass-through and it looks impeccable, I'm just not approving it now cause I didn't have time to test yet.

One topic that came to my mind though, is that I noticed we're not supporting TLS for the postgres connection. IDK if this is something needed for now, I don't think we've discussed this yet, but I'm pretty sure we'll have to think about this at some point. WDYT?

if receivedHarvesterState.TrustDomain.Compare(token.TrustDomain) != 0 {
err := fmt.Errorf("authenticated trust domain {%s} does not match received trust domain {%s}", receivedHarvesterState.TrustDomain.String(), token.TrustDomain.String())

if harvesterReq.TrustDomainName != authenticatedTD.Name {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just out of curiosity, was this change due to readability?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, IMO it reads better that way.

return nil, nil, err
}

if b != nil {
Copy link
Contributor

@jufantozzi jufantozzi Feb 27, 2023

Choose a reason for hiding this comment

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

Wondering if we might want to have a debug(?) log in case this is nil

@maxlambrecht
Copy link
Contributor Author

Amazing work @maxlambrecht ❤️

Did a code pass-through and it looks impeccable, I'm just not approving it now cause I didn't have time to test yet.

One topic that came to my mind though, is that I noticed we're not supporting TLS for the postgres connection. IDK if this is something needed for now, I don't think we've discussed this yet, but I'm pretty sure we'll have to think about this at some point. WDYT?

Thanks Juliano! ❤️

The Postgres connection string allows TLS, something like:

"sslmode=require sslrootcert=server-ca.pem sslcert=client-cert.pem sslkey=client-key.pem hostaddr=your_host port=5432 user=your_user dbname=your_db"

Signed-off-by: Max Lambrecht <[email protected]>
jufantozzi
jufantozzi previously approved these changes Feb 27, 2023
Copy link
Contributor

@jufantozzi jufantozzi left a comment

Choose a reason for hiding this comment

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

💯 💯

Copy link
Collaborator

@mgbcaio mgbcaio left a comment

Choose a reason for hiding this comment

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

this is a number 10 work, right here. Nice job, Max 🏅 Ready to approve as soon as others take a look into this.

Copy link
Collaborator

@Victorblsilveira Victorblsilveira left a comment

Choose a reason for hiding this comment

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

Hey @maxlambrecht, great work here.
A lot of changes, most of my comments are about naming.
Don't feel obliged to accept it, but let me know what you think about it.
Feel free to ignore them as well.

Copy link
Contributor

@wibarre wibarre left a comment

Choose a reason for hiding this comment

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

Just focused on the pkg/server/datastore. Thank you Maxl for this. Looks great!

Max Lambrecht added 4 commits February 28, 2023 12:07
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
Signed-off-by: Max Lambrecht <[email protected]>
Copy link
Member

@mchurichi mchurichi left a comment

Choose a reason for hiding this comment

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

This is looking great, Max! 💯

Signed-off-by: Max Lambrecht <[email protected]>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 1, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 8 Code Smells

No Coverage information No Coverage information
1.2% 1.2% Duplication

Copy link
Member

@mchurichi mchurichi left a comment

Choose a reason for hiding this comment

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

🌭

@maxlambrecht maxlambrecht merged commit fe91db8 into HewlettPackard:main Mar 1, 2023
@maxlambrecht maxlambrecht deleted the feature/datastore branch March 1, 2023 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants