-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#70 implementation for planNamespaceDeployment step
- Loading branch information
Alexander Kogan
committed
Feb 6, 2019
1 parent
69b82f7
commit bba8009
Showing
2 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,63 @@ | ||
import { call } from '../../dispatcher' | ||
import { IO } from '../../IO' | ||
|
||
import { ServerGroup } from '../../cluster/model/ServerGroup' | ||
import { Deployment } from '../../cluster/model/Deployment' | ||
import * as clusterModule from '../../cluster/ClusterModule' | ||
|
||
import { Filter, matchesDeployment, matchesServerGroup } from '../model/Filter' | ||
import { JobPlan } from '../model/JobPlan' | ||
import { DeploymentPlan } from '../model/DeploymentPlan' | ||
|
||
export class PlanNamespaceDeployment { | ||
public constructor( | ||
readonly name: string, | ||
readonly sourceCluster: string, | ||
readonly sourceNamespace: string, | ||
readonly targetNamespace: string, | ||
readonly filter?: Partial<Filter>, | ||
readonly io: IO = new IO() | ||
) {} | ||
|
||
public async plan(targetGroup: ServerGroup, name: string, targetDeployments: Deployment[]): Promise<JobPlan> { | ||
if (!matchesServerGroup({ namespaces: [this.targetNamespace] }, targetGroup)) { | ||
return { name, deployments: [] } | ||
} | ||
|
||
const sourceDeployments = await call(clusterModule.deployments)({ | ||
cluster: this.sourceCluster, | ||
namespace: this.sourceNamespace, | ||
}) | ||
|
||
const deployments: DeploymentPlan[] = [] | ||
for (const targetDeployment of targetDeployments) { | ||
if (!matchesDeployment(this.filter, targetDeployment)) { | ||
continue | ||
} | ||
this.io.out(`planning namespace deployment ${targetDeployment.name} in ${this.name}`) | ||
const sourceDeployment = sourceDeployments.find(d => d.name === targetDeployment.name) | ||
if (sourceDeployment === undefined) { | ||
this.io.error( | ||
`${targetDeployment.name} in {cluster: ${targetGroup.cluster}, namespace: ${targetGroup.namespace}} ` | ||
`has no appropriate deployment in {cluster: ${this.sourceCluster}, namespace: ${this.sourceNamespace}}` | ||
) | ||
continue | ||
} | ||
if (sourceDeployment.image === undefined) { | ||
this.io.error( | ||
`${targetDeployment.name} in {cluster: ${this.sourceCluster}, namespace: ${this.sourceNamespace}} ` `has no image` | ||
) | ||
continue | ||
} | ||
if (targetDeployment.image === undefined || targetDeployment.image.url !== sourceDeployment.image.url) { | ||
deployments.push({ | ||
group: targetGroup, | ||
deployment: targetDeployment, | ||
image: sourceDeployment.image, | ||
}) | ||
} | ||
} | ||
|
||
return { name, deployments } | ||
} | ||
} |
This file contains 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,164 @@ | ||
import { Request } from '../../../src/dispatcher/model/Request' | ||
|
||
import { Deployment } from '../../../src/cluster/model/Deployment' | ||
import { ServerGroup } from '../../../src/cluster/model/ServerGroup' | ||
|
||
import { PlanNamespaceDeployment } from '../../../src/jobs/steps/PlanNamespaceDeployment' | ||
|
||
let mockDeployments: { [key: string]: Deployment[] } = {} | ||
|
||
jest.mock('../../../src/dispatcher/index', () => ({ | ||
get: jest.fn(), | ||
call: (request: Request<any, any>) => { | ||
switch (request.module) { | ||
case 'cluster': { | ||
switch (request.procedure) { | ||
case 'deployments': { | ||
return (input: { cluster: string }) => Promise.resolve(mockDeployments[input.cluster]) | ||
} | ||
} | ||
break | ||
} | ||
} | ||
return undefined | ||
}, | ||
add: () => {}, | ||
path: () => '', | ||
})) | ||
|
||
describe(PlanNamespaceDeployment.name, () => { | ||
const targetGroup: ServerGroup = { cluster: 'targetCluster', namespace: 'targetNamespace' } | ||
const step = new PlanNamespaceDeployment('stepName', 'sourceCluster', 'sourceNamespace', 'targetNamespace', { | ||
deployments: ['deployment1'], | ||
}) | ||
const targetDeployments: Deployment[] = [{ name: 'deployment1' }] | ||
|
||
it('plans deployments', async () => { | ||
// given | ||
mockDeployments = { sourceCluster: [{ name: 'deployment1', image: { name: 'image1', url: 'url1' } }] } | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([ | ||
{ | ||
deployment: { name: 'deployment1' }, | ||
group: { cluster: 'targetCluster', namespace: 'targetNamespace' }, | ||
image: { name: 'image1', url: 'url1' }, | ||
}, | ||
]) | ||
}) | ||
|
||
it('does not plan if target namespace does not match', async () => { | ||
// when | ||
const plan = await step.plan( | ||
{ | ||
cluster: 'targetCluster', // Cluster is always set to the target cluster from the pipeline, so it can't be different. | ||
namespace: 'otherNamespace', | ||
}, | ||
'pipeline1', | ||
targetDeployments | ||
) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([]) | ||
}) | ||
|
||
it('ignores deployments that do not match', async () => { | ||
// given | ||
mockDeployments = { sourceCluster: [{ name: 'deployment2', image: { name: 'image1', url: 'url1' } }] } | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', [ | ||
{ | ||
name: 'deployment2', | ||
image: { name: 'image1', url: 'url2' }, | ||
}, | ||
]) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([]) | ||
}) | ||
|
||
it('ignores deployments that cannot be found in source', async () => { | ||
// given | ||
step.io.error = jest.fn() | ||
mockDeployments = { sourceCluster: [{ name: 'otherDeployment', image: { name: 'image1', url: 'url1' } }] } | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([]) | ||
expect(step.io.error).toHaveBeenCalledWith( | ||
'deployment1 in {cluster: targetCluster, namespace: targetNamespace} has no appropriate deployment ' | ||
'in {cluster: sourceCluster, namespace: sourceNamespace}' | ||
) | ||
}) | ||
|
||
it('ignores deployments that have no image in source', async () => { | ||
// given | ||
step.io.error = jest.fn() | ||
mockDeployments = { sourceCluster: [{ name: 'deployment1' }] } | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([]) | ||
expect(step.io.error).toHaveBeenCalledWith('deployment1 in {cluster: sourceCluster, namespace: sourceNamespace} has no image') | ||
}) | ||
|
||
it('ignores deployments that have same image as source', async () => { | ||
// given | ||
const deployments = [{ name: 'deployment1', image: { name: 'image1', url: 'url1' } }] | ||
mockDeployments = { sourceCluster: deployments } | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', deployments) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([]) | ||
}) | ||
|
||
it('filters deployments correctly', async () => { | ||
// given | ||
mockDeployments = { | ||
sourceCluster: [ | ||
{ name: 'deployment1', image: { name: 'image1', url: 'url1' } }, | ||
{ name: 'deployment3' }, | ||
{ name: 'deployment4', image: { name: 'image4', url: 'url4' } }, | ||
{ name: 'deployment5', image: { name: 'image5', url: 'url5' } }, | ||
], | ||
} | ||
const step = new PlanNamespaceDeployment('stepName', 'sourceCluster', 'sourceNamespace', 'targetNamespace', { | ||
deployments: ['deployment2', 'deployment3', 'deployment4', 'deployment5'], | ||
}) | ||
step.io.error = jest.fn() | ||
const targetDeployments: Deployment[] = [ | ||
{ name: 'deployment1' }, | ||
{ name: 'deployment2' }, | ||
{ name: 'deployment3' }, | ||
{ name: 'deployment5' }, | ||
] | ||
|
||
// when | ||
const plan = await step.plan(targetGroup, 'pipeline1', targetDeployments) | ||
|
||
// then | ||
expect(plan.deployments).toEqual([ | ||
{ | ||
deployment: { name: 'deployment5' }, | ||
group: { cluster: 'targetCluster', namespace: 'targetNamespace' }, | ||
image: { name: 'image5', url: 'url5' }, | ||
}, | ||
]) | ||
expect(step.io.error).toHaveBeenCalledTimes(2) | ||
expect(step.io.error).toHaveBeenCalledWith('deployment3 in {cluster: sourceCluster, namespace: sourceNamespace} has no image') | ||
expect(step.io.error).toHaveBeenCalledWith( | ||
'deployment2 in {cluster: targetCluster, namespace: targetNamespace} has no appropriate deployment ' | ||
'in {cluster: sourceCluster, namespace: sourceNamespace}' | ||
) | ||
}) | ||
}) |