View Javadoc
1   /*
2    * Copyright (c) 2016 ingenieux Labs
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor;
18  
19  import com.google.common.base.Predicate;
20  import com.google.common.base.Predicates;
21  import com.google.common.collect.Collections2;
22  
23  import com.amazonaws.regions.Region;
24  import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest;
25  import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsRequest;
26  import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsResult;
27  import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription;
28  import com.amazonaws.services.elasticbeanstalk.model.EventDescription;
29  
30  import org.apache.commons.lang.Validate;
31  import org.apache.maven.plugin.MojoExecutionException;
32  
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Comparator;
36  import java.util.Date;
37  import java.util.List;
38  import java.util.Set;
39  import java.util.TreeSet;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  import br.com.ingenieux.mojo.beanstalk.AbstractBeanstalkMojo;
44  import br.com.ingenieux.mojo.beanstalk.cmd.BaseCommand;
45  import br.com.ingenieux.mojo.beanstalk.util.EnvironmentHostnameUtil;
46  
47  import static java.lang.String.format;
48  import static org.apache.commons.lang.StringUtils.defaultString;
49  import static org.apache.commons.lang.StringUtils.isNotBlank;
50  
51  public class WaitForEnvironmentCommand extends BaseCommand<WaitForEnvironmentContext, EnvironmentDescription> {
52  
53    /**
54     * Poll Interval
55     */
56    public static final long POLL_INTERVAL = 30 * 1000;
57    /**
58     * Magic Constant for Mins to MSEC
59     */
60    private static final long MINS_TO_MSEC = 60 * 1000;
61  
62    /**
63     * Constructor
64     *
65     * @param parentMojo parent mojo
66     */
67    public WaitForEnvironmentCommand(AbstractBeanstalkMojo parentMojo) throws MojoExecutionException {
68      super(parentMojo);
69    }
70  
71    public Collection<EnvironmentDescription> lookupInternal(WaitForEnvironmentContext context) {
72      List<Predicate<EnvironmentDescription>> envPredicates = getEnvironmentDescriptionPredicate(context);
73  
74      DescribeEnvironmentsRequest req = new DescribeEnvironmentsRequest().withApplicationName(context.getApplicationName()).withIncludeDeleted(true);
75  
76      final List<EnvironmentDescription> envs = parentMojo.getService().describeEnvironments(req).getEnvironments();
77  
78      return Collections2.filter(envs, Predicates.and(envPredicates));
79    }
80  
81    protected List<Predicate<EnvironmentDescription>> getEnvironmentDescriptionPredicate(WaitForEnvironmentContext context) {
82      // as well as those (which are used as predicate variables, thus being
83      // final)
84      final String environmentRef = context.getEnvironmentRef();
85      final String statusToWaitFor = defaultString(context.getStatusToWaitFor(), "!Terminated");
86      final String healthToWaitFor = context.getHealth();
87  
88      // Sanity Check
89      Validate.isTrue(isNotBlank(environmentRef), "EnvironmentRef is blank or null", environmentRef);
90  
91      // some argument juggling
92  
93      final boolean negated = statusToWaitFor.startsWith("!");
94  
95      // argument juggling
96  
97      List<Predicate<EnvironmentDescription>> result = new ArrayList<Predicate<EnvironmentDescription>>();
98  
99      Matcher hostMatcher = EnvironmentHostnameUtil.PATTERN_HOSTNAME.matcher(environmentRef);
100 
101     if (environmentRef.matches("e-\\p{Alnum}{10}")) {
102       result.add(
103           new Predicate<EnvironmentDescription>() {
104             @Override
105             public boolean apply(EnvironmentDescription t) {
106               return t.getEnvironmentId().equals(environmentRef);
107             }
108           });
109 
110       info("... with environmentId equal to '%s'", environmentRef);
111     } else if (hostMatcher.matches()) {
112       final Region region = parentMojo.getRegion();
113       final String cnamePrefix = hostMatcher.group("cnamePrefix");
114 
115       final Predicate<EnvironmentDescription> predicate = EnvironmentHostnameUtil.getHostnamePredicate(region, cnamePrefix);
116 
117       result.add(predicate);
118 
119       info(predicate.toString());
120     } else {
121       String tmpRE = Pattern.quote(environmentRef);
122 
123       if (environmentRef.endsWith("*")) {
124         tmpRE = format("^\\Q%s\\E.*", environmentRef.substring(0, -1 + environmentRef.length()));
125       }
126 
127       final String environmentRefNameRE = tmpRE;
128 
129       result.add(
130           new Predicate<EnvironmentDescription>() {
131             @Override
132             public boolean apply(EnvironmentDescription t) {
133               return t.getEnvironmentName().matches(environmentRefNameRE);
134             }
135           });
136 
137       info("... with environmentName matching re '%s'", environmentRefNameRE);
138     }
139 
140     {
141       // start building predicates with the status one - "![status]" must
142       // be equal to status or not status
143       final int offset = negated ? 1 : 0;
144       final String vStatusToWaitFor = statusToWaitFor.substring(offset);
145 
146       result.add(
147           new Predicate<EnvironmentDescription>() {
148             public boolean apply(EnvironmentDescription t) {
149 
150               boolean result = vStatusToWaitFor.equals(t.getStatus());
151 
152               if (negated) {
153                 result = !result;
154               }
155 
156               debug("testing status '%s' as equal as '%s' (negated? %s, offset: %d): %s", vStatusToWaitFor, t.getStatus(), negated, offset, result);
157 
158               return result;
159             }
160           });
161 
162       info("... with status %s set to '%s'", (negated ? "*NOT*" : " "), vStatusToWaitFor);
163     }
164 
165     {
166       if (isNotBlank(healthToWaitFor)) {
167         result.add(
168             new Predicate<EnvironmentDescription>() {
169               @Override
170               public boolean apply(EnvironmentDescription t) {
171                 return t.getHealth().equals(healthToWaitFor);
172               }
173             });
174 
175         info("... with health equal to '%s'", healthToWaitFor);
176       }
177     }
178     return result;
179   }
180 
181   public EnvironmentDescription executeInternal(WaitForEnvironmentContext context) throws Exception {
182     // Those are invariants
183     long timeoutMins = context.getTimeoutMins();
184 
185     Date expiresAt = new Date(System.currentTimeMillis() + MINS_TO_MSEC * timeoutMins);
186     Date lastMessageRecord = new Date();
187 
188     info("Environment Lookup");
189 
190     List<Predicate<EnvironmentDescription>> envPredicates = getEnvironmentDescriptionPredicate(context);
191     Predicate<EnvironmentDescription> corePredicate = envPredicates.get(0);
192     Predicate<EnvironmentDescription> fullPredicate = Predicates.and(envPredicates);
193 
194     do {
195       DescribeEnvironmentsRequest req = new DescribeEnvironmentsRequest().withApplicationName(context.getApplicationName()).withIncludeDeleted(true);
196 
197       final List<EnvironmentDescription> envs = parentMojo.getService().describeEnvironments(req).getEnvironments();
198 
199       Collection<EnvironmentDescription> validEnvironments = Collections2.filter(envs, fullPredicate);
200 
201       debug("There are %d environments", validEnvironments.size());
202 
203       if (1 == validEnvironments.size()) {
204         EnvironmentDescription foundEnvironment = validEnvironments.iterator().next();
205 
206         debug("Found environment %s", foundEnvironment);
207 
208         return foundEnvironment;
209       } else {
210         debug("Found %d environments. No good. Ignoring.", validEnvironments.size());
211 
212         for (EnvironmentDescription d : validEnvironments) {
213           debug(" ... %s", d);
214         }
215 
216         // ... but have we've got any closer match? If so, dump recent events
217 
218         Collection<EnvironmentDescription> foundEnvironments = Collections2.filter(envs, corePredicate);
219 
220         if (1 == foundEnvironments.size()) {
221           EnvironmentDescription foundEnvironment = foundEnvironments.iterator().next();
222 
223           DescribeEventsResult events =
224               service.describeEvents(
225                   new DescribeEventsRequest()
226                       .withApplicationName(foundEnvironment.getApplicationName())
227                       .withStartTime(new Date(1000 + lastMessageRecord.getTime()))
228                       .withEnvironmentId(foundEnvironment.getEnvironmentId())
229                       .withSeverity("TRACE"));
230 
231           Set<EventDescription> eventList = new TreeSet<EventDescription>(new EventDescriptionComparator());
232 
233           eventList.addAll(events.getEvents());
234 
235           for (EventDescription d : eventList) {
236             info("%s %s %s", d.getSeverity(), d.getEventDate(), d.getMessage());
237 
238             if (d.getSeverity().equals(("ERROR"))) {
239               throw new MojoExecutionException("Something went wrong in while waiting for the environment setup to complete : " + d.getMessage());
240             }
241             lastMessageRecord = d.getEventDate();
242           }
243         }
244       }
245 
246       sleepInterval(POLL_INTERVAL);
247     } while (!timedOutP(expiresAt));
248 
249     throw new MojoExecutionException("Timed out");
250   }
251 
252   boolean timedOutP(Date expiresAt) throws MojoExecutionException {
253     return expiresAt.before(new Date(System.currentTimeMillis()));
254   }
255 
256   public void sleepInterval(long pollInterval) {
257     debug("Sleeping for %d seconds", pollInterval / 1000);
258     try {
259       Thread.sleep(pollInterval);
260     } catch (InterruptedException e) {
261     }
262   }
263 
264   static class EventDescriptionComparator implements Comparator<EventDescription> {
265 
266     @Override
267     public int compare(EventDescription o1, EventDescription o2) {
268       return o1.getEventDate().compareTo(o2.getEventDate());
269     }
270   }
271 }