Coverage Report - com.jcabi.beanstalk.maven.plugin.Environment
 
Classes in this File Line Coverage Branch Coverage Complexity
Environment
44%
39/87
20%
12/58
2.765
Environment$1
66%
2/3
50%
1/2
2.765
Environment$AjcClosure1
100%
1/1
N/A
2.765
Environment$AjcClosure11
100%
1/1
N/A
2.765
Environment$AjcClosure13
0%
0/1
N/A
2.765
Environment$AjcClosure15
0%
0/1
N/A
2.765
Environment$AjcClosure17
0%
0/1
N/A
2.765
Environment$AjcClosure3
100%
1/1
N/A
2.765
Environment$AjcClosure5
100%
1/1
N/A
2.765
Environment$AjcClosure7
100%
1/1
N/A
2.765
Environment$AjcClosure9
100%
1/1
N/A
2.765
Environment$Barrier
N/A
N/A
2.765
 
 1  2
 /**
 2  
  * Copyright (c) 2012-2014, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.beanstalk.maven.plugin;
 31  
 
 32  
 import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk;
 33  
 import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionSetting;
 34  
 import com.amazonaws.services.elasticbeanstalk.model.ConfigurationSettingsDescription;
 35  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsRequest;
 36  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsResult;
 37  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest;
 38  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsResult;
 39  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsRequest;
 40  
 import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsResult;
 41  
 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription;
 42  
 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentInfoDescription;
 43  
 import com.amazonaws.services.elasticbeanstalk.model.EnvironmentInfoType;
 44  
 import com.amazonaws.services.elasticbeanstalk.model.EventDescription;
 45  
 import com.amazonaws.services.elasticbeanstalk.model.RequestEnvironmentInfoRequest;
 46  
 import com.amazonaws.services.elasticbeanstalk.model.RetrieveEnvironmentInfoRequest;
 47  
 import com.amazonaws.services.elasticbeanstalk.model.TerminateEnvironmentRequest;
 48  
 import com.amazonaws.services.elasticbeanstalk.model.TerminateEnvironmentResult;
 49  
 import com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentRequest;
 50  
 import com.amazonaws.services.elasticbeanstalk.model.UpdateEnvironmentResult;
 51  
 import com.jcabi.aspects.Loggable;
 52  
 import com.jcabi.aspects.Tv;
 53  
 import com.jcabi.log.Logger;
 54  
 import java.io.IOException;
 55  
 import java.net.URL;
 56  
 import java.util.Collection;
 57  
 import java.util.LinkedList;
 58  
 import java.util.List;
 59  
 import java.util.concurrent.TimeUnit;
 60  
 import javax.validation.constraints.NotNull;
 61  
 import lombok.EqualsAndHashCode;
 62  
 import org.apache.commons.io.IOUtils;
 63  
 
 64  
 /**
 65  
  * EBT environment.
 66  
  *
 67  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 68  
  * @version $Id$
 69  
  * @since 0.3
 70  
  * @checkstyle ClassDataAbstractionCoupling (500 lines)
 71  
  */
 72  0
 @EqualsAndHashCode(of = { "client", "eid" })
 73  
 @SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessiveImports" })
 74  
 @Loggable(Loggable.DEBUG)
 75  
 final class Environment {
 76  
 
 77  
     /**
 78  
      * For how long we can wait until env reaches certain status.
 79  
      */
 80  1
     private static final long DELAY_MS = TimeUnit.MINUTES.toMillis(Tv.THIRTY);
 81  
 
 82  
     /**
 83  
      * AWS beanstalk client.
 84  
      */
 85  
     private final transient AWSElasticBeanstalk client;
 86  
 
 87  
     /**
 88  
      * Environment ID.
 89  
      */
 90  
     private final transient String eid;
 91  
 
 92  
     /**
 93  
      * Public ctor.
 94  
      * @param clnt The client
 95  
      * @param idnt Environment ID
 96  
      */
 97  
     protected Environment(@NotNull final AWSElasticBeanstalk clnt,
 98  4
         @NotNull final String idnt) {
 99  4
         this.client = clnt;
 100  4
         this.eid = idnt;
 101  4
         final EnvironmentDescription desc = this.description();
 102  4
         final String template = desc.getTemplateName();
 103  4
         if (template != null) {
 104  0
             final DescribeConfigurationSettingsResult res =
 105  
                 this.client.describeConfigurationSettings(
 106  
                     new DescribeConfigurationSettingsRequest()
 107  
                         .withApplicationName(desc.getApplicationName())
 108  
                         .withTemplateName(template)
 109  
                 );
 110  
             for (final ConfigurationSettingsDescription config
 111  0
                 : res.getConfigurationSettings()) {
 112  0
                 Logger.debug(
 113  
                     Environment.class,
 114  
                     "Environment '%s/%s/%s' settings:",
 115  
                     config.getApplicationName(), config.getEnvironmentName()
 116  
                 );
 117  
                 for (final ConfigurationOptionSetting opt
 118  0
                     : config.getOptionSettings()) {
 119  0
                     Logger.debug(
 120  
                         Environment.class,
 121  
                         "  %s/%s: %s",
 122  
                         opt.getNamespace(), opt.getOptionName(), opt.getValue()
 123  
                     );
 124  0
                 }
 125  0
             }
 126  
         }
 127  4
     }
 128  
 
 129  
     /**
 130  
      * {@inheritDoc}
 131  
      */
 132  
     @Override
 133  
     public String toString() {
 134  2
         final EnvironmentDescription desc = this.description();
 135  2
         return String.format(
 136  
             "%s/%s/%s",
 137  
             desc.getEnvironmentName(), desc.getEnvironmentId(), desc.getCNAME()
 138  
         );
 139  
     }
 140  
 
 141  
     /**
 142  
      * Is it primary environment in the application?
 143  
      * @return TRUE if this environment is attached to the main CNAME
 144  
      */
 145  
     public boolean primary() {
 146  2
         final EnvironmentDescription desc = this.description();
 147  1
         final String prefix = String.format("%s.", desc.getApplicationName());
 148  1
         final boolean primary = this.stable()
 149  
             && desc.getCNAME().startsWith(prefix);
 150  1
         if (primary) {
 151  0
             Logger.info(
 152  
                 this,
 153  
                 "Environment '%s' considered primary", this
 154  
             );
 155  
         } else {
 156  1
             Logger.info(
 157  
                 this,
 158  
                 // @checkstyle LineLength (1 line)
 159  
                 "Environment '%s' considered secondary since its CNAME doesn't start with '%s'",
 160  
                 this, prefix
 161  
             );
 162  
         }
 163  1
         return primary;
 164  
     }
 165  
 
 166  
     /**
 167  
      * Get environment name.
 168  
      * @return Name of it
 169  
      */
 170  
     public String name() {
 171  2
         return this.description().getEnvironmentName();
 172  
     }
 173  
 
 174  
     /**
 175  
      * Environment is in Green health?
 176  
      * @return TRUE if environment is in Green health
 177  
      */
 178  
     public boolean green() {
 179  2
         return this.stable() && "Green".equals(this.description().getHealth());
 180  
     }
 181  
 
 182  
     /**
 183  
      * Wait for stable state, and return TRUE if achieved or FALSE if not.
 184  
      * @return TRUE if environment is stable
 185  
      */
 186  
     public boolean stable() {
 187  10
         return this.until(
 188  5
             new Environment.Barrier() {
 189  
                 @Override
 190  
                 public String message() {
 191  0
                     return "stable state";
 192  
                 }
 193  
                 @Override
 194  
                 public boolean allow(final EnvironmentDescription desc) {
 195  5
                     return !desc.getStatus().matches(".*ing$");
 196  
                 }
 197  
             }
 198  
         );
 199  
     }
 200  
 
 201  
     /**
 202  
      * Is it terminated?
 203  
      * @return Yes or no
 204  
      */
 205  
     public boolean terminated() {
 206  4
         return this.stable()
 207  
             && "Terminated".equals(this.description().getStatus());
 208  
     }
 209  
 
 210  
     /**
 211  
      * Terminate environment.
 212  
      */
 213  
     public void terminate() {
 214  2
         if (!this.stable()) {
 215  0
             throw new DeploymentException(
 216  
                 String.format(
 217  
                     "env '%s' is not stable, can't terminate",
 218  
                     this.eid
 219  
                 )
 220  
             );
 221  
         }
 222  1
         if (!this.terminated()) {
 223  1
             final TerminateEnvironmentResult res =
 224  
                 this.client.terminateEnvironment(
 225  
                     new TerminateEnvironmentRequest()
 226  
                         .withEnvironmentId(this.eid)
 227  
                         .withTerminateResources(true)
 228  
                 );
 229  1
             Logger.info(
 230  
                 this,
 231  
                 "Environment '%s/%s/%s' is terminated (label:'%s', status:%s)",
 232  
                 res.getApplicationName(), res.getEnvironmentName(),
 233  
                 res.getEnvironmentId(),
 234  
                 res.getVersionLabel(), res.getStatus()
 235  
             );
 236  
         }
 237  1
     }
 238  
 
 239  
     /**
 240  
      * Get latest events.
 241  
      * @return Collection of events
 242  
      */
 243  
     public String[] events() {
 244  0
         if (!this.stable()) {
 245  0
             throw new DeploymentException(
 246  
                 String.format(
 247  
                     "env '%s' is not stable, can't get list of events",
 248  
                     this.eid
 249  
                 )
 250  
             );
 251  
         }
 252  0
         final DescribeEventsResult res = this.client.describeEvents(
 253  
             new DescribeEventsRequest().withEnvironmentId(this.eid)
 254  
         );
 255  0
         final Collection<String> events = new LinkedList<String>();
 256  0
         for (final EventDescription desc : res.getEvents()) {
 257  0
             events.add(
 258  
                 String.format("[%s]: %s", desc.getSeverity(), desc.getMessage())
 259  
             );
 260  0
         }
 261  0
         return events.toArray(new String[events.size()]);
 262  
     }
 263  
 
 264  
     /**
 265  
      * Tail log.
 266  
      * @return Full text of tail log from the environment
 267  
      */
 268  
     public String tail() {
 269  0
         if (!this.stable()) {
 270  0
             throw new DeploymentException(
 271  
                 String.format(
 272  
                     "env '%s' is not stable, can't get TAIL report",
 273  
                     this.eid
 274  
                 )
 275  
             );
 276  
         }
 277  0
         if (this.terminated()) {
 278  0
             throw new DeploymentException(
 279  
                 String.format(
 280  
                     "env '%s' is terminated, can't get TAIL report",
 281  
                     this.eid
 282  
                 )
 283  
             );
 284  
         }
 285  0
         this.client.requestEnvironmentInfo(
 286  
             new RequestEnvironmentInfoRequest()
 287  
                 .withEnvironmentId(this.eid)
 288  
                 .withInfoType(EnvironmentInfoType.Tail)
 289  
         );
 290  0
         final RetrieveEnvironmentInfoRequest req =
 291  
             new RetrieveEnvironmentInfoRequest()
 292  
                 .withEnvironmentId(this.eid)
 293  
                 .withInfoType(EnvironmentInfoType.Tail);
 294  
         List<EnvironmentInfoDescription> infos;
 295  0
         final long start = System.currentTimeMillis();
 296  
         do {
 297  0
             if (System.currentTimeMillis() - start > Environment.DELAY_MS) {
 298  0
                 throw new DeploymentException(
 299  
                     String.format(
 300  
                         "env '%s' doesn't report its TAIL, time out",
 301  
                         this.eid
 302  
                     )
 303  
                 );
 304  
             }
 305  0
             Logger.info(
 306  
                 this,
 307  
                 "Waiting for TAIL report of %s",
 308  
                 this.eid
 309  
             );
 310  0
             infos = this.client
 311  
                 .retrieveEnvironmentInfo(req)
 312  
                 .getEnvironmentInfo();
 313  0
         } while (infos.isEmpty());
 314  0
         final EnvironmentInfoDescription desc = infos.get(0);
 315  
         try {
 316  0
             return IOUtils.toString(new URL(desc.getMessage()).openStream());
 317  0
         } catch (final IOException ex) {
 318  0
             throw new IllegalStateException(ex);
 319  
         }
 320  
     }
 321  
 
 322  
     /**
 323  
      * Update this environment with a new version.
 324  
      * @param version The version to update to
 325  
      */
 326  
     public void update(final Version version) {
 327  0
         final UpdateEnvironmentResult res = this.client.updateEnvironment(
 328  
             new UpdateEnvironmentRequest()
 329  
                 .withEnvironmentId(this.eid)
 330  
                 .withVersionLabel(version.label())
 331  
         );
 332  0
         Logger.info(
 333  
             this,
 334  
             "Environment '%s' updated to '%s'",
 335  
             res.getEnvironmentId(), res.getVersionLabel()
 336  
         );
 337  0
     }
 338  
 
 339  
     /**
 340  
      * Get environment description of this.
 341  
      * @return The description
 342  
      */
 343  
     private EnvironmentDescription description() {
 344  16
         final DescribeEnvironmentsResult res = this.client.describeEnvironments(
 345  
             new DescribeEnvironmentsRequest()
 346  
                 .withEnvironmentIds(this.eid)
 347  
         );
 348  16
         if (res.getEnvironments().isEmpty()) {
 349  0
             throw new DeploymentException(
 350  
                 String.format("environment '%s' not found", this.eid)
 351  
             );
 352  
         }
 353  16
         final EnvironmentDescription desc = res.getEnvironments().get(0);
 354  16
         Logger.debug(
 355  
             this,
 356  
             // @checkstyle LineLength (1 line)
 357  
             "ID=%s, env=%s, app=%s, CNAME=%s, label=%s, template=%s, status=%s, health=%s",
 358  
             desc.getEnvironmentId(), desc.getEnvironmentName(),
 359  
             desc.getApplicationName(), desc.getCNAME(),
 360  
             desc.getVersionLabel(), desc.getTemplateName(), desc.getStatus(),
 361  
             desc.getHealth()
 362  
         );
 363  16
         return desc;
 364  
     }
 365  
 
 366  
     /**
 367  
      * Wait for the barrier to pass.
 368  
      * @param barrier The barrier
 369  
      * @return TRUE if passed, FALSE if timeout
 370  
      */
 371  
     private boolean until(final Environment.Barrier barrier) {
 372  5
         boolean passed = false;
 373  5
         final long start = System.currentTimeMillis();
 374  
         while (true) {
 375  5
             final EnvironmentDescription desc = this.description();
 376  5
             if (barrier.allow(desc)) {
 377  5
                 passed = true;
 378  5
                 Logger.info(
 379  
                     this,
 380  
                     "Environment '%s/%s/%s': health=%s, status=%s",
 381  
                     desc.getApplicationName(), desc.getEnvironmentName(),
 382  
                     desc.getEnvironmentId(), desc.getHealth(),
 383  
                     desc.getStatus()
 384  
                 );
 385  5
                 break;
 386  
             }
 387  0
             Logger.info(
 388  
                 this,
 389  
                 // @checkstyle LineLength (1 line)
 390  
                 "Environment '%s/%s/%s': health=%s, status=%s (waiting for %s, %[ms]s)",
 391  
                 desc.getApplicationName(), desc.getEnvironmentName(),
 392  
                 desc.getEnvironmentId(), desc.getHealth(),
 393  
                 desc.getStatus(), barrier.message(),
 394  
                 System.currentTimeMillis() - start
 395  
             );
 396  0
             if (System.currentTimeMillis() - start > Environment.DELAY_MS) {
 397  0
                 Logger.warn(
 398  
                     this,
 399  
                     "Environment failed to reach '%s' after %[ms]s",
 400  
                     barrier.message(), System.currentTimeMillis() - start
 401  
                 );
 402  0
                 break;
 403  
             }
 404  
             try {
 405  0
                 TimeUnit.MINUTES.sleep(1);
 406  0
             } catch (final InterruptedException ex) {
 407  0
                 Thread.currentThread().interrupt();
 408  0
                 throw new DeploymentException(ex);
 409  0
             }
 410  0
         }
 411  5
         return passed;
 412  
     }
 413  
 
 414  
     /**
 415  
      * Barrier before the next operation.
 416  
      */
 417  
     private interface Barrier {
 418  
         /**
 419  
          * Can we continue?
 420  
          * @param desc Description of environment
 421  
          * @return TRUE if we can continue, FALSE if extra cycle of waiting
 422  
          *  is required
 423  
          */
 424  
         boolean allow(EnvironmentDescription desc);
 425  
         /**
 426  
          * What are we waiting for?
 427  
          * @return Message to show in log
 428  
          */
 429  
         String message();
 430  
     }
 431  
 
 432  
 }