Skip to content

Commit

Permalink
VTAdmin: Support for conclude txn
Browse files Browse the repository at this point in the history
Signed-off-by: Noble Mittal <[email protected]>
  • Loading branch information
beingnoble03 committed Sep 24, 2024
1 parent c74db2b commit ff6ac08
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 3 deletions.
22 changes: 22 additions & 0 deletions go/vt/vtadmin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ func (api *API) Handler() http.Handler {
router.HandleFunc("/tablet/{tablet}/stop_replication", httpAPI.Adapt(vtadminhttp.StopReplication)).Name("API.StopReplication").Methods("PUT", "OPTIONS")
router.HandleFunc("/tablet/{tablet}/externally_promoted", httpAPI.Adapt(vtadminhttp.TabletExternallyPromoted)).Name("API.TabletExternallyPromoted").Methods("POST")
router.HandleFunc("/transactions/{cluster_id}/{keyspace}", httpAPI.Adapt(vtadminhttp.GetUnresolvedTransactions)).Name("API.GetUnresolvedTransactions").Methods("GET")
router.HandleFunc("/transaction/{cluster_id}/{dtid}/conclude", httpAPI.Adapt(vtadminhttp.ConcludeTransaction)).Name("API.ConcludeTransaction")
router.HandleFunc("/vschema/{cluster_id}/{keyspace}", httpAPI.Adapt(vtadminhttp.GetVSchema)).Name("API.GetVSchema")
router.HandleFunc("/vschemas", httpAPI.Adapt(vtadminhttp.GetVSchemas)).Name("API.GetVSchemas")
router.HandleFunc("/vtctlds", httpAPI.Adapt(vtadminhttp.GetVtctlds)).Name("API.GetVtctlds")
Expand Down Expand Up @@ -531,6 +532,27 @@ func (api *API) CompleteSchemaMigration(ctx context.Context, req *vtadminpb.Comp
return c.CompleteSchemaMigration(ctx, req.Request)
}

// ConcludeTransaction is part of the vtadminpb.VTAdminServer interface.
func (api *API) ConcludeTransaction(ctx context.Context, req *vtadminpb.ConcludeTransactionRequest) (*vtctldatapb.ConcludeTransactionResponse, error) {
span, ctx := trace.NewSpan(ctx, "API.ConcludeTransaction")
defer span.Finish()

c, err := api.getClusterForRequest(req.ClusterId)
if err != nil {
return nil, err
}

cluster.AnnotateSpan(c, span)

if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.GetAction) {
return nil, nil
}

return c.Vtctld.ConcludeTransaction(ctx, &vtctldatapb.ConcludeTransactionRequest{
Dtid: req.Dtid,
})
}

// CreateKeyspace is part of the vtadminpb.VTAdminServer interface.
func (api *API) CreateKeyspace(ctx context.Context, req *vtadminpb.CreateKeyspaceRequest) (*vtadminpb.CreateKeyspaceResponse, error) {
span, ctx := trace.NewSpan(ctx, "API.CreateKeyspace")
Expand Down
11 changes: 11 additions & 0 deletions go/vt/vtadmin/http/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ func GetUnresolvedTransactions(ctx context.Context, r Request, api *API) *JSONRe

return NewJSONResponse(res, err)
}

func ConcludeTransaction(ctx context.Context, r Request, api *API) *JSONResponse {
vars := r.Vars()

res, err := api.server.ConcludeTransaction(ctx, &vtadminpb.ConcludeTransactionRequest{
ClusterId: vars["cluster_id"],
Dtid: vars["dtid"],
})

return NewJSONResponse(res, err)
}
13 changes: 13 additions & 0 deletions web/vtadmin/src/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,19 @@ export const fetchTransactions = async ({ clusterID, keyspace }: FetchTransactio
return vtctldata.GetUnresolvedTransactionsResponse.create(result);
};

export interface ConcludeTransactionParams {
clusterID: string;
dtid: string;
}

