Parent

Included Modules

OpenShift::FrontendHttpServer

Frontend Http Server

Represents the front-end HTTP server on the system.

Note: This is the Apache VirtualHost implementation; other implementations may vary.

Attributes

container_name[R]
container_uuid[R]
fqdn[R]
namespace[R]

Public Class Methods

all() click to toggle source

Public: return an Enumerator which yields FrontendHttpServer objects for each gear which has run create.

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 127
def self.all
  Enumerator.new do |yielder|

    # Avoid deadlocks by listing the gears first
    gearlist = {}
    GearDB.open(GearDB::READER) do |d|
      d.each do |uuid, container|
        gearlist[uuid.clone] = container.clone
      end
    end

    gearlist.each do |uuid, container|
      frontend = nil
      begin
        frontend = FrontendHttpServer.new(uuid, container['container_name'], container['namespace'])
      rescue => e
        NodeLogger.logger.error("Failed to instantiate FrontendHttpServer for #{uuid}: #{e}")
        NodeLogger.logger.error("Backtrace: #{e.backtrace}")
      else
        yielder.yield(frontend)
      end
    end
  end

end
json_create(obj) click to toggle source

Public: Load from json

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 271
def self.json_create(obj)
  data = obj['data']
  new_obj = new(data['container_uuid'],
                data['container_name'],
                data['namespace'])
  new_obj.create

  if data.has_key?("connections")
    new_obj.connect(data["connections"])
  end

  if data.has_key?("aliases")
    data["aliases"].each do |a|
      new_obj.add_alias(a)
    end
  end

  if data.has_key?("ssl_certs")
    data["ssl_certs"].each do |c, k, a|
      new_obj.add_ssl_cert(c, k, a)
    end
  end

  if data.has_key?("idle")
    if data["idle"]
      new_obj.idle
    else
      new_obj.unidle
    end
  end

  if data.has_key?("sts")
    if data["sts"]
      new_obj.sts(data["sts"])
    else
      new_obj.no_sts
    end
  end

  new_obj
end
new(container_uuid, container_name=nil, namespace=nil) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 153
def initialize(container_uuid, container_name=nil, namespace=nil)
  @config = OpenShift::Config.new

  @cloud_domain = clean_server_name(@config.get("CLOUD_DOMAIN"))

  @basedir = @config.get("OPENSHIFT_HTTP_CONF_DIR")

  @container_uuid = container_uuid
  @container_name = container_name
  @namespace = namespace

  @fqdn = nil

  # Did we save the old information?
  if (@container_name.to_s == "") or (@namespace.to_s == "")
    begin
      GearDB.open(GearDB::READER) do |d|
        @container_name = d.fetch(@container_uuid).fetch('container_name')
        @namespace = d.fetch(@container_uuid).fetch('namespace')
        @fqdn = d.fetch(@container_uuid).fetch('fqdn')
      end
    rescue
    end
  end

  # Last ditch, attempt to infer from the gear itself
  if (@container_name.to_s == "") or (@namespace.to_s == "")
    begin
      env = Utils::Environ.for_gear(File.join(@config.get("GEAR_BASE_DIR"), @container_uuid))
      @fqdn = clean_server_name(env['OPENSHIFT_GEAR_DNS'])
      @container_name = env['OPENSHIFT_GEAR_NAME']
      @namespace = env['OPENSHIFT_GEAR_DNS'].sub(/\..*$/,"").sub(/^.*\-/,"")
    rescue
    end
  end

  # Could not infer from any source
  if (@container_name.to_s == "") or (@namespace.to_s == "")
    raise FrontendHttpServerException.new("Name or namespace not specified and could not infer it",
                                          @container_uuid)
  end

  if @fqdn.nil?
    @fqdn = clean_server_name("#{@container_name}-#{@namespace}.#{@cloud_domain}")
  end

end

Public Instance Methods

add_alias(name) click to toggle source

Public: Add an alias to this namespace

Examples

add_alias("foo.example.com")
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 617
def add_alias(name)
  dname = clean_server_name(name)

  # Broker checks for global uniqueness
  ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do |d|
    d.store(dname, @fqdn)
  end

  NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d|
    begin
      routes_ent = d.fetch(@fqdn)
      if not routes_ent.nil?
        alias_ent = routes_ent.clone
        alias_ent["alias"] = @fqdn
        d.store(name, alias_ent)
      end
    rescue KeyError
    end
  end

