77use super :: * ;
88
99use crate :: Nexus ;
10+ use crucible_pantry_client:: types:: Error as CruciblePantryClientError ;
1011use crucible_pantry_client:: types:: VolumeConstructionRequest ;
12+ use internal_dns_types:: names:: ServiceName ;
1113use nexus_db_queries:: authz;
1214use nexus_db_queries:: context:: OpContext ;
1315use nexus_db_queries:: db;
1416use nexus_db_queries:: db:: lookup:: LookupPath ;
1517use omicron_common:: api:: external:: Error ;
16- use omicron_common:: retry_until_known_result;
18+ use omicron_common:: progenitor_operation_retry:: ProgenitorOperationRetry ;
19+ use omicron_common:: progenitor_operation_retry:: ProgenitorOperationRetryError ;
1720use slog:: Logger ;
1821use slog_error_chain:: InlineErrorChain ;
1922use std:: net:: SocketAddrV6 ;
@@ -26,7 +29,7 @@ pub(crate) use pantry_pool::PooledPantryClient;
2629// Common Pantry operations
2730
2831pub ( crate ) async fn get_pantry_address (
29- nexus : & Arc < Nexus > ,
32+ nexus : & Nexus ,
3033) -> Result < SocketAddrV6 , ActionError > {
3134 let client = nexus. pantry_connection_pool ( ) . claim ( ) . await . map_err ( |e| {
3235 ActionError :: action_failed ( format ! (
@@ -37,10 +40,42 @@ pub(crate) async fn get_pantry_address(
3740 Ok ( client. address ( ) )
3841}
3942
43+ // Helper function for attach/detach below: we retry as long as the pantry isn't
44+ // gone, and we detect "gone" by seeing whether the pantry address we've chosen
45+ // is still present when we resolve all the crucible pantry records in DNS.
46+ //
47+ // This function never returns an error because it's expected to be used with
48+ // `ProgenitorOperationRetry`, which treats an error in the "gone check" as a
49+ // fatal error. We don't want to amplify failures: if something is wrong with
50+ // DNS, we can't go back and choose another pantry anyway, so we'll just keep
51+ // retrying until DNS comes back. All that to say: a failure to resolve DNS is
52+ // treated as "the pantry is not gone".
53+ pub ( super ) async fn is_pantry_gone (
54+ nexus : & Nexus ,
55+ pantry_address : SocketAddrV6 ,
56+ log : & Logger ,
57+ ) -> bool {
58+ let all_pantry_dns_entries = match nexus
59+ . resolver ( )
60+ . lookup_all_socket_v6 ( ServiceName :: CruciblePantry )
61+ . await
62+ {
63+ Ok ( entries) => entries,
64+ Err ( err) => {
65+ warn ! (
66+ log, "Failed to query DNS for Crucible pantry" ;
67+ InlineErrorChain :: new( & err) ,
68+ ) ;
69+ return false ;
70+ }
71+ } ;
72+ !all_pantry_dns_entries. contains ( & pantry_address)
73+ }
74+
4075pub ( crate ) async fn call_pantry_attach_for_disk (
4176 log : & slog:: Logger ,
4277 opctx : & OpContext ,
43- nexus : & Arc < Nexus > ,
78+ nexus : & Nexus ,
4479 disk_id : Uuid ,
4580 pantry_address : SocketAddrV6 ,
4681) -> Result < ( ) , ActionError > {
@@ -82,37 +117,45 @@ pub(crate) async fn call_pantry_attach_for_disk(
82117 volume_construction_request,
83118 } ;
84119
85- retry_until_known_result ( log, || async {
86- client. attach ( & disk_id. to_string ( ) , & attach_request) . await
87- } )
88- . await
89- . map_err ( |e| {
90- ActionError :: action_failed ( format ! ( "pantry attach failed with {:?}" , e) )
91- } ) ?;
120+ let attach_operation =
121+ || async { client. attach ( & disk_id. to_string ( ) , & attach_request) . await } ;
122+ let gone_check =
123+ || async { Ok ( is_pantry_gone ( nexus, pantry_address, log) . await ) } ;
124+
125+ ProgenitorOperationRetry :: new ( attach_operation, gone_check)
126+ . run ( log)
127+ . await
128+ . map_err ( |e| {
129+ ActionError :: action_failed ( format ! (
130+ "pantry attach failed: {}" ,
131+ InlineErrorChain :: new( & e)
132+ ) )
133+ } ) ?;
92134
93135 Ok ( ( ) )
94136}
95137
96138pub ( crate ) async fn call_pantry_detach_for_disk (
139+ nexus : & Nexus ,
97140 log : & slog:: Logger ,
98141 disk_id : Uuid ,
99142 pantry_address : SocketAddrV6 ,
100- ) -> Result < ( ) , ActionError > {
143+ ) -> Result < ( ) , ProgenitorOperationRetryError < CruciblePantryClientError > > {
101144 let endpoint = format ! ( "http://{}" , pantry_address) ;
102145
103146 info ! ( log, "sending detach for disk {disk_id} to endpoint {endpoint}" ) ;
104147
105148 let client = crucible_pantry_client:: Client :: new ( & endpoint) ;
106149
107- retry_until_known_result ( log, || async {
108- client. detach ( & disk_id. to_string ( ) ) . await
109- } )
110- . await
111- . map_err ( |e| {
112- ActionError :: action_failed ( format ! ( "pantry detach failed with {:?}" , e) )
113- } ) ?;
150+ let detach_operation =
151+ || async { client. detach ( & disk_id. to_string ( ) ) . await } ;
152+ let gone_check =
153+ || async { Ok ( is_pantry_gone ( nexus, pantry_address, log) . await ) } ;
114154
115- Ok ( ( ) )
155+ ProgenitorOperationRetry :: new ( detach_operation, gone_check)
156+ . run ( log)
157+ . await
158+ . map ( |_response| ( ) )
116159}
117160
118161pub ( crate ) fn find_only_new_region (
0 commit comments