1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing,
13+ * software distributed under the License is distributed on an
14+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+ * KIND, either express or implied. See the License for the
16+ * specific language governing permissions and limitations
17+ * under the License.
18+ */
19+ package org .apache .brooklyn .test .framework ;
20+
21+ import static org .apache .brooklyn .test .framework .TestFrameworkAssertions .getAssertions ;
22+
23+ import java .net .URI ;
24+ import java .net .URL ;
25+ import java .util .Collection ;
26+ import java .util .List ;
27+ import java .util .Map ;
28+ import java .util .Set ;
29+
30+ import org .apache .brooklyn .api .entity .Entity ;
31+ import org .apache .brooklyn .api .location .Location ;
32+ import org .apache .brooklyn .api .sensor .AttributeSensor ;
33+ import org .apache .brooklyn .core .entity .Attributes ;
34+ import org .apache .brooklyn .core .entity .lifecycle .Lifecycle ;
35+ import org .apache .brooklyn .core .entity .lifecycle .ServiceStateLogic ;
36+ import org .apache .brooklyn .core .sensor .Sensors ;
37+ import org .apache .brooklyn .test .Asserts ;
38+ import org .apache .brooklyn .util .core .flags .TypeCoercions ;
39+ import org .apache .brooklyn .util .exceptions .Exceptions ;
40+ import org .apache .brooklyn .util .guava .Maybe ;
41+ import org .apache .brooklyn .util .net .Networking ;
42+ import org .apache .brooklyn .util .text .Strings ;
43+ import org .apache .brooklyn .util .time .Duration ;
44+ import org .slf4j .Logger ;
45+ import org .slf4j .LoggerFactory ;
46+
47+ import com .google .common .base .Objects ;
48+ import com .google .common .base .Supplier ;
49+ import com .google .common .collect .ImmutableMap ;
50+ import com .google .common .collect .ImmutableSet ;
51+ import com .google .common .collect .Lists ;
52+ import com .google .common .collect .Sets ;
53+ import com .google .common .net .HostAndPort ;
54+
55+ /**
56+ * {@inheritDoc}
57+ */
58+ public class TestEndpointReachableImpl extends TargetableTestComponentImpl implements TestEndpointReachable {
59+
60+ private static final Logger LOG = LoggerFactory .getLogger (TestEndpointReachableImpl .class );
61+
62+ @ Override
63+ public void start (Collection <? extends Location > locations ) {
64+ if (!getChildren ().isEmpty ()) {
65+ throw new RuntimeException (String .format ("The entity [%s] cannot have child entities" , getClass ().getName ()));
66+ }
67+ ServiceStateLogic .setExpectedState (this , Lifecycle .STARTING );
68+ final String endpoint = getConfig (ENDPOINT );
69+ final Object endpointSensor = getConfig (ENDPOINT_SENSOR );
70+ final Duration timeout = getConfig (TIMEOUT );
71+ final List <Map <String , Object >> assertions = getAssertions (this , ASSERTIONS );
72+
73+ final Entity target = resolveTarget ();
74+
75+ if (endpoint == null && endpointSensor == null ) {
76+ throw new RuntimeException (String .format ("The entity [%s] must be configured with one of endpoint or endpointSensor" , getClass ().getName ()));
77+ } else if (endpoint != null && endpointSensor != null ) {
78+ throw new RuntimeException (String .format ("The entity [%s] must be configured with only one of endpoint or endpointSensor" , getClass ().getName ()));
79+ }
80+
81+ final Supplier <HostAndPort > supplier = new Supplier <HostAndPort >() {
82+ @ Override
83+ public HostAndPort get () {
84+ Object val ;
85+ if (endpoint != null ) {
86+ val = endpoint ;
87+ } else if (endpointSensor instanceof AttributeSensor ) {
88+ val = target .sensors ().get ((AttributeSensor <?>)endpointSensor );
89+ } else if (endpointSensor instanceof CharSequence ) {
90+ AttributeSensor <Object > sensor = Sensors .newSensor (Object .class , ((CharSequence )endpointSensor ).toString ());
91+ val = target .sensors ().get (sensor );
92+ } else {
93+ throw new IllegalArgumentException (String .format ("The entity [%s] has endpointSensor of invalid type %s [%s]" , getClass ().getName (), endpointSensor .getClass ().getName (), endpointSensor ));
94+ }
95+ return (val == null ) ? null : toHostAndPort (val );
96+ }
97+ };
98+ if (endpoint != null ) {
99+ // fail-fast if have a static endpoint value
100+ supplier .get ();
101+ }
102+
103+ try {
104+ Asserts .succeedsEventually (ImmutableMap .of ("timeout" , timeout ), new Runnable () {
105+ @ Override
106+ public void run () {
107+ HostAndPort val = supplier .get ();
108+ Asserts .assertNotNull (val );
109+ assertSucceeds (assertions , val );
110+ }});
111+ sensors ().set (Attributes .SERVICE_UP , true );
112+ ServiceStateLogic .setExpectedState (this , Lifecycle .RUNNING );
113+
114+ } catch (Throwable t ) {
115+ LOG .info ("{} [{}] test failed" , this , endpoint != null ? endpoint : endpointSensor );
116+ sensors ().set (Attributes .SERVICE_UP , false );
117+ ServiceStateLogic .setExpectedState (this , Lifecycle .ON_FIRE );
118+ throw Exceptions .propagate (t );
119+ }
120+ }
121+
122+ protected void assertSucceeds (List <Map <String , Object >> assertions , HostAndPort endpoint ) {
123+ Maybe <Object > checkReachableMaybe = getOnlyAssertionsValue (assertions , REACHABLE_KEY );
124+ boolean checkReachable = checkReachableMaybe .isAbsentOrNull () || Boolean .TRUE .equals (TypeCoercions .coerce (checkReachableMaybe .get (), Boolean .class ));
125+ boolean reachable = Networking .isReachable (endpoint );
126+ Asserts .assertEquals (reachable , checkReachable , endpoint +" " +(reachable ? "" : "not " )+"reachable" );
127+ }
128+
129+ /**
130+ * Finds the value for the given key in one of the maps (or {@link Maybe#absent()} if not found).
131+ *
132+ * @throws IllegalArgumentException if multiple conflicts values for the key, or if there are other (unexpected) keys.
133+ */
134+ protected Maybe <Object > getOnlyAssertionsValue (List <Map <String , Object >> assertions , String key ) {
135+ Maybe <Object > result = Maybe .absent ();
136+ Set <String > keys = Sets .newLinkedHashSet ();
137+ boolean foundConflictingDuplicate = false ;
138+ if (assertions != null ) {
139+ for (Map <String , Object > assertionMap : assertions ) {
140+ if (assertionMap .containsKey (key )) {
141+ Object val = assertionMap .get (REACHABLE_KEY );
142+ if (result .isPresent () && !Objects .equal (result .get (), val )) {
143+ foundConflictingDuplicate = true ;
144+ } else {
145+ result = Maybe .of (val );
146+ }
147+ }
148+ keys .addAll (assertionMap .keySet ());
149+ }
150+ }
151+ Set <String > unhandledKeys = Sets .difference (keys , ImmutableSet .of (key ));
152+ if (foundConflictingDuplicate ) {
153+ throw new IllegalArgumentException ("Multiple conflicting values for assertion '" +key +"' in " +this );
154+ } else if (unhandledKeys .size () > 0 ) {
155+ throw new IllegalArgumentException ("Unknown assertions " +unhandledKeys +" in " +this );
156+ }
157+ return result ;
158+ }
159+
160+ protected HostAndPort toHostAndPort (Object endpoint ) {
161+ if (endpoint == null ) {
162+ throw new IllegalArgumentException (String .format ("The entity [%s] has no endpoint" , getClass ().getName ()));
163+ } else if (endpoint instanceof String ) {
164+ return toHostAndPort ((String )endpoint );
165+ } else if (endpoint instanceof URI ) {
166+ return toHostAndPort (((URI )endpoint ).toString ());
167+ } else if (endpoint instanceof URL ) {
168+ return toHostAndPort (((URL )endpoint ).toString ());
169+ } else if (endpoint instanceof HostAndPort ) {
170+ return (HostAndPort )endpoint ;
171+ } else {
172+ throw new IllegalArgumentException (String .format ("The entity [%s] has endpoint of invalid type %s [%s]" , getClass ().getName (), endpoint .getClass ().getName (), endpoint ));
173+ }
174+ }
175+
176+ protected HostAndPort toHostAndPort (String endpoint ) {
177+ if (Strings .isEmpty (endpoint )) {
178+ throw new IllegalArgumentException (String .format ("The entity [%s] has no endpoint" , getClass ().getName ()));
179+ }
180+ try {
181+ URI uri = URI .create (endpoint );
182+ int port ;
183+ if (uri .getPort () != -1 ) {
184+ port = uri .getPort ();
185+ } else {
186+ if ("http" .equalsIgnoreCase (uri .getScheme ())) {
187+ port = 80 ;
188+ } else if ("https" .equalsIgnoreCase (uri .getScheme ())) {
189+ port = 443 ;
190+ } else {
191+ throw new IllegalArgumentException (String .format ("The entity [%s] with endpoint [%s] has no port" , getClass ().getName (), endpoint ));
192+ }
193+ }
194+ return HostAndPort .fromParts (uri .getHost (), port );
195+ } catch (IllegalArgumentException e ) {
196+ // Not a URL; fall-back to host-and-port
197+ }
198+
199+ HostAndPort result = HostAndPort .fromString (endpoint );
200+ if (!result .hasPort ()) {
201+ throw new IllegalArgumentException (String .format ("The entity [%s] with endpoint [%s] has no port" , getClass ().getName (), endpoint ));
202+ }
203+ return result ;
204+ }
205+
206+ /**
207+ * {@inheritDoc}
208+ */
209+ public void stop () {
210+ ServiceStateLogic .setExpectedState (this , Lifecycle .STOPPED );
211+ sensors ().set (Attributes .SERVICE_UP , false );
212+ }
213+
214+ /**
215+ * {@inheritDoc}
216+ */
217+ public void restart () {
218+ final Collection <Location > locations = Lists .newArrayList (getLocations ());
219+ stop ();
220+ start (locations );
221+ }
222+
223+ }
0 commit comments