Represents the front-end HTTP server on the system.
Note: This is the Apache VirtualHost implementation; other implementations may vary.
Public: Load from json
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 216 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 |a, c, k| new_obj.add_ssl_cert(a, c, k) 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 113 def initialize(container_uuid, container_name=nil, namespace=nil) @config = OpenShift::Config.new @cloud_domain = @config.get("CLOUD_DOMAIN") @basedir = @config.get("OPENSHIFT_HTTP_CONF_DIR") @container_uuid = container_uuid @container_name = container_name @namespace = namespace @fqdn = nil # 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)) @container_name = env['OPENSHIFT_GEAR_NAME'] @fqdn = env['OPENSHIFT_GEAR_DNS'] @namespace = @fqdn.sub(%r\.#{@cloud_domain}$/,"").sub(%r^#{@container_name}\-/,"") 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 592 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 657 def add_ssl_cert(ssl_cert, priv_key, server_alias, passphrase='') server_alias_clean = clean_server_name(server_alias) 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 end 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 raise FrontendHttpServerException.new("Invalid X509 Certificate", @container_uuid, @container_name, @namespace) rescue => e raise FrontendHttpServerException.new("Other key/cert error: #{e}", @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") 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) } # # 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 # SSLVerifyClient must be set for +ExportCertData to take effect, so just use # optional_no_ca. SSLVerifyClient optional_no_ca 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_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
Public: List aliases for this gear
Examples
aliases # => ["foo.example.com", "bar.example.com"]
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 578 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 788 def clean_server_name(name) dname = name.downcase if not dname.index(%r[^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 =~ %r^\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) 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 343 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["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 377 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 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 411 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 =~ %r^(GONE|FORBIDDEN|NOPROXY)$/ entry[2][$~[1].downcase] = 1 elsif v =~ %r^(REDIRECT|FILE|TOHTTPS):(.*)$/ entry[2][$~[1].downcase] = 1 entry[1] = $~[2] else entry[1] = v end entry } 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 159 def create # Reserved for future use. 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 171 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) } } # Clean up SSL certs and legacy node configuration paths = Dir.glob(File.join(@basedir, "#{container_uuid}_*")) FileUtils.rm_rf(paths) paths.each do |p| if p =~ %r\.conf$/ begin reload_httpd rescue end break 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 458 def disconnect(*paths) ApacheDBNodes.open(ApacheDBNodes::WRCREAT) do |d| paths.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 467 def disconnect_websocket(*paths) if paths.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 562 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 484 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 513 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 548 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 815 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
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 622 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 762 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) 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
Public: List aliases with SSL certs and unencrypted private keys
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 637 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
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 529 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 194 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 208 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 499 def unidle ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d| d.delete(@fqdn) end end
Public: Update identifier to the new names
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 260 def update(container_name, namespace) new_fqdn = clean_server_name("#{container_name}-#{namespace}.#{@cloud_domain}") ApacheDBNodes.open(ApacheDBNodes::WRCREAT) do |d| d.update_block do |deletions, updates, k, v| if k.split('/')[0] == @fqdn deletions << k updates[k.sub(@fqdn, new_fqdn)] = v end end end ApacheDBAliases.open(ApacheDBAliases::WRCREAT) do |d| d.update_block do |deletions, updates, k, v| if v == @fqdn updates[k]=new_fqdn end end end ApacheDBIdler.open(ApacheDBIdler::WRCREAT) do |d| d.update_block do |deletions, updates, k, v| if k == @fqdn deletions << k updates[new_fqdn] = v end end end ApacheDBSTS.open(ApacheDBSTS::WRCREAT) do |d| d.update_block do |deletions, updates, k, v| if k == @fqdn deletions << k updates[new_fqdn] = v end end end NodeJSDBRoutes.open(NodeJSDBRoutes::WRCREAT) do |d| d.update_block do |deletions, updates, k, v| if k == @fqdn deletions << k updates[new_fqdn] = v end end end @container_name = container_name @namespace = namespace @fqdn = new_fqdn end
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 312 def update_name(container_name) update(container_name, @namespace) end
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 316 def update_namespace(namespace) update(@container_name, namespace) end