Skip to content

Commit 27a4365

Browse files
Update InsertVpcSubnetQuery IP Block conflict detection logic (#6880)
Updates the logic to detect an IP Block collision when attempting to add a new VPC Subnet. Initial approach was to call `inet_contains_or_equals()` twice (switching order of arguments to ensure the test is run bidirectionally), which did work. However, Ben suggested we instead use `&&` to do the testing since it works bidirectionally and reduces the amount of function/operator calls in the query. The first commit in this series is an addition to the VPC Subnet insertion tests, which fail (correctly) without the subsequent commits that address the bad collision detection logic. Fixes: #6870 --------- Signed-off-by: Trey Aspelund <[email protected]> Co-authored-by: Levon Tarver <[email protected]>
1 parent b034821 commit 27a4365

File tree

1 file changed

+94
-13
lines changed

1 file changed

+94
-13
lines changed

nexus/db-queries/src/db/queries/vpc_subnet.rs

+94-13
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use uuid::Uuid;
4747
/// -- we're trying to cacth.
4848
/// CAST(
4949
/// IF(
50-
/// inet_contains_or_equals(ipv4_block, <ipv4_block>),
50+
/// (<ipv4_block> && ipv4_block),
5151
/// 'ipv4',
5252
/// 'ipv6'
5353
/// )
@@ -60,8 +60,8 @@ use uuid::Uuid;
6060
/// time_deleted IS NULL AND
6161
/// id != <id> AND
6262
/// (
63-
/// inet_contains_or_equals(ipv4_block, <ipv4_block>) OR
64-
/// inet_contains_or_equals(ipv6_block, <ipv6_block>)
63+
/// (ipv4_block && <ipv4_block>) OR
64+
/// (ipv6_block && <ipv6_block>)
6565
/// )
6666
/// )
6767
/// INSERT INTO
@@ -104,9 +104,9 @@ impl QueryFragment<Pg> for InsertVpcSubnetQuery {
104104
&'a self,
105105
mut out: AstPass<'_, 'a, Pg>,
106106
) -> diesel::QueryResult<()> {
107-
out.push_sql("WITH overlap AS MATERIALIZED (SELECT CAST(IF(inet_contains_or_equals(");
107+
out.push_sql("WITH overlap AS MATERIALIZED (SELECT CAST(IF((");
108108
out.push_identifier(dsl::ipv4_block::NAME)?;
109-
out.push_sql(", ");
109+
out.push_sql(" && ");
110110
out.push_bind_param::<sql_types::Inet, _>(&self.ipv4_block)?;
111111
out.push_sql("), ");
112112
out.push_bind_param::<sql_types::Text, _>(
@@ -128,13 +128,13 @@ impl QueryFragment<Pg> for InsertVpcSubnetQuery {
128128
out.push_identifier(dsl::id::NAME)?;
129129
out.push_sql(" != ");
130130
out.push_bind_param::<sql_types::Uuid, Uuid>(&self.subnet.identity.id)?;
131-
out.push_sql(" AND (inet_contains_or_equals(");
131+
out.push_sql(" AND ((");
132132
out.push_identifier(dsl::ipv4_block::NAME)?;
133-
out.push_sql(", ");
133+
out.push_sql(" && ");
134134
out.push_bind_param::<sql_types::Inet, IpNetwork>(&self.ipv4_block)?;
135-
out.push_sql(") OR inet_contains_or_equals(");
135+
out.push_sql(") OR (");
136136
out.push_identifier(dsl::ipv6_block::NAME)?;
137-
out.push_sql(", ");
137+
out.push_sql(" && ");
138138
out.push_bind_param::<sql_types::Inet, IpNetwork>(&self.ipv6_block)?;
139139

140140
out.push_sql("))) INSERT INTO ");
@@ -331,8 +331,14 @@ mod test {
331331
};
332332
let ipv4_block = "172.30.0.0/22".parse().unwrap();
333333
let other_ipv4_block = "172.31.0.0/22".parse().unwrap();
334+
let overlapping_ipv4_block_longer = "172.30.0.0/21".parse().unwrap();
335+
let overlapping_ipv4_block_shorter = "172.30.0.0/23".parse().unwrap();
334336
let ipv6_block = "fd12:3456:7890::/64".parse().unwrap();
335337
let other_ipv6_block = "fd00::/64".parse().unwrap();
338+
let overlapping_ipv6_block_longer =
339+
"fd12:3456:7890::/60".parse().unwrap();
340+
let overlapping_ipv6_block_shorter =
341+
"fd12:3456:7890::/68".parse().unwrap();
336342
let name = "a-name".to_string().try_into().unwrap();
337343
let other_name = "b-name".to_string().try_into().unwrap();
338344
let description = "some description".to_string();
@@ -404,6 +410,81 @@ mod test {
404410
"Should be able to insert a VPC Subnet with the same ranges in a different VPC",
405411
);
406412

413+
// We shouldn't be able to insert a subnet if the IPv4 or IPv6 blocks overlap.
414+
// Explicitly test for different CIDR masks with the same network address
415+
let new_row = VpcSubnet::new(
416+
other_other_subnet_id,
417+
vpc_id,
418+
make_id(&other_name, &description),
419+
overlapping_ipv4_block_longer,
420+
other_ipv6_block,
421+
);
422+
let err = db_datastore.vpc_create_subnet_raw(new_row).await.expect_err(
423+
"Should not be able to insert VPC Subnet with \
424+
overlapping IPv4 range {overlapping_ipv4_block_longer}",
425+
);
426+
assert_eq!(
427+
err,
428+
InsertVpcSubnetError::OverlappingIpRange(
429+
overlapping_ipv4_block_longer.into()
430+
),
431+
"InsertError variant should indicate an IP block overlaps"
432+
);
433+
let new_row = VpcSubnet::new(
434+
other_other_subnet_id,
435+
vpc_id,
436+
make_id(&other_name, &description),
437+
overlapping_ipv4_block_shorter,
438+
other_ipv6_block,
439+
);
440+
let err = db_datastore.vpc_create_subnet_raw(new_row).await.expect_err(
441+
"Should not be able to insert VPC Subnet with \
442+
overlapping IPv4 range {overlapping_ipv4_block_shorter}",
443+
);
444+
assert_eq!(
445+
err,
446+
InsertVpcSubnetError::OverlappingIpRange(
447+
overlapping_ipv4_block_shorter.into()
448+
),
449+
"InsertError variant should indicate an IP block overlaps"
450+
);
451+
let new_row = VpcSubnet::new(
452+
other_other_subnet_id,
453+
vpc_id,
454+
make_id(&other_name, &description),
455+
other_ipv4_block,
456+
overlapping_ipv6_block_longer,
457+
);
458+
let err = db_datastore.vpc_create_subnet_raw(new_row).await.expect_err(
459+
"Should not be able to insert VPC Subnet with \
460+
overlapping IPv6 range {overlapping_ipv6_block_longer}",
461+
);
462+
assert_eq!(
463+
err,
464+
InsertVpcSubnetError::OverlappingIpRange(
465+
overlapping_ipv6_block_longer.into()
466+
),
467+
"InsertError variant should indicate an IP block overlaps"
468+
);
469+
let new_row = VpcSubnet::new(
470+
other_other_subnet_id,
471+
vpc_id,
472+
make_id(&other_name, &description),
473+
other_ipv4_block,
474+
overlapping_ipv6_block_shorter,
475+
);
476+
let err = db_datastore.vpc_create_subnet_raw(new_row).await.expect_err(
477+
"Should not be able to insert VPC Subnet with \
478+
overlapping IPv6 range {overlapping_ipv6_block_shorter}",
479+
);
480+
assert_eq!(
481+
err,
482+
InsertVpcSubnetError::OverlappingIpRange(
483+
overlapping_ipv6_block_shorter.into()
484+
),
485+
"InsertError variant should indicate an IP block overlaps"
486+
);
487+
407488
// We shouldn't be able to insert a subnet if we change only the
408489
// IPv4 or IPv6 block. They must _both_ be non-overlapping.
409490
let new_row = VpcSubnet::new(
@@ -413,10 +494,10 @@ mod test {
413494
other_ipv4_block,
414495
ipv6_block,
415496
);
416-
let err = db_datastore
417-
.vpc_create_subnet_raw(new_row)
418-
.await
419-
.expect_err("Should not be able to insert VPC Subnet with overlapping IPv6 range");
497+
let err = db_datastore.vpc_create_subnet_raw(new_row).await.expect_err(
498+
"Should not be able to insert VPC Subnet with \
499+
overlapping IPv6 range",
500+
);
420501
assert_eq!(
421502
err,
422503
InsertVpcSubnetError::OverlappingIpRange(ipv6_block.into()),

0 commit comments

Comments
 (0)