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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ export default {
passing: (item, value) => item.Status === value,
warning: (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) => {
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';
// TODO: This should be changed to ProxyInstance
export default class Proxy extends ServiceInstanceModel {
export default class ProxyServiceInstance extends ServiceInstanceModel {
@attr('string') uid;
@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 { computed, get } from '@ember/object';
import { computed } from '@ember/object';
import { or, filter, alias } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import mergeChecks from 'consul-ui/utils/merge-checks';
export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'Node.Node,Service.ID';
@ -28,8 +27,6 @@ export default class ServiceInstance extends Model {
@attr('string') uid;
@attr('string') Datacenter;
// ProxyInstance is the ember-data model relationship
@belongsTo('Proxy') ProxyInstance;
// Proxy is the actual JSON api response
@attr() Proxy;
@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 === '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')
get ExternalSources() {
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')
findInstanceBySlug(params, configuration) {
return this.findAllBySlug(params, configuration).then(function(items) {
let res = {};
if (get(items, 'length') > 0) {
let instance = items
.filterBy('ServiceProxy.DestinationServiceID', params.serviceId)
.findBy('NodeName', params.node);
async findInstanceBySlug(params, configuration) {
const items = await this.findAllBySlug(params, configuration);
let res = {};
if (get(items, 'length') > 0) {
let instance = items
.filterBy('ServiceProxy.DestinationServiceID', params.serviceId)
.findBy('NodeName', params.node);
if (instance) {
res = instance;
} else {
instance = items.findBy('ServiceProxy.DestinationServiceName', params.id);
if (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 { inject as service } from '@ember/service';
import { set } from '@ember/object';
import { ACCESS_READ } from 'consul-ui/abilities/base';
import dataSource from 'consul-ui/decorators/data-source';
const modelName = 'service-instance';
export default class ServiceInstanceService extends RepositoryService {
@service('repository/proxy') proxyRepo;
getModelName() {
return modelName;
}
@ -34,44 +32,6 @@ export default class ServiceInstanceService extends RepositoryService {
@dataSource('/:partition/:ns/:dc/service-instance/:serviceId/:node/:id')
async findBySlug(params, configuration = {}) {
if (typeof configuration.cursor !== 'undefined') {
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;
return super.findBySlug(...arguments);
}
}

View File

@ -75,21 +75,11 @@ export default class RoutletService extends Service {
return key;
}
addOutlet(name, outlet) {
outlets.set(name, outlet);
}
removeOutlet(name) {
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 {};
outletFor(routeName) {
const keys = [...outlets.keys()];
const pos = keys.indexOf(routeName);
const key = pos + 1;
return outlets.get(keys[key]);
}
/**
@ -149,20 +139,43 @@ export default class RoutletService extends Service {
};
}
addRoute(name, route) {
const keys = [...outlets.keys()];
const pos = keys.indexOf(name);
const key = pos + 1;
const outlet = outlets.get(keys[key]);
// 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;
}
}
addRoute(name, route) {
const outlet = this.outletFor(name);
if (typeof outlet !== 'undefined') {
route._model = outlet.model;
outlet.route = route;
// TODO: Try to avoid the double computation bug
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
)
}}
@onchange={{action (mut meta) value="data"}}
as |meta|>
{{! We only really need meta to get the correct ServiceID }}
{{! 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 }}
{{! a HTTP request/long poll here }}
{{#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
@src={{uri '/${partition}/${nspace}/${dc}/proxy-service-instance/${id}/${node}/${name}'
@src={{uri '/${partition}/${nspace}/${dc}/service-instance/${id}/${node}/${name}'
(hash
partition=route.params.partition
nspace=route.params.nspace
@ -112,7 +118,8 @@ as |item|}}
name=meta.data.ServiceName
)
}}
@onchange={{action (mut proxy) value="data"}}/>
@onchange={{action (mut proxy) value="data"}}
/>
{{/if}}
</DataSource>
{{/if}}
@ -129,7 +136,9 @@ as |item|}}
</h1>
<Consul::ExternalSource @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 />
{{/if}}
</BlockSlot>
@ -149,27 +158,22 @@ as |item|}}
{{/let}}
</BlockSlot>
<BlockSlot @name="content">
<TabNav @items={{
compact
(array
(hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks"))
(if
(eq item.Service.Kind 'mesh-gateway')
(hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")) ""
)
(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"))
)
}}/>
<TabNav @items={{
compact
(array
(hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks"))
(if (eq item.Service.Kind 'mesh-gateway') (hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")))
(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
@name={{routeName}}
@model={{assign (hash
proxy=proxy
meta=meta
item=item
) route.model}}
as |o|>

View File

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

View File

@ -3,13 +3,17 @@
as |route|>
{{#let
route.model.proxy
as |proxy|}}
route.model.meta
as |item proxy|}}
<div class="tab-section">
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
{{#if (gt proxy.ServiceProxy.Expose.Paths.length 0)}}
<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>
<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}}
<EmptyState>
<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|}}
<div class="tab-section">

View File

@ -25,9 +25,10 @@ as |route|>
route.params.dc
route.model.proxy
route.model.meta
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)}}
<input type="checkbox" id="toolbar-toggle" />
<Consul::UpstreamInstance::SearchBar
@ -40,29 +41,33 @@ as |route|>
@filter={{filters}}
/>
{{/if}}
{{#if (eq proxy.ServiceProxy.Mode 'transparent')}}
<Notice
@type="warning"
as |notice|>
<notice.Header>
<h3>{{t "routes.dc.services.instance.upstreams.tproxy-mode.header"}}</h3>
</notice.Header>
<notice.Body>
<p>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.body"}}
</p>
</notice.Body>
<notice.Footer>
<p>
<Action
@href={{concat (env 'CONSUL_DOCS_URL') '/connect/transparent-proxy'}}
@external={{true}}
>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.footer"}}
</Action>
</p>
</notice.Footer>
</Notice>
{{! 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')}}
<Notice
@type="warning"
as |notice|>
<notice.Header>
<h3>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.header"}}
</h3>
</notice.Header>
<notice.Body>
<p>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.body"}}
</p>
</notice.Body>
<notice.Footer>
<p>
<Action
@href={{concat (env 'CONSUL_DOCS_URL') '/connect/transparent-proxy'}}
@external={{true}}
>
{{t "routes.dc.services.instance.upstreams.tproxy-mode.footer"}}
</Action>
</p>
</notice.Footer>
</Notice>
{{/if}}
<DataCollection
@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 |
| [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 TProxy](javascript:Scenario('CONSUL_TPROXY_ENABLE=1')) | Enable TProxy |
| [Enable Nspaces](javascript:Scenario('CONSUL_NSPACES_ENABLE=1')) | Enable Namespace Support |
| [Enable Partitions](javascript:Scenario('CONSUL_PARTITIONS_ENABLE=1')) | Enable Admin Partition 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_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_SSO_ENABLE` | false | Enable/disable SSO support. |
| `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": {},
"DestinationServiceName": "${location.pathname.slice(4)}"
${ 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);
});