Draw the actual rest of the fucking owl.

Change-Id: Ia04fb49ebbe3a5afccc57e62f6335e35b45192fe
diff --git a/bgpwtf/cccampix/verifier/model/config.go b/bgpwtf/cccampix/verifier/model/config.go
index bafd46f..1cbd2d7 100644
--- a/bgpwtf/cccampix/verifier/model/config.go
+++ b/bgpwtf/cccampix/verifier/model/config.go
@@ -4,6 +4,8 @@
 	"context"
 	"database/sql"
 	"fmt"
+	"net"
+	"strconv"
 )
 
 func (m *sqlModel) ConfigureMissingSessions(ctx context.Context, gen func() SessionConfig) error {
@@ -49,3 +51,75 @@
 
 	return tx.Commit()
 }
+
+func (m *sqlModel) GetPeerConfiguration(ctx context.Context) ([]*PeerConfiguration, error) {
+	q := `
+		SELECT
+		    peers.asn "asn",
+			peer_pgp_keys.fingerprint "peer_pgp_keys.fingerprint",
+			peer_routers.v6 "peer_routers.v6", peer_routers.v4 "peer_routers.v4",
+			session_configs.bgp_secret "session_configs.bgp_secret"
+		FROM session_configs
+		LEFT JOIN peer_routers
+		ON peer_routers.id = session_configs.peer_router_id
+		INNER JOIN peer_pgp_keys
+		ON peer_pgp_keys.peer_id = session_configs.peer_id
+		LEFT JOIN peers
+		on peers.id = session_configs.peer_id
+	`
+
+	data := []struct {
+		PGP    sqlPeerPGPKey    `db:"peer_pgp_keys"`
+		Config sqlSessionConfig `db:"session_configs"`
+		Router sqlPeerRouter    `db:"peer_routers"`
+		ASN    string           `db:"asn"`
+	}{}
+
+	if err := m.db.SelectContext(ctx, &data, q); err != nil {
+		return nil, fmt.Errorf("SELECT peers/peer_pgp_keys/session_configs: %v", err)
+	}
+
+	resM := make(map[string]*PeerConfiguration)
+
+	for _, d := range data {
+		k := fmt.Sprintf("%s", d.ASN)
+		r, ok := resM[k]
+		if !ok {
+			asn, err := strconv.ParseInt(d.ASN, 10, 64)
+			if err != nil {
+				return nil, fmt.Errorf("data corruption: invalid ASN %q", d.ASN)
+			}
+			r = &PeerConfiguration{
+				Peer: Peer{
+					ASN:     asn,
+					Routers: []*Router{},
+				},
+				Key: PeerPGPKey{
+					PeerASN:     asn,
+					Fingerprint: d.PGP.Fingerprint,
+				},
+			}
+			resM[k] = r
+		}
+
+		v6 := net.ParseIP(d.Router.V6.String)
+		v4 := net.ParseIP(d.Router.V4.String)
+		secret := d.Config.BGPSecret
+		r.Peer.Routers = append(r.Peer.Routers, &Router{
+			V6: v6,
+			V4: v4,
+			Config: &SessionConfig{
+				BGPSecret: secret,
+			},
+		})
+	}
+
+	res := make([]*PeerConfiguration, len(resM))
+	i := 0
+	for _, pc := range resM {
+		res[i] = pc
+		i += 1
+	}
+
+	return res, nil
+}
diff --git a/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.down.sql b/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.down.sql
new file mode 100644
index 0000000..a768e04
--- /dev/null
+++ b/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.down.sql
@@ -0,0 +1,3 @@
+set sql_safe_updates=false;
+ALTER TABLE peer_pgp_keys DROP COLUMN state;
+set sql_safe_updates=true;
diff --git a/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.up.sql b/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.up.sql
new file mode 100644
index 0000000..e7d7fd4
--- /dev/null
+++ b/bgpwtf/cccampix/verifier/model/migrations/1565806867_pgp_key_state.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE peer_pgp_keys ADD COLUMN state STRING check ( state = 'unchecked' or state = 'unknown' or state = 'known' ) NOT NULL DEFAULT 'unchecked';
+ALTER TABLE peer_pgp_keys ALTER COLUMN state DROP DEFAULT;
diff --git a/bgpwtf/cccampix/verifier/model/model.go b/bgpwtf/cccampix/verifier/model/model.go
index b9b81c9..53294dd 100644
--- a/bgpwtf/cccampix/verifier/model/model.go
+++ b/bgpwtf/cccampix/verifier/model/model.go
@@ -23,40 +23,48 @@
 	GetPeeringDBPeer(ctx context.Context, asn int64) (*pb.PeeringDBMember, error)
 
 	GetCheckablePeers(ctx context.Context) ([]*Peer, error)
