Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion app/api/util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,53 @@
*/
import { describe, expect, it, test } from 'vitest'

import { diskCan, genName, instanceCan, parsePortRange, synthesizeData } from './util'
import {
diskCan,
genName,
instanceCan,
parsePortRange,
subscriptionRegex,
synthesizeData,
} from './util'

describe('subscriptionRegex', () => {
it('matches exact class names', () => {
expect(subscriptionRegex('probe').test('probe')).toBe(true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'probe' is not a valid alert class to subscribe to, and the server will reject attempts to subscribe to the probe class with a 400 error. it's not treated as a "real" alert class. i think we shouldn't be testing with it if the API will reject it; we might want to explicitly include that rule in client side validation.

expect(subscriptionRegex('probe').test('probes')).toBe(false)
expect(subscriptionRegex('instance.create').test('instance.create')).toBe(true)
})

it('* matches exactly one segment', () => {
const re = subscriptionRegex('disk.*')
expect(re.test('disk.create')).toBe(true)
expect(re.test('disk.snapshot.create')).toBe(false)
expect(re.test('disk')).toBe(false)
})

it('* can appear in any position', () => {
const re = subscriptionRegex('*.create')
expect(re.test('disk.create')).toBe(true)
expect(re.test('instance.create')).toBe(true)
expect(re.test('instance.ephemeral_ip.create')).toBe(false)
})

it('** matches one or more segments', () => {
const re = subscriptionRegex('hardware.**')
expect(re.test('hardware.power_shelf.psu.insert')).toBe(true)
expect(re.test('hardware.psu')).toBe(true)
expect(re.test('hardware')).toBe(false)

const suffix = subscriptionRegex('**.delete')
expect(suffix.test('project.delete')).toBe(true)
expect(suffix.test('instance.ephemeral_ip.delete')).toBe(true)
expect(suffix.test('delete')).toBe(false)
})

it('does not match substrings within a segment', () => {
expect(subscriptionRegex('instance.**').test('silo.instance_quota.hit')).toBe(false)
expect(subscriptionRegex('disk.*').test('bigdisk.create')).toBe(false)
})
})

describe('parsePortRange', () => {
describe('parses', () => {
Expand Down
16 changes: 16 additions & 0 deletions app/api/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ export const INSTANCE_MAX_RAM_GiB = 1536
export const ALERT_SUBSCRIPTION_REGEX =
/^([a-zA-Z0-9_]+|\*|\*\*)(\.([a-zA-Z0-9_]+|\*|\*\*))*$/

/** A subscription with a `*` or `**` segment, as opposed to an exact class */
export const isGlobPattern = (subscription: string) => subscription.includes('*')

/**
* Convert an alert subscription to a regex matching the class names it covers:
* a `*` segment matches exactly one segment, `**` matches one or more.
* https://github.com/oxidecomputer/omicron/blob/32615a35/nexus/db-model/src/alert_subscription.rs
*/
export function subscriptionRegex(subscription: string) {
const pattern = subscription
.split('.')
.map((seg) => (seg === '**' ? '.+' : seg === '*' ? '[^.]+' : seg))
.join('\\.')
return new RegExp(`^${pattern}$`)
}

export const MIN_DISK_SIZE_GiB = 1
/**
* Disk size limited to 1023 as that's the maximum we can safely allocate right now
Expand Down
Loading
Loading