end
add_ssl_cert(ssl_cert, priv_key, server_alias, passphrase='') click to toggle source

Public: Adds a ssl certificate for an alias

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 682
def add_ssl_cert(ssl_cert, priv_key, server_alias, passphrase='')
  server_alias_clean = clean_server_name(server_alias)

  begin
    priv_key_clean = OpenSSL::PKey.read(priv_key, passphrase)
    ssl_cert_clean = []
    ssl_cert_unit = ""
    ssl_cert.each_line do |cert_line|
      ssl_cert_unit += cert_line
      if cert_line.start_with?('-----END')
        ssl_cert_clean << OpenSSL::X509::Certificate.new(ssl_cert_unit)
        ssl_cert_unit = ""
      end
    end
  rescue ArgumentError
    raise FrontendHttpServerException.new("Invalid Private Key or Passphrase",
                                          @container_uuid, @container_name,
                                          @namespace)
  rescue OpenSSL::X509::CertificateError => e
    raise FrontendHttpServerException.new("Invalid X509 Certificate: #{e.message}",
                                          @container_uuid, @container_name,
                                          @namespace)
  rescue => e
    raise FrontendHttpServerException.new("Other key/cert error: #{e.message}",
                                          @container_uuid, @container_name,
                                          @namespace)
  end

  if ssl_cert_clean.empty?
    raise FrontendHttpServerException.new("Could not parse certificates",
                                          @container_uuid, @container_name,
                                          @namespace)
  end

  if not ssl_cert_clean[0].check_private_key(priv_key_clean)
    raise FrontendHttpServerException.new("Key/cert mismatch",
                                          @container_uuid, @container_name,
                                          @namespace)
  end

  if not [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA].include?(priv_key_clean.class)
    raise FrontendHttpServerException.new("Key must be RSA or DSA for Apache mod_ssl",
                                          @container_uuid, @container_name,
                                          @namespace)
  end


  # Create a new directory for the alias and copy the certificates
  alias_token = "#{@container_uuid}_#{@namespace}_#{server_alias_clean}"
  alias_conf_dir_path = File.join(@basedir, alias_token)
  ssl_cert_file_path = File.join(alias_conf_dir_path, server_alias_clean + ".crt")
  priv_key_file_path = File.join(alias_conf_dir_path, server_alias_clean + ".key")

  #
  # Create configuration for the alias
  #

  # Create top level config file for the alias
  alias_conf_contents = <VirtualHost *:443>  ServerName #{server_alias_clean}  ServerAdmin openshift-bofh@redhat.com  DocumentRoot /var/www/html  DefaultType None  SSLEngine on    SSLCertificateFile #{ssl_cert_file_path}  SSLCertificateKeyFile #{priv_key_file_path}  SSLCertificateChainFile #{ssl_cert_file_path}  SSLCipherSuite RSA:!EXPORT:!DH:!LOW:!NULL:+MEDIUM:+HIGH  SSLProtocol -ALL +SSLv3 +TLSv1  SSLOptions +StdEnvVars +ExportCertData  RequestHeader set X-Forwarded-Proto "https"  RequestHeader set X-Forwarded-SSL-Client-Cert %{SSL_CLIENT_CERT}e  RewriteEngine On  include conf.d/openshift_route.include</VirtualHost>

  # Finally, commit the changes
  ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do |d|
    if not (d.has_key? server_alias_clean)
      raise FrontendHttpServerException.new("Specified alias #{server_alias_clean} does not exist for the app",
                                            @container_uuid, @container_name,
                                            @namespace)
    end

    FileUtils.mkdir_p(alias_conf_dir_path)
    File.open(ssl_cert_file_path, 'w') { |f| f.write(ssl_cert_clean.map { |c| c.to_pem}.join) }
    File.open(priv_key_file_path, 'w') { |f| f.write(priv_key_clean.to_pem) }

    alias_conf_file_path = File.join(@basedir, "#{alias_token}.conf")
    File.open(alias_conf_file_path, 'w') { |f| f.write(alias_conf_contents) }

    # Reload httpd to pick up the new configuration
    reload_httpd
  end