-	SubmitPeerCheckResults(ctx context.Context, res []*PeerCheckResult) error
+	SubmitPeerCheckResults(ctx context.Context, checkName string, res []*PeerCheckResult) error
 	GetPeerCheckResults(ctx context.Context, asn []int64) ([]*PeerCheckResult, error)
 
 	UpdatePGPKey(ctx context.Context, key *PeerPGPKey) error
+	GetPGPKeysRequiringAttention(ctx context.Context) ([]*PeerPGPKey, error)
+	ValidatePGPKeys(ctx context.Context, positive, negative []string) error
+	GetPeerPGPKey(ctx context.Context, asn int64) (*PeerPGPKey, error)
 
 	ConfigureMissingSessions(ctx context.Context, gen func() SessionConfig) error
 
+	GetPeerConfiguration(ctx context.Context) ([]*PeerConfiguration, error)
+
 	UpdateAllowedPrefixes(ctx context.Context, asn int64, prefixes []*AllowedPrefix) error
 	GetAllowedPrefixes(ctx context.Context, asn int64) ([]*AllowedPrefix, error)
 }
 
-type stringer struct {
+type Router struct {
+	V6     net.IP
+	V4     net.IP
+	Config *SessionConfig
 }
 
-func (s *stringer) String() string {
-	if s == nil {
+func (p *Router) String() string {
+	if p == nil {
 		return "<nil>"
 	}
-	return fmt.Sprintf("%+v", *s)
-}
-
-type Router struct {
-	stringer
-	V6 net.IP
-	V4 net.IP
+	return fmt.Sprintf("%+v", *p)
 }
 
 type Peer struct {
-	stringer
 	ASN     int64
 	Name    string
 	Routers []*Router
 }
 
+func (p *Peer) String() string {
+	if p == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("%+v", *p)
+}
+
 type PeerCheckStatus int
 
 const (
@@ -82,15 +90,41 @@
 }
 
 type PeerPGPKey struct {
-	stringer
 	PeerASN     int64
 	Fingerprint string
+	State       string
+}
+
+func (p *PeerPGPKey) String() string {
+	if p == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("%+v", *p)
 }
 
 type SessionConfig struct {
 	BGPSecret string
 }
 
+func (p *SessionConfig) String() string {
+	if p == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("%+v", *p)
+}
+
+type PeerConfiguration struct {
+	Peer Peer
+	Key  PeerPGPKey
+}
+
+func (p *PeerConfiguration) String() string {
+	if p == nil {
+		return "<nil>"
+	}
+	return fmt.Sprintf("%+v", *p)
+}
+
 type AllowedPrefix struct {
 	Prefix    net.IPNet
 	MaxLength int64
diff --git a/bgpwtf/cccampix/verifier/model/pgp.go b/bgpwtf/cccampix/verifier/model/pgp.go
index a76186e..9292c45 100644
--- a/bgpwtf/cccampix/verifier/model/pgp.go
+++ b/bgpwtf/cccampix/verifier/model/pgp.go
@@ -2,22 +2,26 @@
 
 import (
 	"context"
+	"database/sql"
 	"fmt"
+	"strconv"
 	"time"
 )
 
 func (s *sqlModel) UpdatePGPKey(ctx context.Context, key *PeerPGPKey) error {
 	q := `
 		INSERT INTO peer_pgp_keys
-			(peer_id, fingerprint, time_created)
+			(peer_id, fingerprint, time_created, state)
 		SELECT
-			peers.id, :fingerprint, :time_created
+			peers.id, :fingerprint, :time_created, 'unchecked'
 		FROM peers
 		WHERE peers.asn = :asn
 		ON CONFLICT (peer_id)
 		DO UPDATE SET
 			fingerprint = :fingerprint,
-			time_created = :time_created
+			time_created = :time_created,
+			state = 'unchecked'
+		WHERE peer_pgp_keys.fingerprint != excluded.fingerprint
 	`
 	data := &sqlPeerPGPKey{
 		Fingerprint: key.Fingerprint,
@@ -29,3 +33,105 @@
 	}
 	return nil
 }
+
+func (s *sqlModel) GetPGPKeysRequiringAttention(ctx context.Context) ([]*PeerPGPKey, error) {
+	q := `
+		SELECT
+			peer_pgp_keys.fingerprint "fingerprint",
+			peer_pgp_keys.state "state",
+			peers.asn "asn"
+		FROM peer_pgp_keys
+		LEFT JOIN peers
+		ON peers.id = peer_pgp_keys.peer_id
+		WHERE
+			peer_pgp_keys.state = 'unchecked'
+		OR
+			peer_pgp_keys.state = 'known'
+		OR (
+			peer_pgp_keys.state = 'unknown' AND
+			peer_pgp_keys.time_created > $1
+		)
+	`
+
+	data := []sqlPeerPGPKey{}
+	timestamp := time.Now().Add(-time.Hour).UnixNano()
+	if err := s.db.SelectContext(ctx, &data, q, timestamp); err != nil {
+		return nil, fmt.Errorf("SELECT peer_pgp_keys: %v", err)
+	}
+
+	res := make([]*PeerPGPKey, len(data))
+	for i, datum := range data {
+		asn, err := strconv.ParseInt(datum.ASN, 10, 64)
+		if err != nil {
+			return nil, fmt.Errorf("data corruption: peer_pgp_keys as ASN %q", datum.ASN)
+		}
+		res[i] = &PeerPGPKey{
+			Fingerprint: datum.Fingerprint,
+			State:       datum.State,
+			PeerASN:     asn,
+		}
+	}
+
+	return res, nil
+}
+
+func (s *sqlModel) ValidatePGPKeys(ctx context.Context, positive, negative []string) error {
+	tx := s.db.MustBeginTx(ctx, &sql.TxOptions{})
+	defer tx.Rollback()
+
+	timestamp := time.Now().UnixNano()
+
+	for _, p := range positive {
+		q := `
+			UPDATE
+				peer_pgp_keys
+			SET
+				state = 'known',
+				time_created = $2
+			WHERE
+				fingerprint = $1	
+		`
+
+		if _, err := tx.ExecContext(ctx, q, p, timestamp); err != nil {
+			return fmt.Errorf("UPDATE peer_pgp_keys: %v", err)
+		}
+	}
+
+	for _, n := range negative {
+		q := `
+			UPDATE
+				peer_pgp_keys
+			SET
+				state = 'unknown',
+				time_created = $2
+			WHERE
+				fingerprint = $1	
+		`
+
+		if _, err := tx.ExecContext(ctx, q, n, timestamp); err != nil {
+			return fmt.Errorf("UPDATE peer_pgp_keys: %v", err)
+		}
+	}
+
+	return tx.Commit()
+}
+
+func (s *sqlModel) GetPeerPGPKey(ctx context.Context, asn int64) (*PeerPGPKey, error) {
+	q := `
+		SELECT peer_pgp_keys.fingerprint
+		FROM peer_pgp_keys
+		LEFT JOIN peers
+		ON peers.id = peer_pgp_keys.peer_id
+		WHERE peers.asn = $1
+	`
+	data := []*PeerPGPKey{}
+	if err := s.db.SelectContext(ctx, &data, q, asn); err != nil {
+		return nil, fmt.Errorf("SELECT peer_pgp_keys: %v", err)
+	}
+
+	if len(data) != 1 {
+		return nil, fmt.Errorf("wrong number of peer_pgp_keys (%d)", len(data))
+	}
+
+	return data[0], nil
+}
diff --git a/bgpwtf/cccampix/verifier/model/schema.go b/bgpwtf/cccampix/verifier/model/schema.go
index 093ecc8..1a483c2 100644
--- a/bgpwtf/cccampix/verifier/model/schema.go
+++ b/bgpwtf/cccampix/verifier/model/schema.go
@@ -38,6 +38,7 @@
 	PeerID      string `db:"peer_id"`
 	Fingerprint string `db:"fingerprint"`
 	TimeCreated int64  `db:"time_created"`
+	State       string `db:"state"`
 
 	// Fake, used by app logic.
 	ASN string `db:"asn"`
diff --git a/bgpwtf/cccampix/verifier/model/submit_checks.go b/bgpwtf/cccampix/verifier/model/submit_checks.go
index 79e2a84..0586766 100644
--- a/bgpwtf/cccampix/verifier/model/submit_checks.go
+++ b/bgpwtf/cccampix/verifier/model/submit_checks.go
@@ -8,15 +8,21 @@
 	"github.com/golang/glog"
 )
 
-func (s *sqlModel) SubmitPeerCheckResults(ctx context.Context, res []*PeerCheckResult) error {
+func (s *sqlModel) SubmitPeerCheckResults(ctx context.Context, checkName string, res []*PeerCheckResult) error {
 	tx := s.db.MustBeginTx(ctx, &sql.TxOptions{})
 	defer tx.Rollback()
 
+	glog.Infof("SubmitPeerCheckResults:")
+	for _, r := range res {
+		glog.Infof(" - %+v", *r)
+	}
+
 	q := `
 		UPDATE peer_checks
 		SET delete = true
+		WHERE check_name = $1
 	`
-	if _, err := tx.ExecContext(ctx, q); err != nil {
+	if _, err := tx.ExecContext(ctx, q, checkName); err != nil {
 		return fmt.Errorf("UPDATE for deletion peer_checks: %v", err)
 	}
 
@@ -64,8 +70,9 @@
 	q = `
 		DELETE FROM peer_checks
 		WHERE delete = true
+		AND check_name = $1
 	`
-	if _, err := tx.ExecContext(ctx, q); err != nil {
+	if _, err := tx.ExecContext(ctx, q, checkName); err != nil {
 		return fmt.Errorf("DELETE FROM peer_checks: %v", err)
 	}