Represents the front-end HTTP server on the system.
Note: This is the Apache VirtualHost implementation; other implementations may vary.
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(ApplicationContainer.from_uuid(uuid)) 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
Public: Load from json
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 261 def self.json_create(obj) data = obj['data'] new_obj = FrontendHttpServer.new(ApplicationContainer.from_uuid(data['container_uuid'])) 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
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 154 def initialize(container) @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 = container.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 # 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: 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 624 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
Public: Adds a ssl certificate for an alias
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 689 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 = PathUtils.join(@basedir, alias_token) ssl_cert_file_path = PathUtils.join(alias_conf_dir_path, server_alias_clean + ".crt") priv_key_file_path = PathUtils.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 = <<-ALIAS_CONF_ENTRY <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> ALIAS_CONF_ENTRY # 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 = PathUtils.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
Public: List aliases for this gear
Examples
aliases # => ["foo.example.com", "bar.example.com"]
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 610 def aliases ApacheDBAliases.open(ApacheDBAliases::READER) do |d| return d.select { |k, v| v == @fqdn }.map { |k, v| k } end end
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 821 def clean_server_name(name) dname = name.downcase if not dname =~ /^[a-z0-9]/ raise FrontendHttpServerNameException.new("Invalid start character", @container_uuid, @container_name, @namespace, dname ) end 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
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) target_update Preserve existing options and update the target. 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 325 def connect(*elements) working_elements = [] elements.flatten.enum_for(:each_slice, 3).each do |path, uri, options| if options["target_update"] ApacheDBNodes.open(ApacheDBNodes::READER) do |d| begin old_conn = d.fetch(@fqdn + path.to_s) options = decode_connection(path.to_s, old_conn)[2] rescue KeyError raise FrontendHttpServerException.new("The target_update option specified but no old configuration: #{path}", @container_uuid, @container_name, @namespace) end end end working_elements << [path, uri, options] end ApacheDBNodes.open(ApacheDBNodes::WRCREAT) do |d| working_elements.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
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 375 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
Public: List connections Returns [ [path, uri, options], [path, uri, options], …]
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 440 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| decode_connection(k.sub(@fqdn, ""), v) } end entries end
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 199 def create GearDB.open(GearDB::WRCREAT) do |d| d.store(@container_uuid, {'fqdn' => @fqdn, 'container_name' => @container_name, 'namespace' => @namespace}) end end
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 413 def decode_connection(path, connection) entry = [ path, "", {} ] 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 connection =~ /^(GONE|FORBIDDEN|NOPROXY|HEALTH)$/ entry[2][$~[1].downcase] = 1 elsif connection =~ /^(REDIRECT|FILE|TOHTTPS):(.*)$/ entry[2][$~[1].downcase] = 1 entry[1] = $~[2] else entry[1] = connection end entry end
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 213 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(PathUtils.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
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 466 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
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 475 def disconnect_websocket(*paths) if paths.flatten.include?("") NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d| d.delete(@fqdn) end end end
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 594 def get_sts ApacheDBSTS.open(ApacheDBSTS::READER) do |d| if d.has_key?(@fqdn) return d.fetch(@fqdn) end end nil end
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 492 def idle ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d| d.store(@fqdn, @container_uuid) end end
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 545 def idle? ApacheDBIdler.open(ApacheDBIdler::READER) do |d| return d.has_key?(@fqdn) end end
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 580 def no_sts ApacheDBSTS.open(ApacheDBSTS::WRCREAT) do |d| d.delete(@fqdn) end end
Reload the Apache configuration
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 853 def reload_httpd(async=false) async_opt="-b" if async begin ::OpenShift::Runtime::Utils::oo_spawn("/usr/sbin/oo-httpd-singular #{async_opt} graceful", {:expected_exitstatus=>0}) rescue ::OpenShift::Runtime::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
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 654 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
Public: Removes ssl certificate/private key associated with an alias
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 793 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 = PathUtils.join(@basedir, alias_token) alias_conf_file_path = PathUtils.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
Public: List aliases with SSL certs and unencrypted private keys
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 669 def ssl_certs aliases.map { |a| alias_token = "#{@container_uuid}_#{@namespace}_#{a}" alias_conf_dir_path = PathUtils.join(@basedir, alias_token) ssl_cert_file_path = PathUtils.join(alias_conf_dir_path, a + ".crt") priv_key_file_path = PathUtils.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
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 561 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
Public: extract hash version of complete data for this gear
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 239 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
Public: Generate json
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 253 def to_json(*args) { 'json_class' => self.class.name, 'data' => self.to_hash }.to_json(*args) end
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 507 def unidle ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d| d.delete(@fqdn) end end
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 523 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