Treat namespace and job name as a composite primary key
This commit is contained in:
parent
8eeacebe67
commit
afcfba0910
|
@ -1,7 +1,7 @@
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
import ApplicationAdapter from './application';
|
import ApplicationAdapter from './application';
|
||||||
|
|
||||||
const { RSVP, inject } = Ember;
|
const { RSVP, inject, assign } = Ember;
|
||||||
|
|
||||||
export default ApplicationAdapter.extend({
|
export default ApplicationAdapter.extend({
|
||||||
system: inject.service(),
|
system: inject.service(),
|
||||||
|
@ -9,12 +9,10 @@ export default ApplicationAdapter.extend({
|
||||||
shouldReloadAll: () => true,
|
shouldReloadAll: () => true,
|
||||||
|
|
||||||
buildQuery() {
|
buildQuery() {
|
||||||
const namespace = this.get('system.activeNamespace');
|
const namespace = this.get('system.activeNamespace.id');
|
||||||
|
|
||||||
if (namespace) {
|
if (namespace && namespace !== 'default') {
|
||||||
return {
|
return { namespace };
|
||||||
namespace: namespace.get('name'),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -22,7 +20,7 @@ export default ApplicationAdapter.extend({
|
||||||
const namespace = this.get('system.activeNamespace');
|
const namespace = this.get('system.activeNamespace');
|
||||||
return this._super(...arguments).then(data => {
|
return this._super(...arguments).then(data => {
|
||||||
data.forEach(job => {
|
data.forEach(job => {
|
||||||
job.Namespace = namespace && namespace.get('id');
|
job.Namespace = namespace ? namespace.get('id') : 'default';
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
@ -31,11 +29,21 @@ export default ApplicationAdapter.extend({
|
||||||
findRecord(store, { modelName }, id, snapshot) {
|
findRecord(store, { modelName }, id, snapshot) {
|
||||||
// To make a findRecord response reflect the findMany response, the JobSummary
|
// To make a findRecord response reflect the findMany response, the JobSummary
|
||||||
// from /summary needs to be stitched into the response.
|
// from /summary needs to be stitched into the response.
|
||||||
|
|
||||||
|
// URL is the form of /job/:name?namespace=:namespace with arbitrary additional query params
|
||||||
|
const [name, namespace] = JSON.parse(id);
|
||||||
|
const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
job: this._super(...arguments),
|
job: this.ajax(this.buildURL(modelName, name, snapshot, 'findRecord'), 'GET', {
|
||||||
summary: this.ajax(`${this.buildURL(modelName, id, snapshot, 'findRecord')}/summary`, 'GET', {
|
data: assign(this.buildQuery() || {}, namespaceQuery),
|
||||||
data: this.buildQuery(),
|
|
||||||
}),
|
}),
|
||||||
|
summary: this.ajax(
|
||||||
|
`${this.buildURL(modelName, name, snapshot, 'findRecord')}/summary`,
|
||||||
|
'GET',
|
||||||
|
{
|
||||||
|
data: assign(this.buildQuery() || {}, namespaceQuery),
|
||||||
|
}
|
||||||
|
),
|
||||||
}).then(({ job, summary }) => {
|
}).then(({ job, summary }) => {
|
||||||
job.JobSummary = summary;
|
job.JobSummary = summary;
|
||||||
return job;
|
return job;
|
||||||
|
@ -52,7 +60,9 @@ export default ApplicationAdapter.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchRawDefinition(job) {
|
fetchRawDefinition(job) {
|
||||||
const url = this.buildURL('job', job.get('id'), job, 'findRecord');
|
const [name, namespace] = JSON.parse(job.get('id'));
|
||||||
return this.ajax(url, 'GET', { data: this.buildQuery() });
|
const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};
|
||||||
|
const url = this.buildURL('job', name, job, 'findRecord');
|
||||||
|
return this.ajax(url, 'GET', { data: assign(this.buildQuery() || {}, namespaceQuery) });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ const { computed } = Ember;
|
||||||
export default Model.extend({
|
export default Model.extend({
|
||||||
region: attr('string'),
|
region: attr('string'),
|
||||||
name: attr('string'),
|
name: attr('string'),
|
||||||
|
plainId: attr('string'),
|
||||||
type: attr('string'),
|
type: attr('string'),
|
||||||
priority: attr('number'),
|
priority: attr('number'),
|
||||||
allAtOnce: attr('boolean'),
|
allAtOnce: attr('boolean'),
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
import ApplicationSerializer from './application';
|
import ApplicationSerializer from './application';
|
||||||
|
|
||||||
const { get } = Ember;
|
const { get, inject } = Ember;
|
||||||
|
|
||||||
export default ApplicationSerializer.extend({
|
export default ApplicationSerializer.extend({
|
||||||
|
system: inject.service(),
|
||||||
|
|
||||||
attrs: {
|
attrs: {
|
||||||
taskGroupName: 'TaskGroup',
|
taskGroupName: 'TaskGroup',
|
||||||
states: 'TaskStates',
|
states: 'TaskStates',
|
||||||
|
@ -22,6 +24,14 @@ export default ApplicationSerializer.extend({
|
||||||
|
|
||||||
hash.JobVersion = hash.JobVersion != null ? hash.JobVersion : get(hash, 'Job.Version');
|
hash.JobVersion = hash.JobVersion != null ? hash.JobVersion : get(hash, 'Job.Version');
|
||||||
|
|
||||||
|
hash.PlainJobId = hash.JobID;
|
||||||
|
hash.Namespace =
|
||||||
|
hash.Namespace ||
|
||||||
|
get(hash, 'Job.Namespace') ||
|
||||||
|
this.get('system.activeNamespace.id') ||
|
||||||
|
'default';
|
||||||
|
hash.JobID = JSON.stringify([hash.JobID, hash.Namespace]);
|
||||||
|
|
||||||
// TEMPORARY: https://github.com/emberjs/data/issues/5209
|
// TEMPORARY: https://github.com/emberjs/data/issues/5209
|
||||||
hash.OriginalJobId = hash.JobID;
|
hash.OriginalJobId = hash.JobID;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,14 @@ export default ApplicationSerializer.extend({
|
||||||
return assign({ Name: key }, deploymentStats);
|
return assign({ Name: key }, deploymentStats);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hash.PlainJobId = hash.JobID;
|
||||||
|
hash.Namespace =
|
||||||
|
hash.Namespace ||
|
||||||
|
get(hash, 'Job.Namespace') ||
|
||||||
|
this.get('system.activeNamespace.id') ||
|
||||||
|
'default';
|
||||||
|
hash.JobID = JSON.stringify([hash.JobID, hash.Namespace]);
|
||||||
|
|
||||||
return this._super(typeHash, hash);
|
return this._super(typeHash, hash);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,13 @@ export default ApplicationSerializer.extend({
|
||||||
normalize(typeHash, hash) {
|
normalize(typeHash, hash) {
|
||||||
hash.NamespaceID = hash.Namespace;
|
hash.NamespaceID = hash.Namespace;
|
||||||
|
|
||||||
|
// ID is a composite of both the job ID and the namespace the job is in
|
||||||
|
hash.PlainId = hash.ID;
|
||||||
|
hash.ID = JSON.stringify([hash.ID, hash.NamespaceID || 'default']);
|
||||||
|
|
||||||
// Transform the map-based JobSummary object into an array-based
|
// Transform the map-based JobSummary object into an array-based
|
||||||
// JobSummary fragment list
|
// JobSummary fragment list
|
||||||
hash.TaskGroupSummaries = Object.keys(get(hash, 'JobSummary.Summary')).map(key => {
|
hash.TaskGroupSummaries = Object.keys(get(hash, 'JobSummary.Summary') || {}).map(key => {
|
||||||
const allocStats = get(hash, `JobSummary.Summary.${key}`);
|
const allocStats = get(hash, `JobSummary.Summary.${key}`);
|
||||||
const summary = { Name: key };
|
const summary = { Name: key };
|
||||||
|
|
||||||
|
@ -40,9 +44,10 @@ export default ApplicationSerializer.extend({
|
||||||
const namespace =
|
const namespace =
|
||||||
!hash.NamespaceID || hash.NamespaceID === 'default' ? undefined : hash.NamespaceID;
|
!hash.NamespaceID || hash.NamespaceID === 'default' ? undefined : hash.NamespaceID;
|
||||||
const { modelName } = modelClass;
|
const { modelName } = modelClass;
|
||||||
|
|
||||||
const jobURL = this.store
|
const jobURL = this.store
|
||||||
.adapterFor(modelName)
|
.adapterFor(modelName)
|
||||||
.buildURL(modelName, this.extractId(modelClass, hash), hash, 'findRecord');
|
.buildURL(modelName, hash.PlainId, hash, 'findRecord');
|
||||||
|
|
||||||
return assign(this._super(...arguments), {
|
return assign(this._super(...arguments), {
|
||||||
allocations: {
|
allocations: {
|
||||||
|
|
|
@ -10,6 +10,7 @@ moduleFor('adapter:job', 'Unit | Adapter | Job', {
|
||||||
this.server = startMirage();
|
this.server = startMirage();
|
||||||
this.server.create('node');
|
this.server.create('node');
|
||||||
this.server.create('job', { id: 'job-1' });
|
this.server.create('job', { id: 'job-1' });
|
||||||
|
this.server.create('job', { id: 'job-2', namespaceId: 'some-namespace' });
|
||||||
},
|
},
|
||||||
afterEach() {
|
afterEach() {
|
||||||
this.server.shutdown();
|
this.server.shutdown();
|
||||||
|
@ -18,22 +19,43 @@ moduleFor('adapter:job', 'Unit | Adapter | Job', {
|
||||||
|
|
||||||
test('The job summary is stitched into the job request', function(assert) {
|
test('The job summary is stitched into the job request', function(assert) {
|
||||||
const { pretender } = this.server;
|
const { pretender } = this.server;
|
||||||
const jobId = 'job-1';
|
const jobName = 'job-1';
|
||||||
|
const jobNamespace = 'default';
|
||||||
|
const jobId = JSON.stringify([jobName, jobNamespace]);
|
||||||
|
|
||||||
this.subject().findRecord(null, { modelName: 'job' }, jobId);
|
this.subject().findRecord(null, { modelName: 'job' }, jobId);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
pretender.handledRequests.mapBy('url'),
|
pretender.handledRequests.mapBy('url'),
|
||||||
['/v1/namespaces', `/v1/job/${jobId}`, `/v1/job/${jobId}/summary`],
|
['/v1/namespaces', `/v1/job/${jobName}`, `/v1/job/${jobName}/summary`],
|
||||||
'The three requests made are /namespaces, /job/:id, and /job/:id/summary'
|
'The three requests made are /namespaces, /job/:id, and /job/:id/summary'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('When the job has a namespace other than default, it is in the URL', function(assert) {
|
||||||
|
const { pretender } = this.server;
|
||||||
|
const jobName = 'job-2';
|
||||||
|
const jobNamespace = 'some-namespace';
|
||||||
|
const jobId = JSON.stringify([jobName, jobNamespace]);
|
||||||
|
|
||||||
|
this.subject().findRecord(null, { modelName: 'job' }, jobId);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
pretender.handledRequests.mapBy('url'),
|
||||||
|
[
|
||||||
|
'/v1/namespaces',
|
||||||
|
`/v1/job/${jobName}?namespace=${jobNamespace}`,
|
||||||
|
`/v1/job/${jobName}/summary?namespace=${jobNamespace}`,
|
||||||
|
],
|
||||||
|
'The three requests made are /namespaces, /job/:id?namespace=:namespace, and /job/:id/summary?namespace=:namespace'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('When there is no token set in the token service, no x-nomad-token header is set', function(
|
test('When there is no token set in the token service, no x-nomad-token header is set', function(
|
||||||
assert
|
assert
|
||||||
) {
|
) {
|
||||||
const { pretender } = this.server;
|
const { pretender } = this.server;
|
||||||
const jobId = 'job-1';
|
const jobId = JSON.stringify(['job-1', 'default']);
|
||||||
|
|
||||||
this.subject().findRecord(null, { modelName: 'job' }, jobId);
|
this.subject().findRecord(null, { modelName: 'job' }, jobId);
|
||||||
|
|
||||||
|
@ -47,7 +69,7 @@ test('When a token is set in the token service, then x-nomad-token header is set
|
||||||
assert
|
assert
|
||||||
) {
|
) {
|
||||||
const { pretender } = this.server;
|
const { pretender } = this.server;
|
||||||
const jobId = 'job-1';
|
const jobId = JSON.stringify(['job-1', 'default']);
|
||||||
const secret = 'here is the secret';
|
const secret = 'here is the secret';
|
||||||
|
|
||||||
this.subject().set('token.secret', secret);
|
this.subject().set('token.secret', secret);
|
||||||
|
|
|
@ -51,10 +51,11 @@ test('The JobSummary object is transformed from a map to a list', function(asser
|
||||||
JobModifyIndex: 7,
|
JobModifyIndex: 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalized = this.subject().normalize(JobModel, original);
|
const { data } = this.subject().normalize(JobModel, original);
|
||||||
|
|
||||||
assert.deepEqual(normalized.data.attributes, {
|
assert.deepEqual(data.attributes, {
|
||||||
name: 'example',
|
name: 'example',
|
||||||
|
plainId: 'example',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
priority: 50,
|
priority: 50,
|
||||||
periodic: false,
|
periodic: false,
|
||||||
|
@ -116,6 +117,7 @@ test('The children stats are lifted out of the JobSummary object', function(asse
|
||||||
|
|
||||||
assert.deepEqual(normalized.data.attributes, {
|
assert.deepEqual(normalized.data.attributes, {
|
||||||
name: 'example',
|
name: 'example',
|
||||||
|
plainId: 'example',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
priority: 50,
|
priority: 50,
|
||||||
periodic: false,
|
periodic: false,
|
||||||
|
@ -130,3 +132,29 @@ test('The children stats are lifted out of the JobSummary object', function(asse
|
||||||
modifyIndex: 9,
|
modifyIndex: 9,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('`default` is used as the namespace in the job ID when there is no namespace in the payload', function(
|
||||||
|
assert
|
||||||
|
) {
|
||||||
|
const original = {
|
||||||
|
ID: 'example',
|
||||||
|
Name: 'example',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = this.subject().normalize(JobModel, original);
|
||||||
|
assert.equal(data.id, JSON.stringify([data.attributes.name, 'default']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('The ID of the record is a composite of both the name and the namespace', function(assert) {
|
||||||
|
const original = {
|
||||||
|
ID: 'example',
|
||||||
|
Name: 'example',
|
||||||
|
Namespace: 'special-namespace',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = this.subject().normalize(JobModel, original);
|
||||||
|
assert.equal(
|
||||||
|
data.id,
|
||||||
|
JSON.stringify([data.attributes.name, data.relationships.namespace.data.id])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue