c/bmaas/bmdb: implement OS installation flow

This adds two new tags: OSInstallationRequest and
OSInstallationResponse. It also implements interacting with these tags
from the agent side.

This doesn't yet implement any admin/user-facing API to actually request
OS installation, for now we just exercise this in tests.

Change-Id: I2e31a8369a3a8670bb92bcacfb8231a0d5e1b9fd
Reviewed-on: https://review.monogon.dev/c/monogon/+/1011
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/bmaas/bmdb/model/queries.sql b/cloud/bmaas/bmdb/model/queries.sql
index 4e32cff..8f40aac 100644
--- a/cloud/bmaas/bmdb/model/queries.sql
+++ b/cloud/bmaas/bmdb/model/queries.sql
@@ -42,8 +42,6 @@
   AND session_id = $2
   AND process = $3;
 
--- Example tag processing queries follow.
-
 -- name: MachineAddProvided :exec
 INSERT INTO machine_provided (
     machine_id, provider, provider_id
@@ -79,6 +77,25 @@
     hardware_report_raw = $2
 ;
 
+-- name: MachineSetOSInstallationRequest :exec
+INSERT INTO machine_os_installation_request (
+    machine_id, generation, os_installation_request_raw
+) VALUES (
+    $1, $2, $3
+) ON CONFLICT (machine_id) DO UPDATE SET
+    generation = $2,
+    os_installation_request_raw = $3
+;
+
+-- name: MachineSetOSInstallationReport :exec
+INSERT INTO machine_os_installation_report (
+    machine_id, generation
+) VALUES (
+    $1, $2
+) ON CONFLICT (machine_id) DO UPDATE SET
+    generation = $2
+;
+
 -- name: GetMachinesForAgentStart :many
 -- Get machines that need agent installed for the first time. Machine can be
 -- assumed to be 'new', with no previous attempts or failures.
@@ -126,6 +143,25 @@
   AND work.machine_id IS NULL
 LIMIT $1;
 
+-- name: GetExactMachineForOSInstallation :many
+SELECT
+    machine_os_installation_request.*
+FROM machines
+LEFT JOIN machine_os_installation_request ON machines.machine_id = machine_os_installation_request.machine_id
+LEFT JOIN machine_os_installation_report ON machines.machine_id = machine_os_installation_report.machine_id
+WHERE
+    -- We are only interested in one concrete machine.
+    machines.machine_id = $1
+    -- We must have an installation request.
+    AND machine_os_installation_request.machine_id IS NOT NULL
+    -- And we either must have no installation report, or the installation
+    -- report's generation must not match the installation request's generation.
+    AND (
+        machine_os_installation_report.machine_id IS NULL
+        OR machine_os_installation_report.generation != machine_os_installation_request.generation
+    )
+LIMIT $2;
+
 -- name: AuthenticateAgentConnection :many
 SELECT
     machine_agent_started.*
@@ -133,4 +169,4 @@
 INNER JOIN machine_agent_started ON machines.machine_id = machine_agent_started.machine_id
 WHERE
     machines.machine_id = $1
-    AND machine_agent_started.agent_public_key = $2;
\ No newline at end of file
+    AND machine_agent_started.agent_public_key = $2;