-
Notifications
You must be signed in to change notification settings - Fork 21
Instance auto restart #2644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Instance auto restart #2644
Changes from 31 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
f66afff
First pass instance auto-restart
benjaminleonard 945b0bb
Merge branch 'main' into instance-auto-restart
benjaminleonard 3be8500
Add comma to settings help text
benjaminleonard 8d40e2f
Add cooldown and add copy tweaks
benjaminleonard bf37c96
Add license
benjaminleonard d81c471
Change none to default in listbox text
benjaminleonard 06400f8
Update mock handler to match omicron
benjaminleonard a11042a
Use `useInterval` hook
benjaminleonard c03afd2
Use `design-system` icon
benjaminleonard 3212357
Text tweak
benjaminleonard 7ada68e
Simplify `helpTextState`
benjaminleonard a599745
`InstanceAutoRestartPolicy` can be undefined
benjaminleonard c773c3a
Merge branch 'main' into instance-auto-restart
benjaminleonard 467b58e
Vitest fixes
benjaminleonard ebe8dc0
Upgrade `@oxide/design-system`
benjaminleonard 9263a5e
merge main
david-crespo 72f3ea3
uncontroversial copy tweaks
david-crespo de849ff
fix restart policy form unset bug
david-crespo 9f9d9db
ts-pattern for nicer exhaustiveness
david-crespo 7e9b771
Merge branch 'main' into instance-auto-restart
benjaminleonard b86cf88
Stronger highlight
benjaminleonard 740fb8b
Stop icon spin
benjaminleonard 9df1189
Add instance popover link state
benjaminleonard fb10a59
merge main (reduce e2e flake)
david-crespo d1a41b1
use instance polling instead of interval
david-crespo cbe9362
also rely on polling on settings tab
david-crespo d9a97c6
bump API client generator for date parsing fix
david-crespo db52d77
format dates for locale with existing helper
david-crespo 2547ecc
take enabled out of settings form
david-crespo e2a231d
handle auto restart stuff in msw at update time
david-crespo 690b788
update mock API to allow policy update any time
david-crespo fad694b
copy tweaks, fill in tests
david-crespo d707200
poll slower for failed instances
david-crespo a3871c2
simplify FormMeta, no Label needed
david-crespo a641b89
fix up polling and popover logic
david-crespo 7cde120
copy and popover state polish, more e2es
david-crespo 6c413e9
instanceAutoRestartingSoon helper
david-crespo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * Copyright Oxide Computer Company | ||
| */ | ||
| import { CloseButton, Popover, PopoverButton, PopoverPanel } from '@headlessui/react' | ||
| import { formatDistanceToNow } from 'date-fns' | ||
| import { type ReactNode } from 'react' | ||
| import { Link } from 'react-router' | ||
| import { match } from 'ts-pattern' | ||
|
|
||
| import { | ||
| AutoRestart12Icon, | ||
| NextArrow12Icon, | ||
| OpenLink12Icon, | ||
| } from '@oxide/design-system/icons/react' | ||
|
|
||
| import type { Instance } from '~/api' | ||
| import { HL } from '~/components/HL' | ||
| import { useInstanceSelector } from '~/hooks/use-params' | ||
| import { Badge } from '~/ui/lib/Badge' | ||
| import { Spinner } from '~/ui/lib/Spinner' | ||
| import { links } from '~/util/links' | ||
| import { pb } from '~/util/path-builder' | ||
|
|
||
| const helpText = { | ||
| enabled: ( | ||
| <> | ||
| The control plane will attempt to automatically restart this instance after entering | ||
| the <HL>failed</HL> state. | ||
| </> | ||
| ), | ||
| disabled: ( | ||
| <> | ||
| The control plane will not attempt to automatically restart it after entering the{' '} | ||
| <HL>failed</HL> state. | ||
| </> | ||
| ), | ||
| never: ( | ||
| <> | ||
| Instance auto-restart policy is set to never. The control plane will not attempt to | ||
| automatically restart it after entering the <HL>failed</HL> state. | ||
| </> | ||
| ), | ||
| starting: ( | ||
| <> | ||
| Instance auto-restart policy is queued to start. The control plane will begin the | ||
| restart process shortly. | ||
| </> | ||
| ), | ||
| } | ||
|
|
||
| export const InstanceAutoRestartPopover = ({ instance }: { instance: Instance }) => { | ||
| const { | ||
| autoRestartCooldownExpiration: cooldownExpiration, | ||
| autoRestartPolicy: policy, | ||
| autoRestartEnabled: enabled, | ||
| } = instance | ||
|
|
||
| const instanceSelector = useInstanceSelector() | ||
| const now = new Date() | ||
| const isQueued = cooldownExpiration && cooldownExpiration < now | ||
|
|
||
| let helpTextState: keyof typeof helpText = 'disabled' | ||
| if (isQueued) helpTextState = 'starting' // Expiration is in the past and queued for restart | ||
| if (policy === 'never') helpTextState = 'never' // Will never auto-restart | ||
| if (enabled) helpTextState = 'enabled' // Restart enabled and cooldown as not expired | ||
|
|
||
| return ( | ||
| <Popover> | ||
| <PopoverButton | ||
| className="group flex h-6 w-6 items-center justify-center rounded border border-default hover:bg-hover" | ||
| aria-label="Auto-restart status" | ||
| > | ||
| <AutoRestart12Icon className="shrink-0" aria-hidden /> | ||
| </PopoverButton> | ||
| <PopoverPanel | ||
| // popover-panel needed for enter animation | ||
| className="popover-panel z-10 w-96 rounded-lg border bg-raise border-secondary elevation-2" | ||
| anchor={{ to: 'bottom start', gap: 12 }} | ||
| > | ||
| <PopoverRow label="Auto Restart"> | ||
| {enabled ? <Badge>Enabled</Badge> : <Badge color="neutral">Disabled</Badge>} | ||
| </PopoverRow> | ||
| <PopoverRow label="Policy"> | ||
| <CloseButton | ||
| as={Link} | ||
| to={pb.instanceSettings(instanceSelector)} | ||
| className="group -m-1 flex w-full items-center justify-between rounded px-1" | ||
| > | ||
| {match(policy) | ||
| .with('never', () => ( | ||
| <Badge color="neutral" variant="solid"> | ||
| never | ||
| </Badge> | ||
| )) | ||
| .with('best_effort', () => <Badge>best effort</Badge>) | ||
| .with(undefined, () => <Badge color="neutral">Default</Badge>) | ||
| .exhaustive()} | ||
| <div className="transition-transform group-hover:translate-x-1"> | ||
| <NextArrow12Icon /> | ||
| </div> | ||
| </CloseButton> | ||
| </PopoverRow> | ||
| {cooldownExpiration && ( | ||
| <PopoverRow label="Cooldown"> | ||
| {isQueued ? ( | ||
| <> | ||
| <Spinner /> Queued for restart… | ||
| </> | ||
| ) : ( | ||
| <div> | ||
| Waiting{' '} | ||
| <span className="text-tertiary"> | ||
| ({formatDistanceToNow(cooldownExpiration)}) | ||
| </span> | ||
| </div> | ||
| )} | ||
| </PopoverRow> | ||
| )} | ||
| <div className="p-3 text-sans-md text-default"> | ||
| <p className="mb-2 pr-4">{helpText[helpTextState]}</p> | ||
| <a href={links.instanceUpdateDocs} className="group"> | ||
| <span className="inline-block max-w-[300px] truncate align-middle"> | ||
| Learn about{' '} | ||
| <span className="group-hover:link-with-underline text-raise"> | ||
| Instance Auto-Restart | ||
| </span> | ||
| </span> | ||
| <OpenLink12Icon className="ml-1 translate-y-[1px] text-secondary" /> | ||
| </a> | ||
| </div> | ||
| </PopoverPanel> | ||
| </Popover> | ||
| ) | ||
| } | ||
|
|
||
| const PopoverRow = ({ label, children }: { label: string; children: ReactNode }) => ( | ||
| <div className="flex h-10 items-center border-b border-b-secondary"> | ||
| <div className="w-32 pl-3 pr-2 text-mono-sm text-tertiary">{label}</div> | ||
| <div className="flex h-10 flex-grow items-center gap-1.5 pr-2 text-sans-md"> | ||
| {children} | ||
| </div> | ||
| </div> | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { filesize } from 'filesize' | |
| import { useId, useMemo, useState } from 'react' | ||
| import { useForm } from 'react-hook-form' | ||
| import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router' | ||
| import { match } from 'ts-pattern' | ||
|
|
||
| import { | ||
| apiQueryClient, | ||
|
|
@@ -24,11 +25,13 @@ import { | |
| INSTANCE_MAX_CPU, | ||
| INSTANCE_MAX_RAM_GiB, | ||
| instanceCan, | ||
| instanceCoolingDown, | ||
| instanceTransitioning, | ||
| } from '~/api/util' | ||
| import { ExternalIps } from '~/components/ExternalIps' | ||
| import { NumberField } from '~/components/form/fields/NumberField' | ||
| import { HL } from '~/components/HL' | ||
| import { InstanceAutoRestartPopover } from '~/components/InstanceAutoRestartPopover' | ||
| import { InstanceDocsPopover } from '~/components/InstanceDocsPopover' | ||
| import { MoreActionsMenu } from '~/components/MoreActionsMenu' | ||
| import { RefreshButton } from '~/components/RefreshButton' | ||
|
|
@@ -106,6 +109,20 @@ InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { | |
|
|
||
| const POLL_INTERVAL = 1000 | ||
|
|
||
| function shouldPoll(instance: Instance) { | ||
| if (instanceTransitioning(instance)) return 'transition' | ||
| if (instanceCoolingDown(instance)) return 'cooldown' | ||
| return null | ||
| } | ||
|
|
||
| const PollingSpinner = () => ( | ||
| <Tooltip content="Auto-refreshing while state changes" delay={150}> | ||
| <button type="button"> | ||
| <Spinner className="ml-2" /> | ||
| </button> | ||
| </Tooltip> | ||
| ) | ||
|
|
||
| export function InstancePage() { | ||
| const instanceSelector = useInstanceSelector() | ||
| const [resizeInstance, setResizeInstance] = useState(false) | ||
|
|
@@ -130,11 +147,11 @@ export function InstancePage() { | |
| }, | ||
| { | ||
| refetchInterval: ({ state: { data: instance } }) => | ||
| instance && instanceTransitioning(instance) ? POLL_INTERVAL : false, | ||
| instance && shouldPoll(instance) ? POLL_INTERVAL : false, | ||
| } | ||
| ) | ||
|
|
||
| const polling = instanceTransitioning(instance) | ||
| const pollReason = shouldPoll(instance) | ||
|
|
||
| const { data: nics } = usePrefetchedApiQuery('instanceNetworkInterfaceList', { | ||
| query: { | ||
|
|
@@ -203,15 +220,13 @@ export function InstancePage() { | |
| <span className="ml-1 text-tertiary"> {memory.unit}</span> | ||
| </PropertiesTable.Row> | ||
| <PropertiesTable.Row label="state"> | ||
| <div className="flex"> | ||
| <div className="flex items-center gap-2"> | ||
| <InstanceStateBadge state={instance.runState} /> | ||
| {polling && ( | ||
| <Tooltip content="Auto-refreshing while state changes" delay={150}> | ||
| <button type="button"> | ||
| <Spinner className="ml-2" /> | ||
| </button> | ||
| </Tooltip> | ||
| )} | ||
| {match(pollReason) | ||
| .with('transition', () => <PollingSpinner />) | ||
| .with('cooldown', () => <InstanceAutoRestartPopover instance={instance} />) | ||
| .with(null, () => null) | ||
| .exhaustive()} | ||
|
||
| </div> | ||
| </PropertiesTable.Row> | ||
| <PropertiesTable.Row label="vpc"> | ||
|
|
@@ -241,6 +256,7 @@ export function InstancePage() { | |
| <Tab to={pb.instanceMetrics(instanceSelector)}>Metrics</Tab> | ||
| <Tab to={pb.instanceNetworking(instanceSelector)}>Networking</Tab> | ||
| <Tab to={pb.instanceConnect(instanceSelector)}>Connect</Tab> | ||
| <Tab to={pb.instanceSettings(instanceSelector)}>Settings</Tab> | ||
| </RouteTabs> | ||
| {resizeInstance && ( | ||
| <ResizeInstanceModal | ||
|
|
@@ -298,7 +314,7 @@ export function ResizeInstanceModal({ | |
| mode: 'onChange', | ||
| }) | ||
|
|
||
| const canResize = instanceCan.update(instance) | ||
| const canResize = instanceCan.resize(instance) | ||
| const willChange = | ||
| form.watch('ncpus') !== instance.ncpus || form.watch('memory') !== instance.memory / GiB | ||
| const isDisabled = !form.formState.isValid || !canResize || !willChange | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