Skip to content
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

Add the ability to queue/dequeue revalidations on servers #7923

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,44 @@
describe("Servers table page", () => {
beforeEach(() => {
cy.login();
cy.visit("/core/servers");
});
it("Filters servers by hostname", () => {
cy.visit("/core/servers");
cy.get("input[name=fuzzControl]").focus().type("edge");
cy.window().its("location.search").should("contain", "search=edge");
});
it("Queues and clears revalidations on a server", () => {
cy.get("input[name=fuzzControl]").focus().type("edge");

// We need to force re-rendering of the table every time we do
// something, or cypress moves too fast and undoes things it's doing
// before the effects can be seen. This could be fixed by splitting
// these into separate tests, but that wouldn't be faster and would have
// the added drawback that it depends on the initial state of the data
// and the order in which the tests are run.
const reload = (): void => {
cy.reload();
cy.get("button[aria-label='column visibility menu']").click();
cy.get("input[type=checkbox][name='Reval Pending']").check();
cy.get("body").click(); // closes the menu so you can interact with other things.
};

reload();

cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Queue Content Revalidation").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "schedule");
cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Clear Queued Content Revalidations").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "done");
cy.get(".ag-row:visible").first().rightclick();
cy.get("button").contains("Queue Content Revalidation").click();
reload();

cy.get(".ag-cell[col-id=revalPending]").first().should("contain.text", "schedule");
});
});
121 changes: 118 additions & 3 deletions experimental/traffic-portal/src/app/api/server.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { TestBed } from "@angular/core/testing";
import { type ResponseServer } from "trafficops-types";
import { ResponseServerCapability, type ResponseServer } from "trafficops-types";

import { ServerService } from "./server.service";

Expand Down Expand Up @@ -154,7 +154,15 @@ describe("ServerService", () => {
await expectAsync(resp).toBeResolvedTo(server);
});