export const concludeTransaction = async ({ clusterID, dtid }: ConcludeTransactionParams) => {
const { result } = await vtfetch(`/api/transaction/${clusterID}/${dtid}/conclude`);
const err = vtctldata.ConcludeTransactionResponse.verify(result);
if (err) throw Error(err);

return vtctldata.ConcludeTransactionResponse.create(result);
};

export const fetchWorkflows = async () => {
const { result } = await vtfetch(`/api/workflows`);

Expand Down
36 changes: 33 additions & 3 deletions web/vtadmin/src/components/routes/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ import { formatTransactionState } from '../../util/transactions';
import { ShardLink } from '../links/ShardLink';
import { formatDateTime, formatRelativeTimeInSeconds } from '../../util/time';
import { orderBy } from 'lodash-es';
import { ReadOnlyGate } from '../ReadOnlyGate';
import TransactionActions from './transactions/TransactionActions';
import { isReadOnlyMode } from '../../util/env';

const COLUMNS = ['ID', 'State', 'Participants', 'Time Created'];
const COLUMNS = ['ID', 'State', 'Participants', 'Time Created', 'Actions'];
const READ_ONLY_COLUMNS = ['ID', 'State', 'Participants', 'Time Created'];

export const Transactions = () => {
useDocumentTitle('In Flight Distributed Transactions');
Expand Down Expand Up @@ -82,6 +86,15 @@ export const Transactions = () => {
{formatRelativeTimeInSeconds(row.time_created)}
</div>
</DataCell>
<ReadOnlyGate>
<DataCell>
<TransactionActions
refetchTransactions={transactionsQuery.refetch}
clusterID={params.clusterID as string}
dtid={row.dtid as string}
/>
</DataCell>
</ReadOnlyGate>
</tr>
);
});
Expand All @@ -94,8 +107,9 @@ export const Transactions = () => {
</WorkspaceHeader>

<ContentContainer>
<div className='flex flex-row gap-1 max-w-[740px]'>
<Select
className="block w-full max-w-[740px]"
className="block grow-1 min-w-[300px]"
disabled={keyspacesQuery.isLoading}
inputClassName="block w-full"
items={keyspaces}
Expand All @@ -109,7 +123,23 @@ export const Transactions = () => {
renderItem={(ks) => `${ks?.keyspace?.name} (${ks?.cluster?.id})`}
selectedItem={selectedKeyspace}
/>
<DataTable columns={COLUMNS} data={transactions} renderRows={renderRows} />
<Select
className="block grow-1 min-w-[300px]"
disabled={keyspacesQuery.isLoading}
inputClassName="block w-full"
items={keyspaces}
label="Keyspace"
onChange={(ks) => setParams({ clusterID: ks?.cluster?.id!, keyspace: ks?.keyspace?.name! })}
placeholder={
keyspacesQuery.isLoading
? 'Loading keyspaces...'
: 'Select a keyspace to view unresolved transactions'
}
renderItem={(ks) => `${ks?.keyspace?.name} (${ks?.cluster?.id})`}
selectedItem={selectedKeyspace}
/>
</div>
<DataTable columns={isReadOnlyMode() ? READ_ONLY_COLUMNS : COLUMNS} data={transactions} renderRows={renderRows} />
<QueryLoadingPlaceholder query={transactionsQuery} />
</ContentContainer>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import { Icon, Icons } from '../../Icon';
import Dialog from '../../dialog/Dialog';
import { UseMutationResult } from 'react-query';

interface TransactionActionProps {
isOpen: boolean;
mutation: UseMutationResult<any, any, any>;
title: string;
confirmText: string;
successText: string;
errorText: string;
loadingText: string;
description?: string;
body?: JSX.Element;
refetchTransactions: Function;
closeDialog: () => void;
}

const TransactionAction: React.FC<TransactionActionProps> = ({
isOpen,
closeDialog,
mutation,
title,
confirmText,
description,
successText,
loadingText,
errorText,
refetchTransactions,
body,
}) => {
const onCloseDialog = () => {
setTimeout(mutation.reset, 500);
closeDialog();
};

const hasRun = mutation.data || mutation.error;
const onConfirm = () => {
mutation.mutate(
{},
{
onSuccess: () => {
refetchTransactions();
},
}
);
};
return (
<Dialog
isOpen={isOpen}
confirmText={hasRun ? 'Close' : confirmText}
cancelText="Cancel"
onConfirm={hasRun ? onCloseDialog : onConfirm}
loadingText={loadingText}
loading={mutation.isLoading}
onCancel={onCloseDialog}
onClose={onCloseDialog}
hideCancel={hasRun}
title={hasRun ? undefined : title}
description={hasRun ? undefined : description}
>
<div className="w-full">
{!hasRun && body}
{mutation.data && !mutation.error && (
<div className="w-full flex flex-col justify-center items-center">
<span className="flex h-12 w-12 relative items-center justify-center">
<Icon className="fill-current text-green-500" icon={Icons.checkSuccess} />
</span>
<div className="text-lg mt-3 font-bold text-center">{successText}</div>
</div>
)}
{mutation.error && (
<div className="w-full flex flex-col justify-center items-center">
<span className="flex h-12 w-12 relative items-center justify-center">
<Icon className="fill-current text-red-500" icon={Icons.alertFail} />
</span>
<div className="text-lg mt-3 font-bold text-center">{errorText}</div>
<div className="text-sm">{mutation.error.message}</div>
</div>
)}
</div>
</Dialog>
);
};

export default TransactionAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useState } from 'react';
import Dropdown from '../../dropdown/Dropdown';
import MenuItem from '../../dropdown/MenuItem';
import { Icons } from '../../Icon';
import TransactionAction from './TransactionAction';
import { useConcludeTransaction } from '../../../hooks/api';

interface TransactionActionsProps {
refetchTransactions: Function;
clusterID: string;
dtid: string;
}

const TransactionActions: React.FC<TransactionActionsProps> = ({ refetchTransactions, clusterID, dtid }) => {
const [currentDialog, setCurrentDialog] = useState<string>('');
const closeDialog = () => setCurrentDialog('');

const concludeTransactionMutation = useConcludeTransaction({ clusterID, dtid });

return (
<div className="w-min inline-block">
<Dropdown dropdownButton={Icons.info}>
<MenuItem onClick={() => setCurrentDialog('Conclude Transaction')}>Conclude Transaction</MenuItem>
</Dropdown>
<TransactionAction
title="Conclude Transaction"
confirmText="Conclude"
loadingText="Concluding"
mutation={concludeTransactionMutation}
successText="Concluded transaction"
errorText={`Error occured while concluding the transaction (ID: ${dtid})`}
closeDialog={closeDialog}
isOpen={currentDialog === 'Conclude Transaction'}
refetchTransactions={refetchTransactions}
body={
<div className="text-sm mt-3">
Conclude the transaction with id: <span className="font-mono bg-gray-300">{dtid}</span>.
</div>
}
/>
</div>
);
};

export default TransactionActions;
13 changes: 13 additions & 0 deletions web/vtadmin/src/hooks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
stopWorkflow,
FetchTransactionsParams,
fetchTransactions,
concludeTransaction,
} from '../api/http';
import { vtadmin as pb, vtctldata } from '../proto/vtadmin';
import { formatAlias } from '../util/tablets';
Expand Down Expand Up @@ -418,6 +419,18 @@ export const useTransactions = (
return useQuery(['transactions', params], () => fetchTransactions(params), { ...options });
};

/**
* useConcludeTransaction is a mutate hook that concludes a transaction.
*/
export const useConcludeTransaction = (
params: Parameters<typeof concludeTransaction>[0],
options?: UseMutationOptions<Awaited<ReturnType<typeof concludeTransaction>>, Error>
) => {
return useMutation<Awaited<ReturnType<typeof concludeTransaction>>, Error>(() => {
return concludeTransaction(params);
}, options);
};

export const useVTExplain = (
params: Parameters<typeof fetchVTExplain>[0],
options?: UseQueryOptions<pb.VTExplainResponse, Error> | undefined
Expand Down

0 comments on commit ff6ac08

Please sign in to comment.