Groovy DevOps in the Cloud

Andrey Adamovich, Aestas/IT

Groovy DevOps in the Cloud

About me

Andrey Adamovich

What's this presentation about?

Our take on:

  • DevOps
  • Server Provisioning
  • Continuous Integration
  • Continuous Delivery

Technologies

Developers + Operations = ?

Silos

Silos

Conflicts

Risk

Traditional

Agile

Agile

What is DevOps?

DevOps

It's not about tools!

It's about culture and process!

But without tools...

...it's definitely harder!

DevOps imply automation!

DevOps imply structure!

Infrastructure as Code

Automate the provisioning and maintenance of servers:

  • Build from source control
  • Utilize Open Source tools
  • Ensure testability

Configuration propagation

Automation 1

Changes

No Manual Changes!

Building an automation toolkit

  • Automation is key
  • We are JVM hackers
  • Fragmented ecosystem
  • Full-stack approach

Sshoogr

Sshoogr logo

Sshoogr Features

  • Remote command execution
  • File uploading/downloading
  • Tunneling

Sshoogr Example I

remoteSession {
  url = 'user2:654321@localhost:2222'
  exec 'rm -rf /tmp/*'
  exec 'touch /var/lock/my.pid'
  remoteFile('/var/my.conf').text = "enabled=true"
}

Sshoogr Example II

remoteSession {
  scp {
    from { localDir "$buildDir/application" }
    into { remoteDir '/var/bea/domain/application' }
  }
}

Sshoogr Example III

def result = exec(command: '/usr/bin/mycmd',
  failOnError: false, showOutput: false)
if (result.exitStatus == 1) {
  result.output.eachLine { line ->
    if (line.contains('WARNING')) {
      throw new RuntimeException("Warning!!!")
    }
  }
}

Why Groovy?

  • Groovy is perfect choice for scripting
  • Very mature, concise syntax
  • Extremely easy to produce DSL
  • We wrote a book about it!

Shameless plug

Book Cover

Puppet

Why Puppet?

  • More mature than competition
  • Large community
  • Readable DSL
  • No need to learn Ruby ;)

Continuous Integration

Continous integration

Why Jenkins?

  • De-facto standard
  • Stable
  • There is a plugin for that!

Gradle

Gradle Example I

task uploadModules << {
  remoteSession {
    exec 'rm -rf /tmp/repo.zip'
    scp {
      from { localFile "${buildDir}/repo.zip" }
      into { remoteDir "/root" }
    }
    ...

Gradle Example I

    ...
    exec 'rm -rf /etc/puppet/modules'
    exec 'unzip /tmp/repo.zip -d /etc/puppet/modules'
  }
}

Gradle Example II

task puppetApply(dependsOn: uploadModules) << {
  remoteSession {
    scp {
      from { localFile "${buildDir}/setup.pp" }
      into { remoteDir "/tmp" }
    }
    exec 'puppet apply /tmp/setup.pp'
  }
}

In the meanwhile...

  • We started developing complex Puppet modules
  • Modules needs proper testing
  • ...on different platforms

Do you test, right?

  • How to test this stuff?
  • How to reuse a JUnit approach to testing?
  • We wanted things to be SIMPLE!

PuppetUnit

PUnit logo

PUnit

  • Simple testing tool for verifying remote server state
  • Uses sshoogr and JUnit
  • Reuse reporting features of JUnit and Jenkins
  • As simple as ...

PUnit Example I

class DerbyInstallTest 
    extends BasePuppetIntegrationTest {
  @Before
  void installDerby() {
    apply("include derby")
  }
  ...
}

PUnit Example II

@Test
def void ensureDerbyRunning() {
  command('service derby status > derbystatus.log')
  assertTrue fileText("/root/derbystatus.log")
               .contains('Derby')
  assertTrue fileText("/root/derbystatus.log")
               .contains('is running.')
}

PUnit Example III