end
aliases() click to toggle source

Public: List aliases for this gear

Examples

aliases
# => ["foo.example.com", "bar.example.com"]
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 603
def aliases
  ApacheDBAliases.open(ApacheDBAliases::READER) do |d|
    return d.select { |k, v| v == @fqdn }.map { |k, v| k }
  end
end
clean_server_name(name) click to toggle source

Private: Validate the server name

The name is validated against DNS host name requirements from RFC 1123 and RFC 952. Additionally, OpenShift does not allow names/aliases to be an IP address.

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 814
def clean_server_name(name)
  dname = name.downcase

  if not dname.index(/[^0-9a-z\-.]/).nil?
    raise FrontendHttpServerNameException.new("Invalid characters", @container_uuid,                                                     @container_name, @namespace, dname )
  end

  if dname.length > 255
    raise FrontendHttpServerNameException.new("Too long", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  if dname.length == 0
    raise FrontendHttpServerNameException.new("Name was blank", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  if dname =~ /^\d+\.\d+\.\d+\.\d+$/
    raise FrontendHttpServerNameException.new("IP addresses are not allowed", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  return dname
end
connect(*elements) click to toggle source

Public: Connect path elements to a back-end URI for this namespace.

Examples

connect('', '127.0.250.1:8080')
connect('/', '127.0.250.1:8080/')
connect('/phpmyadmin', '127.0.250.2:8080/')
connect('/socket, '127.0.250.3:8080/', {"websocket"=>1}

    Options:
        websocket      Enable web sockets on a particular path
        gone           Mark the path as gone (uri is ignored)
        forbidden      Mark the path as forbidden (uri is ignored)
        noproxy        Mark the path as not proxied (uri is ignored)
        redirect       Use redirection to uri instead of proxy (uri must be a path)
        file           Ignore request and load file path contained in uri (must be path)
        tohttps        Redirect request to https and use the path contained in the uri (must be path)
    While more than one option is allowed, the above options conflict with each other.
    Additional options may be provided which are target specific.
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 336
def connect(*elements)

  ApacheDBNodes.open(ApacheDBNodes::WRCREAT) do |d|

    elements.flatten.enum_for(:each_slice, 3).each do |path, uri, options|

      if options["gone"]
        map_dest = "GONE"
      elsif options["forbidden"]
        map_dest = "FORBIDDEN"
      elsif options["noproxy"]
        map_dest = "NOPROXY"
      elsif options["health"]
        map_dest = "HEALTH"
      elsif options["redirect"]
        map_dest = "REDIRECT:#{uri}"
      elsif options["file"]
        map_dest = "FILE:#{uri}"
      elsif options["tohttps"]
        map_dest = "TOHTTPS:#{uri}"
      else
        map_dest = uri
      end

      if options["websocket"]
        connect_websocket(path, uri, options)
      else
        disconnect_websocket(path) # We could be changing a path
      end

      d.store(@fqdn + path.to_s, map_dest)
    end
  end

end
connect_websocket(path, uri, options) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 372
def connect_websocket(path, uri, options)

  if path != ""
    raise FrontendHttpServerException.new("Path must be empty for a websocket: #{path}",
                                          @container_uuid, @container_name, @namespace)

  end

  conn = options["connections"]
  if conn.nil?
    conn = 5
  end

  bw = options["bandwidth"]
  if bw.nil?
    bw = 100
  end

  # Use the websocket port if it is passed as an option
  port = options["websocket_port"]
  if port
    uri = uri.sub(/:(\d)+/, ":" + port.to_s)
  end

  routes_ent = {
    "endpoints" => [ uri ],
    "limits"    => {
      "connections" => conn,
      "bandwidth"   => bw
    }
  }

  NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d|
    d.store(@fqdn, routes_ent)
  end

end
connections() click to toggle source

Public: List connections Returns [ [path, uri, options], [path, uri, options], ...]

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 412
def connections
  # We can't simply rely on the open returning the block's value in unit testing.
  # http://rubyforge.org/tracker/?func=detail&atid=7477&aid=8687&group_id=1917
  entries = nil
  ApacheDBNodes.open(ApacheDBNodes::READER) do |d|
    entries = d.select { |k, v|
      k.split('/')[0] == @fqdn
    }.map { |k, v|
      entry = [ k.sub(@fqdn, ""), "", {} ]

      if entry[0] == ""
        begin
          NodeJSDBRoutes.open(NodeJSDBRoutes::READER) do |d|
            routes_ent = d.fetch(@fqdn)
            entry[2].merge!(routes_ent["limits"])
            entry[2]["websocket"]=1
          end
        rescue
        end
      end

      if v =~ /^(GONE|FORBIDDEN|NOPROXY|HEALTH)$/
        entry[2][$~[1].downcase] = 1
      elsif v =~ /^(REDIRECT|FILE|TOHTTPS):(.*)$/
        entry[2][$~[1].downcase] = 1
        entry[1] = $~[2]
      else
        entry[1] = v
      end
      entry
    }
  end
  entries
end
create() click to toggle source

Public: Initialize a new configuration for this gear

Examples

create
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 209
def create
  GearDB.open(GearDB::WRCREAT) do |d|
    d.store(@container_uuid, {'fqdn' => @fqdn,  'container_name' => @container_name, 'namespace' => @namespace})
  end
end
destroy() click to toggle source

Public: Remove the frontend httpd configuration for a gear.

Examples

destroy
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 223
def destroy
  ApacheDBNodes.open(ApacheDBNodes::WRCREAT)     { |d| d.delete_if { |k, v| k.split('/')[0] == @fqdn } }
  ApacheDBAliases.open(ApacheDBAliases::WRCREAT) { |d| d.delete_if { |k, v| v == @fqdn } }
  ApacheDBIdler.open(ApacheDBIdler::WRCREAT)     { |d| d.delete(@fqdn) }
  ApacheDBSTS.open(ApacheDBSTS::WRCREAT)         { |d| d.delete(@fqdn) }
  NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT)   { |d| d.delete_if { |k, v| (k == @fqdn) or (v["alias"] == @fqdn) } }
  GearDB.open(GearDB::WRCREAT)                   { |d| d.delete(@container_uuid) }

  # Clean up SSL certs and legacy node configuration
  ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do
    paths = Dir.glob(File.join(@basedir, "#{container_uuid}_*"))
    FileUtils.rm_rf(paths)
    paths.each do |p|
      if p =~ /\.conf$/
        begin
          reload_httpd
        rescue
        end
        break
      end
    end
  end

end
disconnect(*paths) click to toggle source

Public: Disconnect a path element from this namespace

Examples

disconnect('')
disconnect('/')
disconnect('/phpmyadmin)
disconnect('/a', '/b', '/c')

# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 459
def disconnect(*paths)
  ApacheDBNodes.open(ApacheDBNodes::WRCREAT) do |d|
    paths.flatten.each do |p|
      d.delete(@fqdn + p.to_s)
    end
  end
  disconnect_websocket(*paths)
end
disconnect_websocket(*paths) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 468
def disconnect_websocket(*paths)
  if paths.flatten.include?("")
    NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d|
      d.delete(@fqdn)
    end
  end
end
get_sts() click to toggle source

Public: Determine whether the gear has sts

Examples

sts?

# => true or false

Returns true if the gear is idled

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 587
def get_sts
  ApacheDBSTS.open(ApacheDBSTS::READER) do |d|
    if d.has_key?(@fqdn)
      return d.fetch(@fqdn)
    end
  end
  nil
end
idle() click to toggle source

Public: Mark a gear as idled

Examples

idle()

# => nil()

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 485
def idle
  ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d|
    d.store(@fqdn, @container_uuid)
  end
end
idle?() click to toggle source

Public: Determine whether the gear is idle

Examples

idle?

# => true or false

Returns true if the gear is idled

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 538
def idle?
  ApacheDBIdler.open(ApacheDBIdler::READER) do |d|
    return d.has_key?(@fqdn)
  end
end
no_sts() click to toggle source

Public: Unmark a gear for sts

Examples

nosts()

# => nil()

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 573
def no_sts
  ApacheDBSTS.open(ApacheDBSTS::WRCREAT) do |d|
    d.delete(@fqdn)
  end
end
reload_httpd(async=false) click to toggle source

Reload the Apache configuration

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 841
def reload_httpd(async=false)
  async_opt="-b" if async
  begin
    Utils::oo_spawn("/usr/sbin/oo-httpd-singular #{async_opt} graceful", {:expected_exitstatus=>0})
  rescue Utils::ShellExecutionException => e
    logger.error("ERROR: failure from oo-httpd-singular(#{e.rc}): #{@uuid} stdout: #{e.stdout} stderr:#{e.stderr}")
    raise FrontendHttpServerExecException.new(e.message, @container_uuid, @container_name, @namespace, e.rc, e.stdout, e.stderr)
  end
end
remove_alias(name) click to toggle source

Public: Removes an alias from this namespace

Examples

add_alias("foo.example.com")
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 647
def remove_alias(name)
  dname = clean_server_name(name)

  ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do |d|
    d.delete(dname)
  end

  NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d|
    d.delete(dname)
  end

  remove_ssl_cert(dname)
end
remove_ssl_cert(server_alias) click to toggle source

Public: Removes ssl certificate/private key associated with an alias

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 786
def remove_ssl_cert(server_alias)
  server_alias_clean = clean_server_name(server_alias)

  #
  # Remove the alias specific configuration
  #
  alias_token = "#{@container_uuid}_#{@namespace}_#{server_alias_clean}"

  alias_conf_dir_path = File.join(@basedir, alias_token)
  alias_conf_file_path = File.join(@basedir, "#{alias_token}.conf")

  if File.exists?(alias_conf_file_path) or File.exists?(alias_conf_dir_path)
    ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do

      FileUtils.rm_rf(alias_conf_file_path)
      FileUtils.rm_rf(alias_conf_dir_path)

      # Reload httpd to pick up the configuration changes
      reload_httpd
    end
  end
end
ssl_certs() click to toggle source

Public: List aliases with SSL certs and unencrypted private keys

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 662
def ssl_certs
  aliases.map { |a|
    alias_token = "#{@container_uuid}_#{@namespace}_#{a}"
    alias_conf_dir_path = File.join(@basedir, alias_token)
    ssl_cert_file_path = File.join(alias_conf_dir_path, a + ".crt")
    priv_key_file_path = File.join(alias_conf_dir_path, a + ".key")

    begin
      ssl_cert = File.read(ssl_cert_file_path)
      priv_key = File.read(priv_key_file_path)
    rescue
      ssl_cert = nil
      priv_key = nil
    end

    [ ssl_cert, priv_key, a ]
  }.select { |e| e[0] != nil }
end
sts(max_age=15768000) click to toggle source

Public: Mark a gear for STS

Examples

sts(duration)

# => nil()

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 554
def sts(max_age=15768000)
  ApacheDBSTS.open(ApacheDBSTS::WRCREAT) do |d|
    if max_age.nil?
      d.delete(@fqdn)
    else
      d.store(@fqdn, max_age.to_i)
    end
  end
end
to_hash() click to toggle source

Public: extract hash version of complete data for this gear

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 249
def to_hash
  {
    "container_uuid" => @container_uuid,
    "container_name" => @container_name,
    "namespace"      => @namespace,
    "connections"    => connections,
    "aliases"        => aliases,
    "ssl_certs"      => ssl_certs,
    "idle"           => idle?,
    "sts"            => get_sts
  }
end
to_json(*args) click to toggle source

Public: Generate json

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 263
def to_json(*args)
  {
    'json_class' => self.class.name,
    'data'       => self.to_hash
  }.to_json(*args)
end
unidle() click to toggle source

Public: Unmark a gear as idled

Examples

unidle()

# => nil()

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 500
def unidle
  ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d|
    d.delete(@fqdn)
  end
end
unprivileged_unidle() click to toggle source

Public: Make an unprivileged call to unidle the gear

Examples

unprivileged_unidle()

# => nil()

Returns nil. This is an opportunistic call, failure conditions are ignored but the call may take over a minute to complete.

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 516
def unprivileged_unidle
  begin
    http = Net::HTTP.new('127.0.0.1', 80)
    http.open_timeout = 5
    http.read_timeout = 60
    http.use_ssl = false
    http.start do |client|
      resp = client.request_head('/', { 'Host' => @fqdn })
      resp.code
    end
  rescue
  end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.