it("delete a server", async () => {
it("throws an error for invalid call signatures to updateServer", async () => {
const responseP = (service as unknown as {updateServer: (id: number) => Promise<ResponseServer>}).updateServer(
server.id
);
httpTestingController.expectNone({method: "PUT"});
await expectAsync(responseP).toBeRejected();
});

it("deletes a server", async () => {
const resp = service.deleteServer(server);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servers/${server.id}`);
expect(req.request.method).toBe("DELETE");
Expand All @@ -164,7 +172,7 @@ describe("ServerService", () => {
await expectAsync(resp).toBeResolvedTo(server);
});

it("delete a server by ID", async () => {
it("deletes a server by ID", async () => {
const resp = service.deleteServer(server.id);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servers/${server.id}`);
expect(req.request.method).toBe("DELETE");
Expand Down Expand Up @@ -264,6 +272,77 @@ describe("ServerService", () => {
});
});

describe("Capability-related methods", () => {
const capability: ResponseServerCapability = {
lastUpdated: new Date(),
name: "testquest",
};

it("sends requests for multiple capabilities", async () => {
const responseP = service.getCapabilities();
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("GET");
req.flush({response: [capability]});
await expectAsync(responseP).toBeResolvedTo([capability]);
});
it("sends requests for a single capability by name", async () => {
const responseP = service.getCapabilities(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.method).toBe("GET");
req.flush({response: [capability]});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("throws an error when fetching a non-existent capability", async () => {
const responseP = service.getCapabilities(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.method).toBe("GET");
req.flush({response: []});
await expectAsync(responseP).toBeRejected();
});
it("creates a new capability", async () => {
const responseP = service.createCapability(capability);
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("POST");
expect(req.request.body).toEqual(capability);
req.flush({response: capability});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("updates an existing capability", async () => {
const responseP = service.updateCapability(capability.name, capability);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("PUT");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toEqual(capability);
req.flush({response: capability});
await expectAsync(responseP).toBeResolvedTo(capability);
});
it("deletes server_capabilities", async () => {
const responseP = service.deleteCapability(capability);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("DELETE");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("deletes server_capabilities by name", async () => {
const responseP = service.deleteCapability(capability.name);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/server_capabilities`);
expect(req.request.method).toBe("DELETE");
expect(req.request.params.keys().length).toBe(1);
expect(req.request.params.get("name")).toBe(capability.name);
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
});

describe("other methods", () => {
const serverCheck = {
adminState: "ONLINE",
Expand Down Expand Up @@ -312,6 +391,42 @@ describe("ServerService", () => {
req.flush({response});
await expectAsync(responseP).toBeResolvedTo(response);
});
it("queues revalidations on a server", async () => {
const responseP = service.queueReval(server);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("true");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("queues revalidations on a server by ID", async () => {
const responseP = service.queueReval(server.id);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("true");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("de-queues revalidations on a server", async () => {
const responseP = service.clearReval(server);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("false");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("de-queues revalidations on a server by ID", async () => {
const responseP = service.clearReval(server.id);
const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/servers/${server.id}/update`);
expect(req.request.method).toBe("POST");
expect(req.request.params.get("reval_updated")).toBe("false");
expect(req.request.body).toBeNull();
req.flush({alerts: []});
await expectAsync(responseP).toBeResolved();
});
it("sends a request for multiple Serverchecks", async () => {
const responseP = service.getServerChecks();
const req = httpTestingController.expectOne(`/api/${service.apiVersion}/servercheck`);
Expand Down
43 changes: 43 additions & 0 deletions experimental/traffic-portal/src/app/api/server.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,49 @@ export class ServerService extends APIService {
return this.post<ServerQueueResponse>(`servers/${id}/queue_update`, {action: "dequeue"}).toPromise();
}

/**
* Queues revalidations on a single server.
*
* @param server Either the server on which revalidations will be queued, or
* its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async queueReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const params = {
// This param casing is in the API specification, so it must have
// this casing.
// eslint-disable-next-line @typescript-eslint/naming-convention
reval_updated: true
// TODO: This is really confusing; `reval_updated = true` means that
// revalidations **haven't** been updated, and need to be done.
};
return this.post(`servers/${id}/update`, undefined, params).toPromise();
}

/**
* Clears pending revalidations on a single server.
*
* @param server Either the server for which pending revalidations will be
* cleared, or its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async clearReval(server: number | ResponseServer): Promise<void> {
const id = typeof(server) === "number" ? server : server.id;
const params = {
// This param casing is in the API specification, so it must have
// this casing.
// eslint-disable-next-line @typescript-eslint/naming-convention
reval_updated: false
// TODO: This is really confusing; `reval_updated = false` means
// that revalidations **have** been updated, and no longer need to
// be done.
};
return this.post(`servers/${id}/update`, undefined, params).toPromise();
}

/**
* Updates a server's status.
*
Expand Down
36 changes: 36 additions & 0 deletions experimental/traffic-portal/src/app/api/testing/server.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,42 @@
return {action: "dequeue", serverId: id};
}

/**
* Queues revalidations on a single server.
*
* @param server Either the server on which revalidations will be queued, or
* its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async queueReval(server: number | ResponseServer): Promise<void> {

Check warning on line 321 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L321

Added line #L321 was not covered by tests
const id = typeof(server) === "number" ? server : server.id;
const srv = this.servers.find(s=>s.id===id);

Check warning on line 323 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L323

Added line #L323 was not covered by tests
if (!srv) {
throw new Error(`no such Server: #${id}`);

Check warning on line 325 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L325

Added line #L325 was not covered by tests
}

srv.revalPending = true;

Check warning on line 328 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L328

Added line #L328 was not covered by tests
}

/**
* Clears pending revalidations on a single server.
*
* @param server Either the server for which pending revalidations will be
* cleared, or its integral, unique identifier.
* @returns The 'response' property of the TO server's response. See TO API
* docs.
*/
public async clearReval(server: number | ResponseServer): Promise<void> {

Check warning on line 339 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L339

Added line #L339 was not covered by tests
const id = typeof(server) === "number" ? server : server.id;
const srv = this.servers.find(s=>s.id===id);

Check warning on line 341 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L341

Added line #L341 was not covered by tests
if (!srv) {
throw new Error(`no such Server: #${id}`);

Check warning on line 343 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L343

Added line #L343 was not covered by tests
}

srv.revalPending = false;
}

Check warning on line 347 in experimental/traffic-portal/src/app/api/testing/server.service.ts

View check run for this annotation

Codecov / codecov/patch

experimental/traffic-portal/src/app/api/testing/server.service.ts#L346-L347

Added lines #L346 - L347 were not covered by tests

/**
* Updates a server's status.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,22 +274,61 @@ describe("ServersTableComponent", () => {
const service = TestBed.inject(ServerService);
const queueSpy = spyOn(service, "queueUpdates");
const clearSpy = spyOn(service, "clearUpdates");
const revalSpy = spyOn(service, "queueReval");
const clearRevalSpy = spyOn(service, "clearReval");

expect(queueSpy).not.toHaveBeenCalled();
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "queue", data: server});
expect(queueSpy).toHaveBeenCalledTimes(1);
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "queue", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).not.toHaveBeenCalled();
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "dequeue", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(1);
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "dequeue", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).not.toHaveBeenCalled();
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "reval", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(1);
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "reval", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).not.toHaveBeenCalled();

component.handleContextMenu({action: "unreval", data: server});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).toHaveBeenCalledTimes(1);

component.handleContextMenu({action: "unreval", data: [server, server]});
expect(queueSpy).toHaveBeenCalledTimes(3);
expect(clearSpy).toHaveBeenCalledTimes(3);
expect(revalSpy).toHaveBeenCalledTimes(3);
expect(clearRevalSpy).toHaveBeenCalledTimes(3);

expectAsync(component.handleContextMenu({action: "not a real action", data: []})).toBeRejected();
}));
Expand Down
Loading
Loading