diff --git a/cypress/integration/distroSettings/general_section.ts b/cypress/integration/distroSettings/general_section.ts index bde620981b..a95a379ff8 100644 --- a/cypress/integration/distroSettings/general_section.ts +++ b/cypress/integration/distroSettings/general_section.ts @@ -39,4 +39,16 @@ describe("general section", () => { save(); cy.validateToast("success"); }); + + describe("container pool distro", () => { + beforeEach(() => { + cy.visit("/distro/ubuntu1604-parent/settings/general"); + }); + + it("warns users that the distro will not be spawned for tasks", () => { + cy.contains( + "Distro is a container pool, so it cannot be spawned for tasks." + ).should("be.visible"); + }); + }); }); diff --git a/cypress/integration/distroSettings/host_section.ts b/cypress/integration/distroSettings/host_section.ts index c3aaddd67b..23c8873eeb 100644 --- a/cypress/integration/distroSettings/host_section.ts +++ b/cypress/integration/distroSettings/host_section.ts @@ -12,6 +12,8 @@ describe("host section", () => { cy.dataCy("maximum-hosts-input").should("not.exist"); cy.dataCy("idle-time-input").should("not.exist"); cy.dataCy("future-fraction-input").should("not.exist"); + cy.dataCy("rounding-rule-select").should("not.exist"); + cy.dataCy("feedback-rule-select").should("not.exist"); }); it("shows an error when selecting an incompatible host communication method", () => { @@ -29,8 +31,6 @@ describe("host section", () => { cy.getInputByLabel("SSH User").type("sudo"); cy.contains("button", "Add SSH option").click(); cy.getInputByLabel("SSH Option").type("BatchMode=yes"); - cy.selectLGOption("Host Allocator Rounding Rule", "Round down"); - cy.selectLGOption("Host Allocator Feedback Rule", "No feedback"); cy.selectLGOption( "Host Overallocation Rule", "Terminate hosts when overallocated" @@ -46,8 +46,6 @@ describe("host section", () => { cy.getInputByLabel("SSH User").clear(); cy.getInputByLabel("SSH User").type("ubuntu"); cy.dataCy("delete-item-button").click(); - cy.selectLGOption("Host Allocator Rounding Rule", "Default"); - cy.selectLGOption("Host Allocator Feedback Rule", "Default"); cy.selectLGOption("Host Overallocation Rule", "Default"); save(); diff --git a/src/pages/distroSettings/Tabs.tsx b/src/pages/distroSettings/Tabs.tsx index 4f11d8e1b0..edf8e07e50 100644 --- a/src/pages/distroSettings/Tabs.tsx +++ b/src/pages/distroSettings/Tabs.tsx @@ -43,7 +43,10 @@ export const DistroSettingsTabs: React.FC = ({ distro }) => { + } /> = ({ distroData }) => { +export const GeneralTab: React.FC = ({ + distroData, + minimumHosts, +}) => { + const { distroId } = useParams(); + const { containerPools } = useSpruceConfig(); + const containerPoolDistros = + containerPools?.pools?.map(({ distro }) => distro) ?? []; + + const isContainerDistro = containerPoolDistros.includes(distroId); + const initialFormState = distroData; - const formSchema = useMemo(() => getFormSchema(), []); + const formSchema = useMemo( + () => getFormSchema(isContainerDistro, minimumHosts), + [isContainerDistro, minimumHosts] + ); return ( <> diff --git a/src/pages/distroSettings/tabs/GeneralTab/getFormSchema.ts b/src/pages/distroSettings/tabs/GeneralTab/getFormSchema.ts index 576b2fdd07..8f0a297d94 100644 --- a/src/pages/distroSettings/tabs/GeneralTab/getFormSchema.ts +++ b/src/pages/distroSettings/tabs/GeneralTab/getFormSchema.ts @@ -1,8 +1,10 @@ -import { css } from "@emotion/react"; import { GetFormSchema } from "components/SpruceForm"; import { CardFieldTemplate } from "components/SpruceForm/FieldTemplates"; -export const getFormSchema = (): ReturnType => ({ +export const getFormSchema = ( + isContainerDistro: boolean, + minimumHosts: number +): ReturnType => ({ fields: {}, schema: { type: "object" as "object", @@ -33,37 +35,30 @@ export const getFormSchema = (): ReturnType => ({ }, }, }, - distroNote: { - type: "object" as "object", - title: "Notes", - properties: { - note: { - type: "string" as "string", - title: "Notes", - default: "", - }, - }, - }, distroOptions: { type: "object" as "object", title: "Distro Options", properties: { isCluster: { type: "boolean" as "boolean", - title: - "Mark distro as a cluster (jobs are not run on this host, used for special purposes).", + title: "Mark distro as cluster", default: false, }, disableShallowClone: { type: "boolean" as "boolean", - title: "Disable shallow clone for this distro.", + title: "Disable shallow clone for this distro", default: false, }, disabled: { type: "boolean" as "boolean", - title: "Disable queueing for this distro.", + title: "Disable queueing for this distro", default: false, }, + note: { + type: "string" as "string", + title: "Notes", + default: "", + }, }, }, }, @@ -71,6 +66,13 @@ export const getFormSchema = (): ReturnType => ({ uiSchema: { distroName: { "ui:ObjectFieldTemplate": CardFieldTemplate, + identifier: { + ...(isContainerDistro && { + "ui:warnings": [ + "Distro is a container pool, so it cannot be spawned for tasks.", + ], + }), + }, }, distroAliases: { "ui:rootFieldId": "aliases", @@ -84,25 +86,22 @@ export const getFormSchema = (): ReturnType => ({ }, }, }, - distroNote: { - "ui:ObjectFieldTemplate": CardFieldTemplate, - note: { - "ui:widget": "textarea", - "ui:elementWrapperCSS": textAreaCSS, - }, - }, distroOptions: { "ui:ObjectFieldTemplate": CardFieldTemplate, + isCluster: { + "ui:description": + "Jobs will not be run on this host. Used for special purposes.", + }, disabled: { "ui:description": "Tasks already in the task queue will be removed.", + ...(minimumHosts > 0 && { + "ui:tooltipDescription": `This will still allow the minimum number of hosts (${minimumHosts}) to start`, + }), + }, + note: { + "ui:rows": 7, + "ui:widget": "textarea", }, }, }, }); - -const textAreaCSS = css` - box-sizing: border-box; - textarea { - min-height: 150px; - } -`; diff --git a/src/pages/distroSettings/tabs/GeneralTab/transformers.test.tsx b/src/pages/distroSettings/tabs/GeneralTab/transformers.test.tsx index c0d91aa99e..f37c54b5e2 100644 --- a/src/pages/distroSettings/tabs/GeneralTab/transformers.test.tsx +++ b/src/pages/distroSettings/tabs/GeneralTab/transformers.test.tsx @@ -20,13 +20,11 @@ const generalForm: GeneralFormState = { distroAliases: { aliases: ["rhel71-power8", "rhel71-power8-build"], }, - distroNote: { - note: "distro note", - }, distroOptions: { isCluster: false, disableShallowClone: true, disabled: false, + note: "distro note", }, }; @@ -34,8 +32,8 @@ const generalGql: DistroInput = { ...distroData, name: "rhel71-power8-large", aliases: ["rhel71-power8", "rhel71-power8-build"], - note: "distro note", isCluster: false, disableShallowClone: true, disabled: false, + note: "distro note", }; diff --git a/src/pages/distroSettings/tabs/GeneralTab/transformers.ts b/src/pages/distroSettings/tabs/GeneralTab/transformers.ts index 151d05b286..24c11d30c2 100644 --- a/src/pages/distroSettings/tabs/GeneralTab/transformers.ts +++ b/src/pages/distroSettings/tabs/GeneralTab/transformers.ts @@ -16,25 +16,23 @@ export const gqlToForm = ((data) => { distroAliases: { aliases, }, - distroNote: { - note, - }, distroOptions: { isCluster, disableShallowClone, disabled, + note, }, }; }) satisfies GqlToFormFunction; export const formToGql = (( - { distroAliases, distroName, distroNote, distroOptions }, + { distroAliases, distroName, distroOptions }, distro ) => ({ ...distro, name: distroName.identifier, aliases: distroAliases.aliases, - note: distroNote.note, + note: distroOptions.note, isCluster: distroOptions.isCluster, disableShallowClone: distroOptions.disableShallowClone, disabled: distroOptions.disabled, diff --git a/src/pages/distroSettings/tabs/GeneralTab/types.ts b/src/pages/distroSettings/tabs/GeneralTab/types.ts index 409dbede7b..998ae4f641 100644 --- a/src/pages/distroSettings/tabs/GeneralTab/types.ts +++ b/src/pages/distroSettings/tabs/GeneralTab/types.ts @@ -5,16 +5,15 @@ export interface GeneralFormState { distroAliases: { aliases: string[]; }; - distroNote: { - note: string; - }; distroOptions: { isCluster: boolean; disableShallowClone: boolean; disabled: boolean; + note: string; }; } export type TabProps = { distroData: GeneralFormState; + minimumHosts: number; }; diff --git a/src/pages/distroSettings/tabs/HostTab/getFormSchema.tsx b/src/pages/distroSettings/tabs/HostTab/getFormSchema.tsx index 731890413b..ae02c6de4a 100644 --- a/src/pages/distroSettings/tabs/HostTab/getFormSchema.tsx +++ b/src/pages/distroSettings/tabs/HostTab/getFormSchema.tsx @@ -128,7 +128,10 @@ export const getFormSchema = ({ setup: setup.uiSchema(architecture, hasStaticProvider), bootstrapSettings: bootstrapProperties.uiSchema(architecture), sshConfig: sshConfigProperties.uiSchema(hasStaticProvider), - allocation: allocationProperties.uiSchema(hasEC2Provider), + allocation: allocationProperties.uiSchema( + hasEC2Provider, + hasStaticProvider + ), }, }; }; diff --git a/src/pages/distroSettings/tabs/HostTab/schemaFields.tsx b/src/pages/distroSettings/tabs/HostTab/schemaFields.tsx index 2d05d38002..e2e4880ec3 100644 --- a/src/pages/distroSettings/tabs/HostTab/schemaFields.tsx +++ b/src/pages/distroSettings/tabs/HostTab/schemaFields.tsx @@ -145,6 +145,7 @@ export const icecreamSchedulerHost = { title: "Icecream Scheduler Host", }, uiSchema: { + "ui:description": "Host name to connect to the icecream scheduler", "ui:elementWrapperCSS": indentCSS, }, }; @@ -155,6 +156,7 @@ export const icecreamConfigPath = { title: "Icecream Config File Path", }, uiSchema: { + "ui:description": "Path to the icecream config file", "ui:elementWrapperCSS": indentCSS, }, }; @@ -285,7 +287,7 @@ const lockedMemoryKb = { const virtualMemoryKb = { schema: { type: "number" as "number", - title: "Virtual Memory (kB)", + title: "Virtual Memory", minimum: -1, }, uiSchema: { @@ -362,7 +364,12 @@ const preconditionScripts = { script: { "ui:description": "The precondition script that must run and succeed before Jasper can start.", - "ui:widget": "textarea", + "ui:elementWrapperCSS": css` + textarea { + font-family: ${fontFamilies.code}; + } + `, + "ui:rows": 8, }, }, }, @@ -401,6 +408,8 @@ const authorizedKeysFile = { }, uiSchema: (hasStaticProvider: boolean) => ({ "ui:data-cy": "authorized-keys-input", + "ui:description": "Path to file containing authorized SSH keys", + "ui:placeholder": "~/.ssh/authorized_keys", ...(!hasStaticProvider && { "ui:widget": "hidden" }), }), }; @@ -448,9 +457,11 @@ const roundingRule = { title: "Host Allocator Rounding Rule", oneOf: enumSelect(roundingRuleToCopy), }, - uiSchema: { + uiSchema: (hasStaticProvider: boolean) => ({ "ui:allowDeselect": false, - }, + "ui:data-cy": "rounding-rule-select", + ...(hasStaticProvider && { "ui:widget": "hidden" }), + }), }; const feedbackRule = { @@ -459,9 +470,11 @@ const feedbackRule = { title: "Host Allocator Feedback Rule", oneOf: enumSelect(feedbackRuleToCopy), }, - uiSchema: { + uiSchema: (hasStaticProvider: boolean) => ({ "ui:allowDeselect": false, - }, + "ui:data-cy": "feedback-rule-select", + ...(hasStaticProvider && { "ui:widget": "hidden" }), + }), }; const hostsOverallocatedRule = { @@ -490,7 +503,7 @@ const minimumHosts = { const maximumHosts = { schema: { type: "number" as "number", - title: "Maxiumum Number of Hosts Allowed", + title: "Maximum Number of Hosts Allowed", minimum: 0, }, uiSchema: (hasEC2Provider: boolean) => ({ @@ -502,7 +515,7 @@ const maximumHosts = { const acceptableHostIdleTime = { schema: { type: "number" as "number", - title: "Acceptable Host Idle Time (s)", + title: "Acceptable Host Idle Time (ms)", minimum: 0, }, uiSchema: (hasEC2Provider: boolean) => ({ @@ -614,11 +627,11 @@ export const allocation = { acceptableHostIdleTime: acceptableHostIdleTime.schema, futureHostFraction: futureHostFraction.schema, }, - uiSchema: (hasEC2Provider: boolean) => ({ + uiSchema: (hasEC2Provider: boolean, hasStaticProvider: boolean) => ({ "ui:ObjectFieldTemplate": CardFieldTemplate, version: version.uiSchema, - roundingRule: roundingRule.uiSchema, - feedbackRule: feedbackRule.uiSchema, + roundingRule: roundingRule.uiSchema(hasStaticProvider), + feedbackRule: feedbackRule.uiSchema(hasStaticProvider), hostsOverallocatedRule: hostsOverallocatedRule.uiSchema, minimumHosts: minimumHosts.uiSchema(hasEC2Provider), maximumHosts: maximumHosts.uiSchema(hasEC2Provider), diff --git a/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts b/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts index 3e8527fceb..98aba4438e 100644 --- a/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts +++ b/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts @@ -38,6 +38,7 @@ const securityGroups = { title: "Security Group ID", default: "", minLength: 1, + pattern: "^sg-.*", }, }, uiSchema: { @@ -257,10 +258,15 @@ const vpcOptions = { subnetId: { type: "string" as "string", title: "Default VPC Subnet ID", + default: "", + minLength: 1, + pattern: "^subnet-.*", }, subnetPrefix: { type: "string" as "string", title: "VPC Subnet Prefix", + default: "", + minLength: 1, }, }, }, diff --git a/src/pages/distroSettings/tabs/TaskTab/getFormSchema.ts b/src/pages/distroSettings/tabs/TaskTab/getFormSchema.ts index 78ab84bd8b..0823c1916a 100644 --- a/src/pages/distroSettings/tabs/TaskTab/getFormSchema.ts +++ b/src/pages/distroSettings/tabs/TaskTab/getFormSchema.ts @@ -94,7 +94,7 @@ export const getFormSchema = ({ properties: { targetTime: { type: "number" as "number", - title: "Target Time (seconds)", + title: "Target Time (ms)", default: 0, minimum: 0, maximum: 1800, @@ -108,37 +108,35 @@ export const getFormSchema = ({ }, patchTimeInQueueFactor: { type: "number" as "number", - title: - "Patch Time in Queue Factor (0 to 100 inclusive)", + title: "Patch Time in Queue Factor", default: 0, minimum: 0, maximum: 100, }, mainlineTimeInQueueFactor: { type: "number" as "number", - title: - "Mainline Time in Queue Factor (0 to 100 inclusive)", + title: "Mainline Time in Queue Factor", default: 0, minimum: 0, maximum: 100, }, commitQueueFactor: { type: "number" as "number", - title: "Commit Queue Factor (0 to 100 inclusive)", + title: "Commit Queue Factor", default: 0, minimum: 0, maximum: 100, }, expectedRuntimeFactor: { type: "number" as "number", - title: "Expected Runtime Factor (0 to 100 inclusive)", + title: "Expected Runtime Factor", default: 0, minimum: 0, maximum: 100, }, generateTaskFactor: { type: "number" as "number", - title: "Generate Task Factor (0 to 100 inclusive)", + title: "Generate Task Factor", default: 0, minimum: 0, maximum: 100, @@ -195,6 +193,26 @@ export const getFormSchema = ({ tunableOptions: { "ui:field-data-cy": "tunable-options", ...(!hasEC2Provider && { "ui:widget": "hidden" }), + patchTimeInQueueFactor: { + "ui:description": + "Set 0 to use global default. Value should range from 0-100 inclusive.", + }, + mainlineTimeInQueueFactor: { + "ui:description": + "Set 0 to use global default. Value should range from 0-100 inclusive.", + }, + commitQueueFactor: { + "ui:description": + "Set 0 to use global default. Value should range from 0-100 inclusive.", + }, + expectedRuntimeFactor: { + "ui:description": + "Set 0 to use global default. Value should range from 0-100 inclusive.", + }, + generateTaskFactor: { + "ui:description": + "Set 0 to use global default. Value should range from 0-100 inclusive.", + }, }, }, dispatcherSettings: {