@Test
def void ensureCanConnect() {
  Thread.sleep(10000)
  uploadScript()
  command('/opt/derby/db-derby-10.9.1.0-bin/bin/ij ' + 
          'testDataScript.sql > derbytest.log')
  ...

PUnit Example III

  ...
  // Check if the log of the insert 
  // operation contains the word ERROR.
  assertFalse(
    "The script should return at least one error",
    fileText("/root/derbytest.log")
      .contains('ERROR')
  )
  ...

PUnit Example III

  ...
  // Check on data that was inserted into a table.
  assertTrue(
    "The log should contain a SELECT result",
    fileText("/root/derbytest.log")
     .contains('Grand Ave.')
  )
}

PUnit

Puppet Build

More examples

session {
  tunnel ('127.0.0.1', 8080) { int localPort ->

    // Login to Jenkins.
    def driver = new HtmlUnitDriver(false)
    driver.manage().timeouts().pageLoadTimeout(300, TimeUnit.SECONDS).implicitlyWait(30, TimeUnit.SECONDS)
    driver.get("http://127.0.0.1:${localPort}/login")
    ...

More examples

    ...
    def input = driver.findElement(By.name('j_username'))
    input.sendKeys('john')
    input = driver.findElement(By.name('j_password'))
    input.sendKeys('123456')
    input.submit()
    ...

More examples

    ...
    // Verify login.
    def wait = new WebDriverWait(driver, 30)
    wait.until ExpectedConditions.presenceOfElementLocated (By.linkText('John Doe'))
    ...
  }
}

More examples

session {
  tunnel ('127.0.0.1', 80) { int localPort ->

    // Initilize repository connection data.
    DAVRepositoryFactory.setup()
    def url = SVNURL.create('http', null, '127.0.0.1', localPort, 'repos/cafebabe', true)
    def repository = SVNRepositoryFactory.create(url)
    println "Verifying SVN repository at ${url}"
    ...

More examples

    ...
    // Setup credentials.
    def authManager = SVNWCUtil.createDefaultAuthenticationManager('joe', '123456')
    repository.setAuthenticationManager(authManager)

    // Verify repository is at revision 0.
    assertEquals 0, repository.getLatestRevision()
    ...

More examples

    ...
    // Commit first revision.
    ISVNEditor editor = repository.getCommitEditor("Initial commit.", null)
    editor.with {
      openRoot(-1)
      addFile('dummy.txt', null, -1)
      applyTextDelta('dummy.txt', null)
      def deltaGenerator = new SVNDeltaGenerator()
      ...

More examples

      ...
      def checksum = deltaGenerator.sendDelta('dummy.txt', new ByteArrayInputStream("data".getBytes()), editor, true) 
      closeFile('dummy.txt', checksum)
      def commitInfo = closeEdit()
      println commitInfo
    }
    ...

More examples

    ...
    // Verify repository is at revision 1 now.
    assertEquals 1, repository.getLatestRevision()

  }
}

Next problem?

Scalability

  • How do we test on different OS?
  • How do we run parallel tests on multiple architectures?
  • How do we avoid selling our houses?

Amazon Web Services

Elastic Compute Cloud

  • Mature
  • Great API
  • Virtual hardware variety
  • OS variety

Gramazon

Gramazon Logo

Gramazon

  • Groovy based API for interacting with EC2
  • Integrates with the rest of the stack

Gramazon Example I

task startInstance(type: StartInstance) {
  keyName       'cloud-do'
  securityGroup 'cloud-do'
  instanceName  'gramazon/cloud-do'
  stateFileName 'cloud-do.json'
  ami           'ami-6f07e418'       
  instanceType  't1.micro'
  waitForStart  true
}

Gramazon Example II

task terminateInstance(type: TerminateInstance) {
  stateFileName 'cloud-do.json'
}

Imgr

Imgr Logo

Imgr

  • A tool for building images
  • Inspired by Packer

Images?

Images

Supports

  • Shell
  • Puppet

Configuration Example

Imgr

The flow

  1. Start instance(s)
  2. Upload manifests
  3. Run tests
  4. Generate report
  5. Terminate instance(s)

Images, manifests, tasks

Images

The big picture

Images

Aetomation

Images

Conclusions

  • Reuse your existing Java knowledge
  • ...to build a bridge between DEVs and OPs
  • Reuse development best practices for OPs
  • Don't be afraid to try new technologies
  • Automate!

One more thing...

It's all Open Source!

Source code

Seeking contributors!

Questions?

Hvala! Thank you!

DevOps

Links