@@ -199,3 +199,170 @@ pub async fn get_all_records(
199199 . await
200200 . map_err ( |e| DatabaseError :: query ( query, e) )
201201}
202+
203+ #[ cfg( test) ]
204+ mod tests {
205+ use carbide_uuid:: instance:: InstanceId ;
206+ use carbide_uuid:: network:: NetworkSegmentId ;
207+ use carbide_uuid:: vpc:: VpcId ;
208+ use model:: dns:: NewDomain ;
209+
210+ use super :: find_record;
211+ use crate :: dns:: domain;
212+
213+ /// Seed a machine, an instance on it, a forward zone, and a network segment of
214+ /// `segment_type` whose forward zone is that domain. Returns the instance and
215+ /// segment so a caller can attach addresses and look them up.
216+ async fn seed_instance_segment (
217+ conn : & mut sqlx:: PgConnection ,
218+ zone : & str ,
219+ segment_type : & str ,
220+ ) -> ( InstanceId , NetworkSegmentId , VpcId ) {
221+ let zone_domain = domain:: persist ( NewDomain :: new ( zone. to_string ( ) ) , conn)
222+ . await
223+ . unwrap ( ) ;
224+ let vpc_id: VpcId =
225+ sqlx:: query_scalar ( "INSERT INTO vpcs (name, version) VALUES ($1, $2) RETURNING id" )
226+ . bind ( "vpc-2408" )
227+ . bind ( "1" )
228+ . fetch_one ( & mut * conn)
229+ . await
230+ . unwrap ( ) ;
231+ sqlx:: query ( "INSERT INTO machines (id, dpf) VALUES ($1, '{}'::jsonb)" )
232+ . bind ( "test-machine-2408" )
233+ . execute ( & mut * conn)
234+ . await
235+ . unwrap ( ) ;
236+ let instance_id: InstanceId =
237+ sqlx:: query_scalar ( "INSERT INTO instances (machine_id) VALUES ($1) RETURNING id" )
238+ . bind ( "test-machine-2408" )
239+ . fetch_one ( & mut * conn)
240+ . await
241+ . unwrap ( ) ;
242+ let segment_id: NetworkSegmentId = sqlx:: query_scalar (
243+ "INSERT INTO network_segments (name, version, network_segment_type, subdomain_id, vpc_id)
244+ VALUES ($1, $2, $3::network_segment_type_t, $4, $5) RETURNING id" ,
245+ )
246+ . bind ( "seg-2408" )
247+ . bind ( "1" )
248+ . bind ( segment_type)
249+ . bind ( zone_domain. id )
250+ . bind ( vpc_id)
251+ . fetch_one ( & mut * conn)
252+ . await
253+ . unwrap ( ) ;
254+ ( instance_id, segment_id, vpc_id)
255+ }
256+
257+ async fn add_address (
258+ conn : & mut sqlx:: PgConnection ,
259+ instance_id : InstanceId ,
260+ segment_id : NetworkSegmentId ,
261+ vpc_id : VpcId ,
262+ address : & str ,
263+ prefix : & str ,
264+ ) {
265+ // The allocate path stores the IP-derived hostname; mirror that here so the
266+ // view has a name to publish.
267+ let hostname =
268+ crate :: host_naming:: address_to_hostname ( & address. parse :: < std:: net:: IpAddr > ( ) . unwrap ( ) )
269+ . unwrap ( ) ;
270+ sqlx:: query (
271+ "INSERT INTO instance_addresses (instance_id, address, segment_id, prefix, vpc_id, hostname)
272+ VALUES ($1::uuid, $2::inet, $3::uuid, $4::cidr, $5::uuid, $6)" ,
273+ )
274+ . bind ( instance_id)
275+ . bind ( address)
276+ . bind ( segment_id)
277+ . bind ( prefix)
278+ . bind ( vpc_id)
279+ . bind ( hostname)
280+ . execute ( conn)
281+ . await
282+ . unwrap ( ) ;
283+ }
284+
285+ #[ crate :: sqlx_test]
286+ async fn overlay_instance_addresses_are_served_forward ( pool : sqlx:: PgPool ) {
287+ struct Case {
288+ address : & ' static str ,
289+ prefix : & ' static str ,
290+ q_name : & ' static str ,
291+ q_type : & ' static str ,
292+ }
293+ // One row per address family: the served name is the address in dashed,
294+ // IP-derived form under the segment's forward zone.
295+ let cases = [
296+ Case {
297+ address : "10.1.2.3" ,
298+ prefix : "10.1.2.0/24" ,
299+ q_name : "10-1-2-3.tenant.example.com." ,
300+ q_type : "A" ,
301+ } ,
302+ Case {
303+ address : "2001:db8:abcd::2" ,
304+ prefix : "2001:db8:abcd::/64" ,
305+ q_name : "2001-0db8-abcd-0000-0000-0000-0000-0002.tenant.example.com." ,
306+ q_type : "AAAA" ,
307+ } ,
308+ ] ;
309+
310+ let mut txn = pool. begin ( ) . await . unwrap ( ) ;
311+ let ( instance_id, segment_id, vpc_id) =
312+ seed_instance_segment ( txn. as_mut ( ) , "tenant.example.com" , "tenant" ) . await ;
313+ for case in & cases {
314+ add_address (
315+ txn. as_mut ( ) ,
316+ instance_id,
317+ segment_id,
318+ vpc_id,
319+ case. address ,
320+ case. prefix ,
321+ )
322+ . await ;
323+ }
324+
325+ for case in & cases {
326+ let records = find_record ( txn. as_mut ( ) , case. q_name ) . await . unwrap ( ) ;
327+ assert_eq ! (
328+ records. len( ) ,
329+ 1 ,
330+ "one {} record for {}" ,
331+ case. q_type,
332+ case. address
333+ ) ;
334+ assert_eq ! ( records[ 0 ] . q_type, case. q_type) ;
335+ assert_eq ! (
336+ records[ 0 ] . record. parse:: <std:: net:: IpAddr >( ) . unwrap( ) ,
337+ case. address. parse:: <std:: net:: IpAddr >( ) . unwrap( )
338+ ) ;
339+ }
340+ }
341+
342+ #[ crate :: sqlx_test]
343+ async fn host_inband_instance_addresses_are_not_served_here ( pool : sqlx:: PgPool ) {
344+ // A host_inband instance address *is* the host's own interface address,
345+ // already published by the shortname view -- the instance arm must skip it
346+ // so it is not served twice.
347+ let mut txn = pool. begin ( ) . await . unwrap ( ) ;
348+ let ( instance_id, segment_id, vpc_id) =
349+ seed_instance_segment ( txn. as_mut ( ) , "host.example.com" , "host_inband" ) . await ;
350+ add_address (
351+ txn. as_mut ( ) ,
352+ instance_id,
353+ segment_id,
354+ vpc_id,
355+ "10.9.9.9" ,
356+ "10.9.9.0/24" ,
357+ )
358+ . await ;
359+
360+ let records = find_record ( txn. as_mut ( ) , "10-9-9-9.host.example.com." )
361+ . await
362+ . unwrap ( ) ;
363+ assert ! (
364+ records. is_empty( ) ,
365+ "host_inband addresses are not published by the instance arm"
366+ ) ;
367+ }
368+ }
0 commit comments