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

added support for detecting multiple namespaces in a layer #394

Merged
merged 2 commits into from
May 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions api/v1/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Error struct {

type Layer struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
NamespaceNames []string `json:"NamespaceNames,omitempty"`
Path string `json:"Path,omitempty"`
Headers map[string]string `json:"Headers,omitempty"`
ParentName string `json:"ParentName,omitempty"`
Expand All @@ -52,8 +52,8 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
layer.ParentName = dbLayer.Parent.Name
}

if dbLayer.Namespace != nil {
layer.NamespaceName = dbLayer.Namespace.Name
for _, ns := range dbLayer.Namespaces {
layer.NamespaceNames = append(layer.NamespaceNames, ns.Name)
}

if withFeatures || withVulnerabilities && dbLayer.Features != nil {
Expand Down
2 changes: 1 addition & 1 deletion database/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Layer struct {
Name string
EngineVersion int
Parent *Layer
Namespace *Namespace
Namespaces []Namespace
Features []FeatureVersion
}

Expand Down
83 changes: 63 additions & 20 deletions database/pgsql/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
&layer.EngineVersion,
&parentID,
&parentName,
&nsID,
&nsName,
&nsVersionFormat,
)
observeQueryTime("FindLayer", "searchLayer", t)

Expand All @@ -68,11 +65,23 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
Name: parentName.String,
}
}
if !nsID.IsZero() {
layer.Namespace = &database.Namespace{
Model: database.Model{ID: int(nsID.Int64)},
Name: nsName.String,
VersionFormat: nsVersionFormat.String,

rows, err := pgSQL.Query(searchLayerNamespace, layer.ID)
defer rows.Close()
if err != nil {
return layer, handleError("searchLayerNamespace", err)
}
for rows.Next() {
err = rows.Scan(&nsID, &nsName, &nsVersionFormat)
if err != nil {
return layer, handleError("searchLayerNamespace", err)
}
if !nsID.IsZero() {
layer.Namespaces = append(layer.Namespaces, database.Namespace{
Model: database.Model{ID: int(nsID.Int64)},
Name: nsName.String,
VersionFormat: nsVersionFormat.String,
})
}
}

Expand Down Expand Up @@ -277,18 +286,22 @@ func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {
parentID = zero.IntFrom(int64(layer.Parent.ID))
}

// Find or insert namespace if provided.
var namespaceID zero.Int
if layer.Namespace != nil {
n, err := pgSQL.insertNamespace(*layer.Namespace)
// namespaceIDs will contain inherited and new namespaces
namespaceIDs := make(map[int]struct{})

// try to insert the new namespaces
for _, ns := range layer.Namespaces {
n, err := pgSQL.insertNamespace(ns)
if err != nil {
return err
return handleError("pgSQL.insertNamespace", err)
}
namespaceID = zero.IntFrom(int64(n))
} else if layer.Namespace == nil && layer.Parent != nil {
// Import the Namespace from the parent if it has one and this layer doesn't specify one.
if layer.Parent.Namespace != nil {
namespaceID = zero.IntFrom(int64(layer.Parent.Namespace.ID))
namespaceIDs[n] = struct{}{}
}

// inherit namespaces from parent layer
if layer.Parent != nil {
for _, ns := range layer.Parent.Namespaces {
namespaceIDs[ns.ID] = struct{}{}
}
}

Expand All @@ -301,7 +314,7 @@ func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {

if layer.ID == 0 {
// Insert a new layer.
err = tx.QueryRow(insertLayer, layer.Name, layer.EngineVersion, parentID, namespaceID).
err = tx.QueryRow(insertLayer, layer.Name, layer.EngineVersion, parentID).
Scan(&layer.ID)
if err != nil {
tx.Rollback()
Expand All @@ -315,12 +328,18 @@ func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {
}
} else {
// Update an existing layer.
_, err = tx.Exec(updateLayer, layer.ID, layer.EngineVersion, namespaceID)
_, err = tx.Exec(updateLayer, layer.ID, layer.EngineVersion)
if err != nil {
tx.Rollback()
return handleError("updateLayer", err)
}

// replace the old namespace in the database
_, err := tx.Exec(removeLayerNamespace, layer.ID)
if err != nil {
tx.Rollback()
return handleError("removeLayerNamespace", err)
}
// Remove all existing Layer_diff_FeatureVersion.
_, err = tx.Exec(removeLayerDiffFeatureVersion, layer.ID)
if err != nil {
Expand All @@ -329,6 +348,30 @@ func (pgSQL *pgSQL) InsertLayer(layer database.Layer) error {
}
}

// insert the layer's namespaces
stmt, err := tx.Prepare(insertLayerNamespace)

if err != nil {
tx.Rollback()
return handleError("failed to prepare statement", err)
}

defer func() {
err = stmt.Close()
if err != nil {
tx.Rollback()
log.WithError(err).Error("failed to close prepared statement")
}
}()

for nsid := range namespaceIDs {
_, err := stmt.Exec(layer.ID, nsid)
if err != nil {
tx.Rollback()
return handleError("insertLayerNamespace", err)
}
}

// Update Layer_diff_FeatureVersion now.
err = pgSQL.updateDiffFeatureVersions(tx, &layer, &existingLayer)
if err != nil {
Expand Down
95 changes: 74 additions & 21 deletions database/pgsql/layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestFindLayer(t *testing.T) {
layer, err := datastore.FindLayer("layer-0", false, false)
if assert.Nil(t, err) && assert.NotNil(t, layer) {
assert.Equal(t, "layer-0", layer.Name)
assert.Nil(t, layer.Namespace)
assert.Len(t, layer.Namespaces, 0)
assert.Nil(t, layer.Parent)
assert.Equal(t, 1, layer.EngineVersion)
assert.Len(t, layer.Features, 0)
Expand All @@ -52,7 +52,7 @@ func TestFindLayer(t *testing.T) {
layer, err = datastore.FindLayer("layer-1", false, false)
if assert.Nil(t, err) && assert.NotNil(t, layer) {
assert.Equal(t, layer.Name, "layer-1")
assert.Equal(t, "debian:7", layer.Namespace.Name)
assertExpectedNamespaceName(t, &layer, []string{"debian:7"})
if assert.NotNil(t, layer.Parent) {
assert.Equal(t, "layer-0", layer.Parent.Name)
}
Expand Down Expand Up @@ -100,6 +100,27 @@ func TestFindLayer(t *testing.T) {
}
}
}

// Testing Multiple namespaces layer-3b has debian:7 and debian:8 namespaces
layer, err = datastore.FindLayer("layer-3b", true, true)

if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
assert.Equal(t, "layer-3b", layer.Name)
// validate the namespace
assertExpectedNamespaceName(t, &layer, []string{"debian:7", "debian:8"})
for _, featureVersion := range layer.Features {
switch featureVersion.Feature.Namespace.Name {
case "debian:7":
assert.Equal(t, "wechat", featureVersion.Feature.Name)
assert.Equal(t, "0.5", featureVersion.Version)
case "debian:8":
assert.Equal(t, "openssl", featureVersion.Feature.Name)
assert.Equal(t, "1.0", featureVersion.Version)
default:
t.Errorf("unexpected package %s for layer-3b", featureVersion.Feature.Name)
}
}
}
}

func TestInsertLayer(t *testing.T) {
Expand Down Expand Up @@ -205,19 +226,19 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
{
Name: "TestInsertLayer2",
Parent: &database.Layer{Name: "TestInsertLayer1"},
Namespace: &database.Namespace{
Namespaces: []database.Namespace{database.Namespace{
Name: "TestInsertLayerNamespace1",
VersionFormat: dpkg.ParserName,
},
}},
},
// This layer changes the namespace and adds Features.
{
Name: "TestInsertLayer3",
Parent: &database.Layer{Name: "TestInsertLayer2"},
Namespace: &database.Namespace{
Namespaces: []database.Namespace{database.Namespace{
Name: "TestInsertLayerNamespace2",
VersionFormat: dpkg.ParserName,
},
}},
Features: []database.FeatureVersion{f1, f2, f3},
},
// This layer covers the case where the last layer doesn't provide any new Feature.
Expand All @@ -232,10 +253,10 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
{
Name: "TestInsertLayer4b",
Parent: &database.Layer{Name: "TestInsertLayer3"},
Namespace: &database.Namespace{
Namespaces: []database.Namespace{database.Namespace{
Name: "TestInsertLayerNamespace3",
VersionFormat: dpkg.ParserName,
},
}},
Features: []database.FeatureVersion{
// Deletes TestInsertLayerFeature1.
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
Expand Down Expand Up @@ -264,10 +285,9 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
assert.Nil(t, err)
}

// layer inherits all namespaces from its ancestries
l4a := retrievedLayers["TestInsertLayer4a"]
if assert.NotNil(t, l4a.Namespace) {
assert.Equal(t, "TestInsertLayerNamespace2", l4a.Namespace.Name)
}
assertExpectedNamespaceName(t, &l4a, []string{"TestInsertLayerNamespace2", "TestInsertLayerNamespace1"})
assert.Len(t, l4a.Features, 3)
for _, featureVersion := range l4a.Features {
if cmpFV(featureVersion, f1) && cmpFV(featureVersion, f2) && cmpFV(featureVersion, f3) {
Expand All @@ -276,9 +296,7 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
}

l4b := retrievedLayers["TestInsertLayer4b"]
if assert.NotNil(t, l4b.Namespace) {
assert.Equal(t, "TestInsertLayerNamespace3", l4b.Namespace.Name)
}
assertExpectedNamespaceName(t, &l4b, []string{"TestInsertLayerNamespace1", "TestInsertLayerNamespace2", "TestInsertLayerNamespace3"})
assert.Len(t, l4b.Features, 3)
for _, featureVersion := range l4b.Features {
if cmpFV(featureVersion, f2) && cmpFV(featureVersion, f5) && cmpFV(featureVersion, f6) {
Expand All @@ -303,10 +321,10 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
l3u := database.Layer{
Name: l3.Name,
Parent: l3.Parent,
Namespace: &database.Namespace{
Namespaces: []database.Namespace{database.Namespace{
Name: "TestInsertLayerNamespaceUpdated1",
VersionFormat: dpkg.ParserName,
},
}},
Features: []database.FeatureVersion{f7},
}

Expand All @@ -323,7 +341,7 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {

l3uf, err := datastore.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) {
assert.Equal(t, l3.Namespace.Name, l3uf.Namespace.Name)
assertSameNamespaceName(t, &l3, &l3uf)
assert.Equal(t, l3.EngineVersion, l3uf.EngineVersion)
assert.Len(t, l3uf.Features, len(l3.Features))
}
Expand All @@ -336,7 +354,7 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {

l3uf, err = datastore.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) {
assert.Equal(t, l3u.Namespace.Name, l3uf.Namespace.Name)
assertSameNamespaceName(t, &l3u, &l3uf)
assert.Equal(t, l3u.EngineVersion, l3uf.EngineVersion)
if assert.Len(t, l3uf.Features, 1) {
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l3uf.Features[0])
Expand All @@ -352,29 +370,64 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {

l4uf, err := datastore.FindLayer(l3u.Name, true, false)
if assert.Nil(t, err) {
assert.Equal(t, l3u.Namespace.Name, l4uf.Namespace.Name)
assertSameNamespaceName(t, &l3u, &l4uf)
assert.Equal(t, l4u.EngineVersion, l4uf.EngineVersion)
if assert.Len(t, l4uf.Features, 1) {
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l4uf.Features[0])
}
}
}

func assertSameNamespaceName(t *testing.T, layer1 *database.Layer, layer2 *database.Layer) {
assert.Len(t, compareStringLists(extractNamespaceName(layer1), extractNamespaceName(layer2)), 0)
}

func assertExpectedNamespaceName(t *testing.T, layer *database.Layer, expectedNames []string) {
assert.Len(t, compareStringLists(extractNamespaceName(layer), expectedNames), 0)
}

func extractNamespaceName(layer *database.Layer) []string {
slist := make([]string, 0, len(layer.Namespaces))
for _, ns := range layer.Namespaces {
slist = append(slist, ns.Name)
}
return slist
}

func testInsertLayerDelete(t *testing.T, datastore database.Datastore) {
err := datastore.DeleteLayer("TestInsertLayerX")
assert.Equal(t, commonerr.ErrNotFound, err)

// ensure layer_namespace table is cleaned up once a layer is removed
layer3, err := datastore.FindLayer("TestInsertLayer3", false, false)
layer4a, err := datastore.FindLayer("TestInsertLayer4a", false, false)
layer4b, err := datastore.FindLayer("TestInsertLayer4b", false, false)

err = datastore.DeleteLayer("TestInsertLayer3")
assert.Nil(t, err)

_, err = datastore.FindLayer("TestInsertLayer3", false, false)
assert.Equal(t, commonerr.ErrNotFound, err)

assertNotInLayerNamespace(t, layer3.ID, datastore)
_, err = datastore.FindLayer("TestInsertLayer4a", false, false)
assert.Equal(t, commonerr.ErrNotFound, err)

assertNotInLayerNamespace(t, layer4a.ID, datastore)
_, err = datastore.FindLayer("TestInsertLayer4b", true, false)
assert.Equal(t, commonerr.ErrNotFound, err)
assertNotInLayerNamespace(t, layer4b.ID, datastore)
}

func assertNotInLayerNamespace(t *testing.T, layerID int, datastore database.Datastore) {
pg, ok := datastore.(*pgSQL)
if !assert.True(t, ok) {
return
}
tx, err := pg.Begin()
if !assert.Nil(t, err) {
return
}
rows, err := tx.Query(searchLayerNamespace, layerID)
assert.False(t, rows.Next())
}

func cmpFV(a, b database.FeatureVersion) bool {
Expand Down
Loading