vitess.io/vitess@v0.16.2/web/vtadmin/src/components/routes/shard/Advanced.tsx (about) 1 import React, { useState } from 'react'; 2 import { useParams, Link, useHistory } from 'react-router-dom'; 3 import { 4 useDeleteShard, 5 useEmergencyFailoverShard, 6 useKeyspace, 7 usePlannedFailoverShard, 8 useReloadSchemaShard, 9 useTabletExternallyPromoted, 10 useTablets, 11 useValidateShard, 12 useValidateVersionShard, 13 } from '../../../hooks/api'; 14 import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; 15 import ActionPanel from '../../ActionPanel'; 16 import { success, warn } from '../../Snackbar'; 17 import { UseMutationResult } from 'react-query'; 18 import Toggle from '../../toggle/Toggle'; 19 import { Label } from '../../inputs/Label'; 20 import { TextInput } from '../../TextInput'; 21 import { NumberInput } from '../../NumberInput'; 22 import { Select } from '../../inputs/Select'; 23 import { formatAlias, formatDisplayType } from '../../../util/tablets'; 24 import { logutil, vtadmin, vtctldata } from '../../../proto/vtadmin'; 25 import Dialog from '../../dialog/Dialog'; 26 import EventLogEntry from './EventLogEntry'; 27 import ValidationResults from '../../ValidationResults'; 28 interface RouteParams { 29 clusterID: string; 30 keyspace: string; 31 shard: string; 32 } 33 34 const Advanced: React.FC = () => { 35 const params = useParams<RouteParams>(); 36 const history = useHistory(); 37 38 const shardName = `${params.keyspace}/${params.shard}`; 39 40 useDocumentTitle(`${shardName} (${params.clusterID})`); 41 42 const { data: keyspace, ...kq } = useKeyspace({ clusterID: params.clusterID, name: params.keyspace }); 43 const { data: tablets = [] } = useTablets(); 44 45 // dialog parameters 46 const [failoverDialogIsOpen, setFailoverDialogIsOpen] = useState(false); 47 const [dialogTitle, setDialogTitle] = useState(''); 48 const [dialogDescription, setDialogDescription] = useState(''); 49 const [events, setEvents] = useState<logutil.IEvent[]>([]); 50 51 const tabletsInCluster = tablets.filter( 52 (t) => 53 t.cluster?.id === params.clusterID && 54 t.tablet?.keyspace === params.keyspace && 55 t.tablet?.shard === params.shard 56 ); 57 58 // deleteShard parameters 59 const [evenIfServing, setEvenIfServing] = useState(false); 60 const [recursive, setRecursive] = useState(false); 61 const deleteShardMutation = useDeleteShard( 62 { clusterID: params.clusterID, keyspaceShard: shardName, recursive, evenIfServing }, 63 { 64 onSuccess: (result) => { 65 success(`Successfully deleted shard ${shardName}`, { autoClose: 7000 }); 66 history.push(`/keyspace/${params.keyspace}/${params.clusterID}/shards`); 67 }, 68 onError: (error) => warn(`There was an error deleting shard ${shardName}: ${error}`), 69 } 70 ); 71 72 // reloadSchemaShard parameters 73 const [waitPosition, setWaitPosition] = useState<string | undefined>(undefined); 74 const [includePrimary, setIncludePrimary] = useState(false); 75 const [concurrency, setConcurrency] = useState<number | undefined>(undefined); 76 const reloadSchemaShardMutation = useReloadSchemaShard( 77 { 78 clusterID: params.clusterID, 79 keyspace: params.keyspace, 80 shard: params.shard, 81 includePrimary, 82 waitPosition, 83 concurrency, 84 }, 85 { 86 onSuccess: (result) => { 87 success(`Successfully reloaded shard ${shardName}`, { autoClose: 7000 }); 88 }, 89 onError: (error) => warn(`There was an error reloading shard ${shardName}: ${error}`), 90 } 91 ); 92 93 // externallyReparent parameters 94 const [tablet, setTablet] = useState<vtadmin.Tablet | null>(null); 95 const externallyPromoteMutation = useTabletExternallyPromoted( 96 { alias: formatAlias(tablet?.tablet?.alias) as string, clusterIDs: [params.clusterID] }, 97 { 98 onSuccess: (result) => { 99 success(`Successfully promoted tablet ${formatAlias(tablet?.tablet?.alias)}`, { autoClose: 7000 }); 100 }, 101 onError: (error) => 102 warn(`There was an error promoting tablet ${formatAlias(tablet?.tablet?.alias)}: ${error}`), 103 } 104 ); 105 106 // plannedReparent parameters 107 const [plannedReparentTablet, setPlannedReparentTablet] = useState<vtadmin.Tablet | null>(null); 108 const plannedReparentMutation = usePlannedFailoverShard( 109 { 110 clusterID: params.clusterID, 111 keyspace: params.keyspace, 112 shard: params.shard, 113 new_primary: plannedReparentTablet as vtadmin.Tablet, 114 }, 115 { 116 onSuccess: (result) => { 117 setDialogTitle(`Planned Failover`); 118 setDialogDescription( 119 `Successfully failed over shard ${params.shard} to tablet ${formatAlias( 120 plannedReparentTablet?.tablet?.alias 121 )}.` 122 ); 123 setFailoverDialogIsOpen(true); 124 setEvents(result.events); 125 }, 126 onError: (error) => 127 warn(`There was an error failing over shard ${params.shard} to ${plannedReparentTablet}: ${error}`), 128 } 129 ); 130 131 // emergencyReparent parameters 132 const [emergencyReparentTablet, setEmergencyReparentTablet] = useState<vtadmin.Tablet | null>(null); 133 const emergencyReparentMutation = useEmergencyFailoverShard( 134 { 135 clusterID: params.clusterID, 136 keyspace: params.keyspace, 137 shard: params.shard, 138 new_primary: emergencyReparentTablet as vtadmin.Tablet, 139 }, 140 { 141 onSuccess: (result) => { 142 setDialogTitle(`Emergency Failover`); 143 setDialogDescription( 144 `Successfully failed over ${params.shard} to tablet ${formatAlias( 145 emergencyReparentTablet?.tablet?.alias 146 )}.` 147 ); 148 setFailoverDialogIsOpen(true); 149 setEvents(result.events); 150 }, 151 onError: (error) => 152 warn( 153 `There was an error failing over shard ${params.shard} to ${formatAlias( 154 emergencyReparentTablet?.tablet?.alias 155 )}: ${error}` 156 ), 157 } 158 ); 159 160 // validation dialog parameters 161 const [validateDialogTitle, setValidateDialogTitle] = useState(''); 162 const [validateDialogDescription, setValidateDialogDescription] = useState(''); 163 const onCloseValidateDialog = () => { 164 setValidateDialogIsOpen(false); 165 setValidateDialogTitle(''); 166 setValidateDialogDescription(''); 167 setValidateShardResponse(null); 168 setValidateVersionShardResponse(null); 169 }; 170 171 // validateShard parameters 172 const [pingTablets, setPingTablets] = useState(false); 173 const [validateDialogIsOpen, setValidateDialogIsOpen] = useState(false); 174 const [validateShardResponse, setValidateShardResponse] = useState<vtctldata.ValidateShardResponse | null>(null); 175 176 const validateShardMutation = useValidateShard( 177 { 178 clusterID: params.clusterID, 179 keyspace: params.keyspace, 180 shard: params.shard, 181 pingTablets, 182 }, 183 { 184 onSuccess: (result) => { 185 setValidateDialogTitle('Validate Shard'); 186 setValidateDialogDescription(`Successfully validated ${params.shard}.`); 187 setValidateDialogIsOpen(true); 188 setValidateShardResponse(result); 189 }, 190 onError: (error) => warn(`There was an error validating shard ${params.shard}: ${error}`), 191 } 192 ); 193 194 const [ 195 validateVersionShardResponse, 196 setValidateVersionShardResponse, 197 ] = useState<vtctldata.ValidateVersionShardResponse | null>(null); 198 199 const validateVersionShardMutation = useValidateVersionShard( 200 { 201 clusterID: params.clusterID, 202 keyspace: params.keyspace, 203 shard: params.shard, 204 }, 205 { 206 onSuccess: (result) => { 207 setValidateDialogTitle('Validate Version Shard'); 208 setValidateDialogDescription(`Successfully validated versions on ${params.shard}.`); 209 setValidateDialogIsOpen(true); 210 setValidateShardResponse(result); 211 }, 212 onError: (error) => warn(`There was an error validating versions on shard ${params.shard}: ${error}`), 213 } 214 ); 215 216 if (kq.error) { 217 return ( 218 <div className="items-center flex flex-column text-lg justify-center m-3 text-center max-w-[720px]"> 219 <span className="text-xl">😰</span> 220 <h1>An error occurred</h1> 221 <code>{(kq.error as any).response?.error?.message || kq.error?.message}</code> 222 <p> 223 <Link to="/keyspaces">← All keyspaces</Link> 224 </p> 225 </div> 226 ); 227 } 228 229 return ( 230 <> 231 <Dialog 232 className="min-w-[500px]" 233 isOpen={failoverDialogIsOpen} 234 onClose={() => setFailoverDialogIsOpen(false)} 235 onConfirm={() => setFailoverDialogIsOpen(false)} 236 title={dialogTitle} 237 hideCancel 238 confirmText="Dismiss" 239 > 240 <> 241 <div className="mt-8 mb-4">{dialogDescription}</div> 242 <div className="mb-2 font-bold">Log</div> 243 <div className="bg-gray-100 p-4 overflow-scroll max-h-[200px]"> 244 {events.map((e, i) => ( 245 <EventLogEntry event={e} key={`${i}_event_log`} /> 246 ))} 247 </div> 248 </> 249 </Dialog> 250 <Dialog 251 className="min-w-[500px]" 252 isOpen={validateDialogIsOpen} 253 onClose={onCloseValidateDialog} 254 onConfirm={onCloseValidateDialog} 255 title={validateDialogTitle} 256 hideCancel 257 confirmText="Dismiss" 258 > 259 <> 260 <div className="mt-8 mb-4">{validateDialogDescription}</div> 261 {validateShardResponse && ( 262 <ValidationResults 263 resultsByShard={validateShardResponse || validateVersionShardResponse} 264 shard={shardName} 265 /> 266 )} 267 </> 268 </Dialog> 269 <div className="pt-4"> 270 <div className="my-8"> 271 <h3 className="mb-4">Status</h3> 272 <div> 273 <ActionPanel 274 description={ 275 <> 276 Validates that all nodes reachable from the specified shard{' '} 277 <span className="font-bold">{shardName}</span> are consistent. 278 </> 279 } 280 documentationLink="https://vitess.io/docs/reference/programs/vtctldclient/vtctldclient_validateshard/#vtctldclient-validateshard" 281 loadingText="Validating shard..." 282 loadedText="Validate" 283 mutation={validateShardMutation as UseMutationResult} 284 title="Validate Shard" 285 body={ 286 <> 287 <div className="mt-2"> 288 <div className="flex items-center"> 289 <Toggle 290 className="mr-2" 291 enabled={pingTablets} 292 onChange={() => setPingTablets(!pingTablets)} 293 /> 294 <Label label="Ping Tablets" /> 295 </div> 296 When set, all tablets are pinged during the validation process. 297 </div> 298 </> 299 } 300 /> 301 <ActionPanel 302 description={ 303 <> 304 Validates that the version on the primary matches all of the replicas on shard{' '} 305 <span className="font-bold">{shardName}</span>. 306 </> 307 } 308 documentationLink="https://vitess.io/docs/reference/programs/vtctl/schema-version-permissions/#validateversionshard" 309 loadingText="Validating shard versions..." 310 loadedText="Validate" 311 mutation={validateVersionShardMutation as UseMutationResult} 312 title="Validate Version Shard" 313 /> 314 </div> 315 </div> 316 </div> 317 <div className="pt-4"> 318 <div className="my-8"> 319 <h3 className="mb-4">Reload</h3> 320 <div> 321 <ActionPanel 322 description={ 323 <> 324 Reloads the schema on all tablets in shard{' '} 325 <span className="font-bold">{shardName}</span>. This is done on a best-effort basis. 326 </> 327 } 328 documentationLink="https://vitess.io/docs/reference/programs/vtctldclient/vtctldclient_reloadschemashard/" 329 loadingText="Reloading schema shard..." 330 loadedText="Reload" 331 mutation={reloadSchemaShardMutation as UseMutationResult} 332 title="Reload Schema Shard" 333 body={ 334 <> 335 <p className="text-base"> 336 <strong>Wait Position</strong> <br /> 337 Allows scheduling a schema reload to occur after a given DDL has replicated to 338 this server, by specifying a replication position to wait for. Leave empty to 339 trigger the reload immediately. 340 </p> 341 <div className="w-1/3"> 342 <TextInput 343 value={waitPosition} 344 onChange={(e) => setWaitPosition(e.target.value)} 345 /> 346 </div> 347 <div className="mt-2"> 348 <Label label="Concurrency" /> <br /> 349 Number of tablets to reload in parallel. Set to zero for unbounded concurrency. 350 (Default 10) 351 <div className="w-1/3 mt-4"> 352 <NumberInput 353 value={concurrency} 354 onChange={(e) => setConcurrency(parseInt(e.target.value))} 355 /> 356 </div> 357 </div> 358 <div className="mt-2"> 359 <div className="flex items-center"> 360 <Toggle 361 className="mr-2" 362 enabled={includePrimary} 363 onChange={() => setIncludePrimary(!includePrimary)} 364 /> 365 <Label label="Include Primary" /> 366 </div> 367 When set, also reloads the primary tablet. 368 </div> 369 </> 370 } 371 /> 372 </div> 373 </div> 374 </div> 375 <div className="pt-4"> 376 <div className="my-8"> 377 <h3 className="mb-4">Change</h3> 378 <div> 379 <ActionPanel 380 description={ 381 <> 382 Changes metadata in the topology service to acknowledge a shard primary change 383 performed by an external tool. 384 </> 385 } 386 disabled={!tablet} 387 documentationLink="https://vitess.io/docs/reference/programs/vtctl/shards/#tabletexternallyreparented" 388 loadingText="Failing over..." 389 loadedText="Failover" 390 mutation={externallyPromoteMutation as UseMutationResult} 391 title="External Failover" 392 body={ 393 <> 394 <div className="mt-2"> 395 <div className="flex items-center"> 396 <Select 397 onChange={(t) => setTablet(t as vtadmin.Tablet)} 398 label="Tablet" 399 renderItem={(t: vtadmin.Tablet) => 400 `${formatAlias(t.tablet?.alias)} (${formatDisplayType(t)})` 401 } 402 items={tabletsInCluster} 403 selectedItem={tablet} 404 placeholder="Tablet" 405 description="This chosen tablet will be considered the shard primary (but Vitess won't change the replication setup)." 406 required 407 /> 408 </div> 409 </div> 410 </> 411 } 412 /> 413 <ActionPanel 414 confirmationValue={shardName} 415 description={ 416 <> 417 Delete shard <span className="font-bold">{shardName}</span>. In recursive mode, it 418 also deletes all tablets belonging to the shard. Otherwise, there must be no tablets 419 left in the shard. 420 </> 421 } 422 documentationLink="https://vitess.io/docs/reference/programs/vtctl/shards/#deleteshard" 423 loadingText="Deleting..." 424 loadedText="Delete" 425 mutation={deleteShardMutation as UseMutationResult} 426 title="Delete Shard" 427 body={ 428 <> 429 <div className="mt-2"> 430 <div className="flex items-center"> 431 <Toggle 432 className="mr-2" 433 enabled={evenIfServing} 434 onChange={() => setEvenIfServing(!evenIfServing)} 435 /> 436 <Label label="Even If Serving" /> 437 </div> 438 When set, removes the shard even if it is serving. Use with caution. 439 </div> 440 <div className="mt-2"> 441 <div className="flex items-center"> 442 <Toggle 443 className="mr-2" 444 enabled={recursive} 445 onChange={() => setRecursive(!recursive)} 446 /> 447 <Label label="Recursive" /> 448 </div> 449 When set, also deletes all tablets belonging to the shard. 450 </div> 451 </> 452 } 453 /> 454 </div> 455 </div> 456 </div> 457 <div className="pt-4"> 458 <div className="my-8"> 459 <h3 className="mb-4">Reparent</h3> 460 <div> 461 <ActionPanel 462 description={ 463 <> 464 Reparents the shard to a new primary that can either be explicitly specified, or 465 chosen by Vitess. 466 </> 467 } 468 disabled={!plannedReparentTablet} 469 documentationLink="https://vitess.io/docs/reference/programs/vtctl/shards/#plannedreparentshard" 470 loadingText="Failing over..." 471 loadedText="Failover" 472 mutation={plannedReparentMutation as UseMutationResult} 473 title="Planned Failover" 474 body={ 475 <> 476 <div className="mt-2"> 477 <div className="flex items-center"> 478 <Select 479 required 480 onChange={(t) => setPlannedReparentTablet(t as vtadmin.Tablet)} 481 label="Tablet" 482 items={tabletsInCluster} 483 renderItem={(t: vtadmin.Tablet) => 484 `${formatAlias(t.tablet?.alias)} (${formatDisplayType(t)})` 485 } 486 selectedItem={plannedReparentTablet} 487 placeholder="Tablet" 488 description="This tablet will be the new primary for this shard." 489 /> 490 </div> 491 </div> 492 </> 493 } 494 /> 495 <ActionPanel 496 description={ 497 <> 498 Reparents the shard to the new primary. Assumes the old primary is dead and not 499 responding. 500 </> 501 } 502 disabled={!emergencyReparentTablet} 503 documentationLink="https://vitess.io/docs/reference/programs/vtctl/shards/#emergencyreparentshard" 504 loadingText="Failing over..." 505 loadedText="Failover" 506 mutation={emergencyReparentMutation as UseMutationResult} 507 title="Emergency Failover" 508 body={ 509 <> 510 <div className="mt-2"> 511 <div className="flex items-center"> 512 <Select 513 required 514 onChange={(t) => setEmergencyReparentTablet(t as vtadmin.Tablet)} 515 label="Tablet" 516 items={tabletsInCluster} 517 renderItem={(t: vtadmin.Tablet) => 518 `${formatAlias(t.tablet?.alias)} (${formatDisplayType(t)})` 519 } 520 selectedItem={emergencyReparentTablet} 521 placeholder="Tablet" 522 description="This tablet will be the new primary for this shard." 523 /> 524 </div> 525 </div> 526 </> 527 } 528 /> 529 </div> 530 </div> 531 </div> 532 </> 533 ); 534 }; 535 536 export default Advanced;