ui: Ensure service instance data does not get re-written on blocking refresh (#11903)

* Add some less fake API data

* Rename the models class so as to not be confused with JS Proxies

* Rearrange routlets slightly and add some initial outletFor tests

* Move away from a MeshChecks computed property and just use a helper

* Just use ServiceChecks for healthiness filtering for the moment

* Make TProxy cookie configurable

* Amend exposed paths and upstreams so they know about meta AND proxy

* Slight bit of TaggedAddresses refactor while I was checking for `meta` etc

* Document CONSUL_TPROXY_ENABLE
This commit is contained in:
John Cowen 2022-01-07 19:16:21 +00:00 committed by GitHub
parent 3ab747109b
commit 514e24ba9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 593 additions and 164 deletions

4
.changelog/11903.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:bug
ui: Fixes a bug where proxy service health checks would sometimes not appear
until refresh
```

View File

@ -28,6 +28,10 @@ export default class Outlet extends Component {
return this.args.model || {}; return this.args.model || {};
} }
get name() {
return this.args.name;
}
setAppRoute(name) { setAppRoute(name) {
if (name !== 'loading' || name === 'oidc-provider-debug') { if (name !== 'loading' || name === 'oidc-provider-debug') {
const doc = this.element.ownerDocument.documentElement; const doc = this.element.ownerDocument.documentElement;
@ -55,7 +59,9 @@ export default class Outlet extends Component {
} }
break; break;
case 'model': case 'model':
this.route._model = this.args.model; if(typeof this.route !== 'undefined') {
this.route._model = value;
}
break; break;
} }
} }

View File

@ -1,7 +1,7 @@
{{did-insert this.connect}} {{did-insert this.connect}}
{{will-destroy this.disconnect}} {{will-destroy this.disconnect}}
{{yield (hash {{yield (hash
model=(or this.model this._model) model=this.model
params=this.params params=this.params
currentName=this.router.currentRoute.name currentName=this.router.currentRoute.name

View File

@ -14,17 +14,14 @@ export default class RouteComponent extends Component {
} }
get model() { get model() {
if(this.args.name) { if(this._model) {
const temp = this.args.name.split('.'); return this._model;
temp.pop();
const name = temp.join('.');
let model = this.routlet.modelFor(name);
if(Object.keys(model).length === 0) {
return null;
}
return model;
} }
return null; if (this.args.name) {
const outlet = this.routlet.outletFor(this.args.name);
return this.routlet.modelFor(outlet.name);
}
return undefined;
} }
@action @action

View File

@ -5,7 +5,7 @@ export default {
passing: (item, value) => item.Status === value, passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value, warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value, critical: (item, value) => item.Status === value,
empty: (item, value) => item.MeshChecks.length === 0, empty: (item, value) => item.ServiceChecks.length === 0,
}, },
source: (item, values) => { source: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0; return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;

View File

@ -0,0 +1,9 @@
import { helper } from '@ember/component/helper';
import mergeChecks from 'consul-ui/utils/merge-checks';
export default helper(function([checks, exposed], hash) {
return mergeChecks(
checks,
exposed
);
});

View File

@ -5,7 +5,7 @@ export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'Node,ServiceID'; export const SLUG_KEY = 'Node,ServiceID';
// TODO: This should be changed to ProxyInstance // TODO: This should be changed to ProxyInstance
export default class Proxy extends ServiceInstanceModel { export default class ProxyServiceInstance extends ServiceInstanceModel {
@attr('string') uid; @attr('string') uid;
@attr('string') ID; @attr('string') ID;

View File

@ -1,9 +1,8 @@
import Model, { attr, belongsTo } from '@ember-data/model'; import Model, { attr } from '@ember-data/model';
import { fragmentArray } from 'ember-data-model-fragments/attributes'; import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { computed, get } from '@ember/object'; import { computed } from '@ember/object';
import { or, filter, alias } from '@ember/object/computed'; import { or, filter, alias } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import mergeChecks from 'consul-ui/utils/merge-checks';
export const PRIMARY_KEY = 'uid'; export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'Node.Node,Service.ID'; export const SLUG_KEY = 'Node.Node,Service.ID';
@ -28,8 +27,6 @@ export default class ServiceInstance extends Model {
@attr('string') uid; @attr('string') uid;
@attr('string') Datacenter; @attr('string') Datacenter;
// ProxyInstance is the ember-data model relationship
@belongsTo('Proxy') ProxyInstance;
// Proxy is the actual JSON api response // Proxy is the actual JSON api response
@attr() Proxy; @attr() Proxy;
@attr() Node; @attr() Node;
@ -55,18 +52,6 @@ export default class ServiceInstance extends Model {
@filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'service') ServiceChecks; @filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'service') ServiceChecks;
@filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'node') NodeChecks; @filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'node') NodeChecks;
// MeshChecks are a concatenation of Checks for the Instance and Checks for
// the ProxyInstance.
@computed('Checks.[]', 'ProxyInstance.{Checks.[],ServiceProxy.Expose.Checks}')
get MeshChecks() {
// merge the instance and proxy checks together, avoiding duplicate node
// checks and additionally setting any checks to exposed if required
return mergeChecks(
[get(this, 'Checks'), get(this, 'ProxyInstance.Checks')],
get(this, 'ProxyInstance.ServiceProxy.Expose.Checks')
);
}
@computed('Service.Meta') @computed('Service.Meta')
get ExternalSources() { get ExternalSources() {
const sources = Object.entries(this.Service.Meta || {}) const sources = Object.entries(this.Service.Meta || {})

View File

@ -36,24 +36,24 @@ export default class ProxyService extends RepositoryService {
} }
@dataSource('/:partition/:ns/:dc/proxy-instance/:serviceId/:node/:id') @dataSource('/:partition/:ns/:dc/proxy-instance/:serviceId/:node/:id')
findInstanceBySlug(params, configuration) { async findInstanceBySlug(params, configuration) {
return this.findAllBySlug(params, configuration).then(function(items) { const items = await this.findAllBySlug(params, configuration);
let res = {};
if (get(items, 'length') > 0) { let res = {};
let instance = items if (get(items, 'length') > 0) {
.filterBy('ServiceProxy.DestinationServiceID', params.serviceId) let instance = items
.findBy('NodeName', params.node); .filterBy('ServiceProxy.DestinationServiceID', params.serviceId)
.findBy('NodeName', params.node);
if (instance) {
res = instance;
} else {
instance = items.findBy('ServiceProxy.DestinationServiceName', params.id);
if (instance) { if (instance) {
res = instance; res = instance;
} else {
instance = items.findBy('ServiceProxy.DestinationServiceName', params.id);
if (instance) {
res = instance;
}
} }
} }
set(res, 'meta', get(items, 'meta')); }
return res; set(res, 'meta', get(items, 'meta'));
}); return res;
} }
} }

View File

@ -1,12 +1,10 @@
import RepositoryService from 'consul-ui/services/repository'; import RepositoryService from 'consul-ui/services/repository';
import { inject as service } from '@ember/service';
import { set } from '@ember/object'; import { set } from '@ember/object';
import { ACCESS_READ } from 'consul-ui/abilities/base'; import { ACCESS_READ } from 'consul-ui/abilities/base';
import dataSource from 'consul-ui/decorators/data-source'; import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'service-instance'; const modelName = 'service-instance';
export default class ServiceInstanceService extends RepositoryService { export default class ServiceInstanceService extends RepositoryService {
@service('repository/proxy') proxyRepo;
getModelName() { getModelName() {
return modelName; return modelName;
} }
@ -34,44 +32,6 @@ export default class ServiceInstanceService extends RepositoryService {
@dataSource('/:partition/:ns/:dc/service-instance/:serviceId/:node/:id') @dataSource('/:partition/:ns/:dc/service-instance/:serviceId/:node/:id')
async findBySlug(params, configuration = {}) { async findBySlug(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') { return super.findBySlug(...arguments);
params.index = configuration.cursor;
params.uri = configuration.uri;
}
return this.authorizeBySlug(
async () => this.store.queryRecord(this.getModelName(), params),
ACCESS_READ,
params
);
}
@dataSource('/:partition/:ns/:dc/proxy-service-instance/:serviceId/:node/:id')
async findProxyBySlug(params, configuration = {}) {
const instance = await this.findBySlug(...arguments);
let proxy = this.store.peekRecord('proxy', instance.uid);
// Currently, we call the proxy endpoint before this endpoint
// therefore proxy is never undefined. If we ever call this endpoint
// first we'll need to do something like the following
// if(typeof proxy === 'undefined') {
// await proxyRepo.create({})
// }
// Copy over all the things to the ProxyServiceInstance
['Service', 'Node', 'meta'].forEach(prop => {
set(proxy, prop, instance[prop]);
});
['Checks'].forEach(prop => {
// completely wipe out any previous values so we don't accumulate things
// eternally
proxy.set(prop, []);
instance[prop].forEach(item => {
if (typeof item !== 'undefined') {
proxy[prop].addFragment(item.copy());
}
});
});
// delete the ServiceInstance record as we now have a ProxyServiceInstance
instance.unloadRecord();
return proxy;
} }
} }

View File

@ -75,21 +75,11 @@ export default class RoutletService extends Service {
return key; return key;
} }
addOutlet(name, outlet) { outletFor(routeName) {
outlets.set(name, outlet); const keys = [...outlets.keys()];
} const pos = keys.indexOf(routeName);
const key = pos + 1;
removeOutlet(name) { return outlets.get(keys[key]);
outlets.delete(name);
}
// modelFor gets the model for Outlet specified by `name`, not the Route
modelFor(name) {
const outlet = outlets.get(name);
if (typeof outlet !== 'undefined') {
return outlet.model || {};
}
return {};
} }
/** /**
@ -149,20 +139,43 @@ export default class RoutletService extends Service {
}; };
} }
addRoute(name, route) {
const keys = [...outlets.keys()]; // modelFor gets the model for Outlet specified by `name`, not the Route
const pos = keys.indexOf(name); modelFor(name) {
const key = pos + 1; const outlet = outlets.get(name);
const outlet = outlets.get(keys[key]); if (typeof outlet !== 'undefined') {
return outlet.model;
}
}
addRoute(name, route) {
const outlet = this.outletFor(name);
if (typeof outlet !== 'undefined') { if (typeof outlet !== 'undefined') {
route._model = outlet.model;
outlet.route = route; outlet.route = route;
// TODO: Try to avoid the double computation bug // TODO: Try to avoid the double computation bug
schedule('afterRender', () => { schedule('afterRender', () => {
outlet.routeName = route.args.name; outlet.routeName = name;
}); });
} }
} }
removeRoute(name, route) {} removeRoute(name, route) {
const outlet = this.outletFor(name);
route._model = undefined;
if (typeof outlet !== 'undefined') {
schedule('afterRender', () => {
outlet.route = undefined;
});
}
}
addOutlet(name, outlet) {
outlets.set(name, outlet);
}
removeOutlet(name) {
schedule('afterRender', () => {
outlets.delete(name);
});
}
} }

View File

@ -94,6 +94,7 @@ as |item|}}
name=route.params.name name=route.params.name
) )
}} }}
@onchange={{action (mut meta) value="data"}}
as |meta|> as |meta|>
{{! We only really need meta to get the correct ServiceID }} {{! We only really need meta to get the correct ServiceID }}
{{! but we may as well use the NodeName and ServiceName }} {{! but we may as well use the NodeName and ServiceName }}
@ -101,8 +102,13 @@ as |item|}}
{{! so if we can ever get ServiceID from elsewhere we could save }} {{! so if we can ever get ServiceID from elsewhere we could save }}
{{! a HTTP request/long poll here }} {{! a HTTP request/long poll here }}
{{#if meta.data.ServiceID}} {{#if meta.data.ServiceID}}
{{! if we have a proxy then get the additional instance information }}
{{! for the proxy itself so if the service is called `backend` }}
{{! its likely to have a proxy service called `backend-sidecar-proxy` }}
{{! and this second request get the info for that instance and saves }}
{{! it into the `proxy` variable }}
<DataSource <DataSource
@src={{uri '/${partition}/${nspace}/${dc}/proxy-service-instance/${id}/${node}/${name}' @src={{uri '/${partition}/${nspace}/${dc}/service-instance/${id}/${node}/${name}'
(hash (hash
partition=route.params.partition partition=route.params.partition
nspace=route.params.nspace nspace=route.params.nspace
@ -112,7 +118,8 @@ as |item|}}
name=meta.data.ServiceName name=meta.data.ServiceName
) )
}} }}
@onchange={{action (mut proxy) value="data"}}/> @onchange={{action (mut proxy) value="data"}}
/>
{{/if}} {{/if}}
</DataSource> </DataSource>
{{/if}} {{/if}}
@ -129,7 +136,9 @@ as |item|}}
</h1> </h1>
<Consul::ExternalSource @item={{item}} @withInfo={{true}} /> <Consul::ExternalSource @item={{item}} @withInfo={{true}} />
<Consul::Kind @item={{item}} @withInfo={{true}} /> <Consul::Kind @item={{item}} @withInfo={{true}} />
{{#if (eq proxy.ServiceProxy.Mode 'transparent')}} {{! TODO: Looks like we can get this straight from item.Proxy.Mode }}
{{! the less we need `proxy` and `meta` the better }}
{{#if (eq meta.ServiceProxy.Mode 'transparent')}}
<Consul::TransparentProxy /> <Consul::TransparentProxy />
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
@ -149,27 +158,22 @@ as |item|}}
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="content"> <BlockSlot @name="content">
<TabNav @items={{ <TabNav @items={{
compact compact
(array (array
(hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks")) (hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks"))
(if (if (eq item.Service.Kind 'mesh-gateway') (hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")))
(eq item.Service.Kind 'mesh-gateway') (if proxy (hash label="Upstreams" href=(href-to "dc.services.instance.upstreams") selected=(is-href "dc.services.instance.upstreams")))
(hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")) "" (if proxy (hash label="Exposed Paths" href=(href-to "dc.services.instance.exposedpaths") selected=(is-href "dc.services.instance.exposedpaths")))
) (hash label="Tags & Meta" href=(href-to "dc.services.instance.metadata") selected=(is-href "dc.services.instance.metadata"))
(if proxy )
(hash label="Upstreams" href=(href-to "dc.services.instance.upstreams") selected=(is-href "dc.services.instance.upstreams")) }}
) />
(if proxy
(hash label="Exposed Paths" href=(href-to "dc.services.instance.exposedpaths") selected=(is-href "dc.services.instance.exposedpaths"))
)
(hash label="Tags & Meta" href=(href-to "dc.services.instance.metadata") selected=(is-href "dc.services.instance.metadata"))
)
}}/>
<Outlet <Outlet
@name={{routeName}} @name={{routeName}}
@model={{assign (hash @model={{assign (hash
proxy=proxy proxy=proxy
meta=meta
item=item item=item
) route.model}} ) route.model}}
as |o|> as |o|>

View File

@ -2,14 +2,14 @@
@name={{routeName}} @name={{routeName}}
as |route|> as |route|>
{{#let {{#let
route.model.item (entries route.model.item.Service.TaggedAddresses)
as |item|}} as |items|}}
<div class="tab-section"> <div class="tab-section">
{{#if item.Service.TaggedAddresses }} {{#if (gt items.length 0)}}
<TabularCollection <TabularCollection
data-test-addresses data-test-addresses
class="consul-tagged-addresses" class="consul-tagged-addresses"
@items={{entries item.Service.TaggedAddresses}} as |taggedAddress index| @items={{items}} as |taggedAddress index|
> >
<BlockSlot @name="header"> <BlockSlot @name="header">
<th>Tag</th> <th>Tag</th>

View File

@ -3,13 +3,17 @@
as |route|> as |route|>
{{#let {{#let
route.model.proxy route.model.proxy
as |proxy|}} route.model.meta
as |item proxy|}}
<div class="tab-section"> <div class="tab-section">
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}} {{#if (gt proxy.ServiceProxy.Expose.Paths.length 0)}}
<p> <p>
The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation. The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our <Action @href={{concat (env 'CONSUL_DOCS_URL') '/connect/registration/service-registration#expose-paths-configuration-reference'}} @external={{true}}>documentation</Action>.
</p> </p>
<Consul::ExposedPath::List @items={{proxy.Service.Proxy.Expose.Paths}} @address={{proxy.Address}} /> <Consul::ExposedPath::List
@items={{proxy.ServiceProxy.Expose.Paths}}
@address={{or item.Service.Address item.Node.Address}}
/>
{{else}} {{else}}
<EmptyState> <EmptyState>
<BlockSlot @name="body"> <BlockSlot @name="body">

View File

@ -27,7 +27,7 @@ as |route|>
) )
) )
route.model.item.MeshChecks (merge-checks (array route.model.item.Checks route.model.proxy.Checks) route.model.proxy.ServiceProxy.Expose.Checks)
as |sort filters items|}} as |sort filters items|}}
<div class="tab-section"> <div class="tab-section">

View File

@ -25,9 +25,10 @@ as |route|>
route.params.dc route.params.dc
route.model.proxy route.model.proxy
route.model.meta
route.model.proxy.Service.Proxy.Upstreams route.model.proxy.Service.Proxy.Upstreams
as |sort filters partition nspace dc proxy items|}} as |sort filters partition nspace dc proxy meta items|}}
{{#if (gt items.length 0)}} {{#if (gt items.length 0)}}
<input type="checkbox" id="toolbar-toggle" /> <input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar <Consul::UpstreamInstance::SearchBar
@ -40,29 +41,33 @@ as |route|>
@filter={{filters}} @filter={{filters}}
/> />
{{/if}} {{/if}}
{{#if (eq proxy.ServiceProxy.Mode 'transparent')}} {{! TODO: Looks like we can get this straight from item.Proxy.Mode }}
<Notice {{! the less we need `proxy` and `meta` the better }}
@type="warning" {{#if (eq meta.ServiceProxy.Mode 'transparent')}}
as |notice|> <Notice
<notice.Header> @type="warning"
<h3>{{t "routes.dc.services.instance.upstreams.tproxy-mode.header"}}</h3> as |notice|>
</notice.Header> <notice.Header>
<notice.Body> <h3>
<p> {{t "routes.dc.services.instance.upstreams.tproxy-mode.header"}}
{{t "routes.dc.services.instance.upstreams.tproxy-mode.body"}} </h3>
</p> </notice.Header>
</notice.Body> <notice.Body>
<notice.Footer> <p>
<p> {{t "routes.dc.services.instance.upstreams.tproxy-mode.body"}}
<Action </p>
@href={{concat (env 'CONSUL_DOCS_URL') '/connect/transparent-proxy'}} </notice.Body>
@external={{true}} <notice.Footer>
> <p>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.footer"}} <Action
</Action> @href={{concat (env 'CONSUL_DOCS_URL') '/connect/transparent-proxy'}}
</p> @external={{true}}
</notice.Footer> >
</Notice> {{t "routes.dc.services.instance.upstreams.tproxy-mode.footer"}}
</Action>
</p>
</notice.Footer>
</Notice>
{{/if}} {{/if}}
<DataCollection <DataCollection
@type="upstream-instance" @type="upstream-instance"

View File

@ -9,6 +9,7 @@ Below is a list of the most commonly used functions as bookmarklets followed by
| [Print Routing DSL](javascript:Routes()) | Print out Ember's Route DSL for the application | | [Print Routing DSL](javascript:Routes()) | Print out Ember's Route DSL for the application |
| [Save Current Scenario](javascript:Scenario()) | Opens a tab with links to allow you to create a bookmarklet or share a URL of your current scenario (your Consul UI relarted development/debug cookies) | | [Save Current Scenario](javascript:Scenario()) | Opens a tab with links to allow you to create a bookmarklet or share a URL of your current scenario (your Consul UI relarted development/debug cookies) |
| [Enable ACLs](javascript:Scenario('CONSUL_ACLS_ENABLE=1')) | Enable ACLs | | [Enable ACLs](javascript:Scenario('CONSUL_ACLS_ENABLE=1')) | Enable ACLs |
| [Enable TProxy](javascript:Scenario('CONSUL_TPROXY_ENABLE=1')) | Enable TProxy |
| [Enable Nspaces](javascript:Scenario('CONSUL_NSPACES_ENABLE=1')) | Enable Namespace Support | | [Enable Nspaces](javascript:Scenario('CONSUL_NSPACES_ENABLE=1')) | Enable Namespace Support |
| [Enable Partitions](javascript:Scenario('CONSUL_PARTITIONS_ENABLE=1')) | Enable Admin Partition Support | | [Enable Partitions](javascript:Scenario('CONSUL_PARTITIONS_ENABLE=1')) | Enable Admin Partition Support |
| [Enable SSO](javascript:Scenario('CONSUL_SSO_ENABLE=1')) | Enable SSO Support | | [Enable SSO](javascript:Scenario('CONSUL_SSO_ENABLE=1')) | Enable SSO Support |
@ -37,6 +38,7 @@ token/secret.
| -------- | ------------- | ----------- | | -------- | ------------- | ----------- |
| `CONSUL_ACLS_ENABLE` | false | Enable/disable ACLs support. | | `CONSUL_ACLS_ENABLE` | false | Enable/disable ACLs support. |
| `CONSUL_ACLS_LEGACY` | false | Enable/disable legacy ACLs support. | | `CONSUL_ACLS_LEGACY` | false | Enable/disable legacy ACLs support. |
| `CONSUL_TPROXY_ENABLE` | false | Enable/disable TProxy support globally (if not a service may have this applied randomly). |
| `CONSUL_NSPACES_ENABLE` | false | Enable/disable Namespace support. | | `CONSUL_NSPACES_ENABLE` | false | Enable/disable Namespace support. |
| `CONSUL_SSO_ENABLE` | false | Enable/disable SSO support. | | `CONSUL_SSO_ENABLE` | false | Enable/disable SSO support. |
| `CONSUL_OIDC_PROVIDER_URL` | undefined | Provide a OIDC provider URL for SSO. | | `CONSUL_OIDC_PROVIDER_URL` | undefined | Provide a OIDC provider URL for SSO. |

View File

@ -52,7 +52,7 @@ ${range(env('CONSUL_EXPOSED_COUNT', 3)).map((i) => `
`)} `)}
] ]
}, },
"Mode": "${fake.helpers.randomize(['', 'direct', 'transparent'])}", "Mode": "${env('CONSUL_TPROXY_ENABLE') ? `transparent` : fake.helpers.randomize(['', 'direct', 'transparent'])}",
"TransparentProxy": {}, "TransparentProxy": {},
"DestinationServiceName": "${location.pathname.slice(4)}" "DestinationServiceName": "${location.pathname.slice(4)}"
${ location.pathname.slice(4) === "service-0" ? ` ${ location.pathname.slice(4) === "service-0" ? `

View File

@ -0,0 +1,82 @@
[
{
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"NodeMeta": {
"consul-network-segment": ""
},
"ServiceKind": "connect-proxy",
"ServiceID": "backend-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"ServiceAddress": "",
"ServiceWeights": {
"Passing": 1,
"Warning": 1
},
"ServiceMeta": {},
"ServicePort": 21000,
"ServiceSocketPath": "",
"ServiceEnableTagOverride": false,
"ServiceProxy": {
"DestinationServiceName": "backend",
"DestinationServiceID": "backend",
"LocalServiceAddress": "127.0.0.1",
"LocalServicePort": 7000,
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"ServiceConnect": {},
"CreateIndex": 19,
"ModifyIndex": 19
},
{
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"NodeMeta": {
"consul-network-segment": ""
},
"ServiceKind": "connect-proxy",
"ServiceID": "backend-v2-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"ServiceAddress": "",
"ServiceWeights": {
"Passing": 1,
"Warning": 1
},
"ServiceMeta": {},
"ServicePort": 21001,
"ServiceSocketPath": "",
"ServiceEnableTagOverride": false,
"ServiceProxy": {
"DestinationServiceName": "backend",
"DestinationServiceID": "backend-v2",
"LocalServiceAddress": "127.0.0.1",
"LocalServicePort": 7001,
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"ServiceConnect": {},
"CreateIndex": 21,
"ModifyIndex": 21
}
]

View File

@ -0,0 +1,122 @@
[
{
"Node": {
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"Meta": {
"consul-network-segment": ""
},
"CreateIndex": 14,
"ModifyIndex": 17
},
"Service": {
"ID": "backend",
"Service": "backend",
"Tags": [],
"Address": "",
"Meta": null,
"Port": 7000,
"Weights": {
"Passing": 1,
"Warning": 1
},
"EnableTagOverride": false,
"Proxy": {
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"Connect": {},
"CreateIndex": 18,
"ModifyIndex": 18
},
"Checks": [
{
"Node": "node",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"ServiceTags": [],
"Type": "",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 14,
"ModifyIndex": 14
}
]
},
{
"Node": {
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"Meta": {
"consul-network-segment": ""
},
"CreateIndex": 14,
"ModifyIndex": 17
},
"Service": {
"ID": "backend-v2",
"Service": "backend",
"Tags": [],
"Address": "",
"Meta": null,
"Port": 7001,
"Weights": {
"Passing": 1,
"Warning": 1
},
"EnableTagOverride": false,
"Proxy": {
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"Connect": {},
"CreateIndex": 20,
"ModifyIndex": 20
},
"Checks": [
{
"Node": "node",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"ServiceTags": [],
"Type": "",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 14,
"ModifyIndex": 14
}
]
}
]

View File

@ -0,0 +1,204 @@
[
{
"Node": {
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"Meta": {
"consul-network-segment": ""
},
"CreateIndex": 14,
"ModifyIndex": 17
},
"Service": {
"Kind": "connect-proxy",
"ID": "backend-sidecar-proxy",
"Service": "backend-sidecar-proxy",
"Tags": [],
"Address": "",
"Meta": null,
"Port": 21000,
"Weights": {
"Passing": 1,
"Warning": 1
},
"EnableTagOverride": false,
"Proxy": {
"DestinationServiceName": "backend",
"DestinationServiceID": "backend",
"LocalServiceAddress": "127.0.0.1",
"LocalServicePort": 7000,
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"Connect": {},
"CreateIndex": 19,
"ModifyIndex": 19
},
"Checks": [
{
"Node": "node",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"ServiceTags": [],
"Type": "",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 14,
"ModifyIndex": 14
},
{
"Node": "node",
"CheckID": "service:backend-sidecar-proxy:1",
"Name": "Connect Sidecar Listening",
"Status": "critical",
"Notes": "",
"Output": "dial tcp 127.0.0.1:21000: connect: connection refused",
"ServiceID": "backend-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"Type": "tcp",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 19,
"ModifyIndex": 42
},
{
"Node": "node",
"CheckID": "service:backend-sidecar-proxy:2",
"Name": "Connect Sidecar Aliasing backend",
"Status": "critical",
"Notes": "",
"Output": "No checks found.",
"ServiceID": "backend-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"Type": "alias",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 19,
"ModifyIndex": 19
}
]
},
{
"Node": {
"ID": "237b9eeb-bba1-e6e3-3c99-c527d6d76cc0",
"Node": "node",
"Address": "172.17.0.2",
"Datacenter": "dc1",
"TaggedAddresses": {
"lan": "172.17.0.2",
"lan_ipv4": "172.17.0.2",
"wan": "172.17.0.2",
"wan_ipv4": "172.17.0.2"
},
"Meta": {
"consul-network-segment": ""
},
"CreateIndex": 14,
"ModifyIndex": 17
},
"Service": {
"Kind": "connect-proxy",
"ID": "backend-v2-sidecar-proxy",
"Service": "backend-sidecar-proxy",
"Tags": [],
"Address": "",
"Meta": null,
"Port": 21001,
"Weights": {
"Passing": 1,
"Warning": 1
},
"EnableTagOverride": false,
"Proxy": {
"DestinationServiceName": "backend",
"DestinationServiceID": "backend-v2",
"LocalServiceAddress": "127.0.0.1",
"LocalServicePort": 7001,
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"Connect": {},
"CreateIndex": 21,
"ModifyIndex": 21
},
"Checks": [
{
"Node": "node",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"ServiceTags": [],
"Type": "",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 14,
"ModifyIndex": 14
},
{
"Node": "node",
"CheckID": "service:backend-v2-sidecar-proxy:1",
"Name": "Connect Sidecar Listening",
"Status": "critical",
"Notes": "",
"Output": "dial tcp 127.0.0.1:21001: connect: connection refused",
"ServiceID": "backend-v2-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"Type": "tcp",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 21,
"ModifyIndex": 44
},
{
"Node": "node",
"CheckID": "service:backend-v2-sidecar-proxy:2",
"Name": "Connect Sidecar Aliasing backend-v2",
"Status": "passing",
"Notes": "",
"Output": "No checks found.",
"ServiceID": "backend-v2-sidecar-proxy",
"ServiceName": "backend-sidecar-proxy",
"ServiceTags": [],
"Type": "alias",
"Interval": "",
"Timeout": "",
"ExposedPort": 0,
"Definition": {},
"CreateIndex": 21,
"ModifyIndex": 21
}
]
}
]

View File

@ -0,0 +1,32 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('service:routlet', 'Integration | Routlet', {
// Specify the other units that are required for this test.
integration: true,
});
test('outletFor works', function(assert) {
const routlet = this.subject();
routlet.addOutlet('application', {
name: 'application'
});
routlet.addRoute('dc', {});
routlet.addOutlet('dc', {
name: 'dc'
});
routlet.addRoute('dc.services', {});
routlet.addOutlet('dc.services', {
name: 'dc.services'
});
routlet.addRoute('dc.services.instances', {});
let actual = routlet.outletFor('dc.services');
let expected = 'dc';
assert.equal(actual.name, expected);
actual = routlet.outletFor('dc');
expected = 'application';
assert.equal(actual.name, expected);
actual = routlet.outletFor('application');
expected = undefined;
assert.equal(actual, expected);
});