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;