Model
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(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
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
# 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: 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
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
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
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
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
# 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
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
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
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
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
# 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
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
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
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
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 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
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
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
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
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
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
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
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
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
Generated with the Darkfish Rdoc Generator 2.