-
Notifications
You must be signed in to change notification settings - Fork 239
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #224 from kevinrizza/graph-loader
Graph Loader initial implementation
- Loading branch information
Showing
33 changed files
with
18,894 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package registry | ||
|
||
type Package struct { | ||
Name string | ||
DefaultChannel string | ||
Channels map[string]Channel | ||
} | ||
|
||
type Channel struct { | ||
Head BundleKey | ||
Nodes map[BundleKey]map[BundleKey]struct{} | ||
} | ||
|
||
type BundleKey struct { | ||
BundlePath string | ||
Version string //semver string | ||
CsvName string | ||
} | ||
|
||
func (b *BundleKey) IsEmpty() bool { | ||
return b.BundlePath == "" && b.Version == "" && b.CsvName == "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package sqlite | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
|
||
"github.com/operator-framework/operator-registry/pkg/registry" | ||
) | ||
|
||
type SQLGraphLoader struct { | ||
Querier registry.Query | ||
PackageName string | ||
} | ||
|
||
func NewSQLGraphLoader(dbFilename, name string) (*SQLGraphLoader, error) { | ||
querier, err := NewSQLLiteQuerier(dbFilename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &SQLGraphLoader{ | ||
Querier: querier, | ||
PackageName: name, | ||
}, nil | ||
} | ||
|
||
func NewSQLGraphLoaderFromDB(db *sql.DB, name string) (*SQLGraphLoader, error) { | ||
return &SQLGraphLoader{ | ||
Querier: NewSQLLiteQuerierFromDb(db), | ||
PackageName: name, | ||
}, nil | ||
} | ||
|
||
func (g *SQLGraphLoader) Generate() (*registry.Package, error) { | ||
ctx := context.TODO() | ||
defaultChannel, err := g.Querier.GetDefaultPackage(ctx, g.PackageName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
channelEntries, err := g.Querier.GetChannelEntriesFromPackage(ctx, g.PackageName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
channels, err := graphFromEntries(channelEntries) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return ®istry.Package{ | ||
Name: g.PackageName, | ||
DefaultChannel: defaultChannel, | ||
Channels: channels, | ||
}, nil | ||
} | ||
|
||
// graphFromEntries builds the graph from a set of channel entries | ||
func graphFromEntries(channelEntries []registry.ChannelEntryAnnotated) (map[string]registry.Channel, error) { | ||
channels := map[string]registry.Channel{} | ||
|
||
type replaces map[registry.BundleKey]map[registry.BundleKey]struct{} | ||
|
||
channelGraph := map[string]replaces{} | ||
channelHeadCandidates := map[string]map[registry.BundleKey]struct{}{} | ||
|
||
// add all channels and nodes to the graph | ||
for _, entry := range channelEntries { | ||
// create channel if we haven't seen it yet | ||
if _, ok := channelGraph[entry.ChannelName]; !ok { | ||
channelGraph[entry.ChannelName] = replaces{} | ||
} | ||
|
||
key := registry.BundleKey{ | ||
BundlePath: entry.BundlePath, | ||
Version: entry.Version, | ||
CsvName: entry.BundleName, | ||
} | ||
channelGraph[entry.ChannelName][key] = map[registry.BundleKey]struct{}{} | ||
|
||
// every bundle in a channel is a potential head of that channel | ||
if _, ok := channelHeadCandidates[entry.ChannelName]; !ok { | ||
channelHeadCandidates[entry.ChannelName] = map[registry.BundleKey]struct{}{key: {}} | ||
} else { | ||
channelHeadCandidates[entry.ChannelName][key] = struct{}{} | ||
} | ||
} | ||
|
||
for _, entry := range channelEntries { | ||
key := registry.BundleKey{ | ||
BundlePath: entry.BundlePath, | ||
Version: entry.Version, | ||
CsvName: entry.BundleName, | ||
} | ||
replacesKey := registry.BundleKey{ | ||
BundlePath: entry.BundlePath, | ||
Version: entry.ReplacesVersion, | ||
CsvName: entry.Replaces, | ||
} | ||
|
||
if !replacesKey.IsEmpty() { | ||
channelGraph[entry.ChannelName][key][replacesKey] = struct{}{} | ||
} | ||
|
||
delete(channelHeadCandidates[entry.ChannelName], replacesKey) | ||
} | ||
|
||
for channelName, candidates := range channelHeadCandidates { | ||
if len(candidates) == 0 { | ||
return nil, fmt.Errorf("no channel head found for %s", channelName) | ||
} | ||
if len(candidates) > 1 { | ||
return nil, fmt.Errorf("multiple candidate channel heads found for %s: %v", channelName, candidates) | ||
} | ||
|
||
for head := range candidates { | ||
channel := registry.Channel{ | ||
Head: head, | ||
Nodes: channelGraph[channelName], | ||
} | ||
channels[channelName] = channel | ||
} | ||
} | ||
|
||
return channels, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package sqlite | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"github.com/operator-framework/operator-registry/pkg/registry" | ||
"math/rand" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func createLoadedTestDb(t *testing.T) (*sql.DB, func()) { | ||
dbName := fmt.Sprintf("test-%d.db", rand.Int()) | ||
|
||
db, err := sql.Open("sqlite3", dbName) | ||
require.NoError(t, err) | ||
|
||
dbLoader, err := NewSQLLiteLoader(db) | ||
require.NoError(t, err) | ||
|
||
err = dbLoader.Migrate(context.TODO()) | ||
require.NoError(t, err) | ||
|
||
loader := NewSQLLoaderForDirectory(dbLoader, "./testdata/loader_data") | ||
err = loader.Populate() | ||
require.NoError(t, err) | ||
|
||
return db, func() { | ||
defer func() { | ||
if err := os.Remove(dbName); err != nil { | ||
t.Fatal(err) | ||
} | ||
}() | ||
if err := db.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
} | ||
|
||
func TestLoadPackageGraph_Etcd(t *testing.T) { | ||
expectedGraph := ®istry.Package{ | ||
Name: "etcd", | ||
DefaultChannel: "alpha", | ||
Channels: map[string]registry.Channel{ | ||
"alpha": { | ||
Head: registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}, | ||
Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ | ||
registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: {}, | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, | ||
}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}: { | ||
registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: struct{}{}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
"beta": { | ||
Head: registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}, | ||
Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
"stable": { | ||
Head: registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}, | ||
Nodes: map[registry.BundleKey]map[registry.BundleKey]struct{}{ | ||
registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: {}, | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: {}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: { | ||
registry.BundleKey{BundlePath: "", Version: "0.6.1", CsvName: "etcdoperator.v0.6.1"}: struct{}{}, | ||
}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.2", CsvName: "etcdoperator.v0.9.2"}: { | ||
registry.BundleKey{BundlePath: "", Version: "", CsvName: "etcdoperator.v0.9.1"}: struct{}{}, | ||
registry.BundleKey{BundlePath: "", Version: "0.9.0", CsvName: "etcdoperator.v0.9.0"}: struct{}{}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
db, cleanup := createLoadedTestDb(t) | ||
defer cleanup() | ||
|
||
graphLoader, err := NewSQLGraphLoaderFromDB(db, "etcd") | ||
require.NoError(t, err) | ||
|
||
result, err := graphLoader.Generate() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, "etcd", result.Name) | ||
require.Equal(t, 3, len(result.Channels)) | ||
|
||
for channelName, channel := range result.Channels { | ||
expectedChannel := expectedGraph.Channels[channelName] | ||
require.Equal(t, expectedChannel.Head, channel.Head) | ||
require.EqualValues(t, expectedChannel.Nodes, channel.Nodes) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
expected:[]registry.OperatorBundle{registry.OperatorBundle{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x9, Patch: 0x2, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.9.2", ReplacesBundles: []registry.OperatorBundle{}, Replaces: []registry.BundleRef{registry.BundleRef{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x9, Patch: 0x0, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.9.0"}}}, registry.OperatorBundle{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x0, Patch: 0x0, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.9.1", ReplacesBundles: []registry.OperatorBundle{}, Replaces: []registry.BundleRef{}}, registry.OperatorBundle{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x9, Patch: 0x0, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.9.0", ReplacesBundles: []registry.OperatorBundle{}, Replaces: []registry.BundleRef{registry.BundleRef{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x6, Patch: 0x1, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.6.1"}}}, registry.OperatorBundle{BundlePath: "", Version: semver.Version{Major: 0x0, Minor: 0x6, Patch: 0x1, Pre: []semver.PRVersion(nil), Build: []string(nil)}, CsvName: "etcdoperator.v0.6.1", ReplacesBundles: []registry.OperatorBundle{}, Replaces: []registry.BundleRef{}}} |
Oops, something went wrong.