Return-Path: Delivered-To: apmail-incubator-deltacloud-dev-archive@minotaur.apache.org Received: (qmail 80274 invoked from network); 20 Dec 2010 16:42:02 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 20 Dec 2010 16:42:02 -0000 Received: (qmail 58771 invoked by uid 500); 20 Dec 2010 16:41:59 -0000 Delivered-To: apmail-incubator-deltacloud-dev-archive@incubator.apache.org Received: (qmail 58739 invoked by uid 500); 20 Dec 2010 16:41:59 -0000 Mailing-List: contact deltacloud-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: deltacloud-dev@incubator.apache.org Delivered-To: mailing list deltacloud-dev@incubator.apache.org Received: (qmail 58451 invoked by uid 99); 20 Dec 2010 16:41:59 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 20 Dec 2010 16:41:59 +0000 X-ASF-Spam-Status: No, hits=-4.6 required=10.0 tests=FILL_THIS_FORM_FRAUD_PHISH,RCVD_IN_DNSWL_HI,SPF_HELO_PASS,SPF_PASS,T_FILL_THIS_FORM_SHORT X-Spam-Check-By: apache.org Received-SPF: pass (nike.apache.org: domain of mfojtik@redhat.com designates 209.132.183.28 as permitted sender) Received: from [209.132.183.28] (HELO mx1.redhat.com) (209.132.183.28) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 20 Dec 2010 16:41:51 +0000 Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id oBKGfTlt021728 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Mon, 20 Dec 2010 11:41:29 -0500 Received: from patashnik.brq.redhat.com (dhcp-2-138.brq.redhat.com [10.34.2.138]) by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id oBKGfRHb001553 for ; Mon, 20 Dec 2010 11:41:28 -0500 From: mfojtik@redhat.com To: deltacloud-dev@incubator.apache.org Subject: [PATCH core 1/2] Initial import of RHEV REST API driver Date: Mon, 20 Dec 2010 17:41:23 +0100 Message-Id: <1292863284-24622-2-git-send-email-mfojtik@redhat.com> In-Reply-To: <1292863284-24622-1-git-send-email-mfojtik@redhat.com> References: <1292863284-24622-1-git-send-email-mfojtik@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11 X-Virus-Checked: Checked by ClamAV on apache.org From: Michal Fojtik --- .../lib/deltacloud/drivers/rhevm2/rhevm2_driver.rb | 231 ++++++++++++++++++++ .../lib/deltacloud/drivers/rhevm2/rhevm_client.rb | 217 ++++++++++++++++++ server/lib/drivers.rb | 1 + 3 files changed, 449 insertions(+), 0 deletions(-) create mode 100644 server/lib/deltacloud/drivers/rhevm2/rhevm2_driver.rb create mode 100644 server/lib/deltacloud/drivers/rhevm2/rhevm_client.rb diff --git a/server/lib/deltacloud/drivers/rhevm2/rhevm2_driver.rb b/server/lib/deltacloud/drivers/rhevm2/rhevm2_driver.rb new file mode 100644 index 0000000..f1655d1 --- /dev/null +++ b/server/lib/deltacloud/drivers/rhevm2/rhevm2_driver.rb @@ -0,0 +1,231 @@ +# +# Copyright (C) 2009 Red Hat, Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +require 'deltacloud/base_driver' +require 'deltacloud/drivers/rhevm2/rhevm_client' + +module Deltacloud + module Drivers + module RHEVM2 + +class RHEVM2Driver < Deltacloud::BaseDriver + + feature :instances, :user_name + + # FIXME: These values are just for ilustration + # Also I choosed 'SERVER' and 'DESKTOP' names + # because they are referred by VM (API type) + # + # Values like RAM or STORAGE are reported by VM + # so they are not static. + + define_hardware_profile 'SERVER' do + cpu 1 + memory [ 1*1024 .. 32*1024 ] + architecture 'x86_64' + end + + define_hardware_profile 'DESKTOP' do + cpu 1 + memory [ 1*1024 .. 32*1024 ] + architecture 'x86_64' + end + + # Instead of setting a URL for RHEV provider + # do it here in driver, so it can be altered by HTTP headers + # + def provider_uri=(uri) + @RHEVM_URI = uri + end + + def provider_uri + # 'http://localhost:8099/rhevm-api-mock/' || @RHEVM_URI + 'http://localhost:8080' + end + + # FIX: This is state table from EC2, fix it pls to + # match RHEV-M model + # + define_instance_states do + start.to( :pending ) .automatically + pending.to( :running ) .automatically + pending.to( :stopping ) .on( :stop ) + pending.to( :stopped ) .automatically + stopped.to( :running ) .on( :start ) + running.to( :running ) .on( :reboot ) + running.to( :stopping ) .on( :stop ) + shutting_down.to( :stopped ) .automatically + stopped.to( :finish ) .automatically + end + + # + # Realms + # + + def realms(credentials, opts=nil) + client = new_client(credentials) + realm_arr = [] + safely do + datacenters = client.datacenters + datacenters.each do |d| + realm_arr << convert_realm(d) + end + end + realm_arr + end + + def images(credentials, opts={}) + client = new_client(credentials) + img_arr = [] + safely do + templates = client.templates + if (!opts.nil? && opts[:id]) + templates = templates.select{|t| opts[:id] == t.id} + end + templates.each do |t| + img_arr << convert_image(t) + end + end + img_arr = filter_on( img_arr, :architecture, opts ) + img_arr.sort_by{|e| [e.owner_id, e.name]} + end + + def instances(credentials, opts={}) + client = new_client(credentials) + inst_arr = [] + safely do + vms = client.vms + vms.each do |vm| + inst_arr << convert_instance(client, vm) + end + end + inst_arr = filter_on( inst_arr, :id, opts ) + filter_on( inst_arr, :state, opts ) + end + + def reboot_instance(credentials, id) + client = new_client(credentials) + safely do + client.vm_action(:reboot, id) + end + end + + def stop_instance(credentials, id) + client = new_client(credentials) + safely do + client.vm_action(:suspend, id) + end + end + + def start_instance(credentials, id) + client = new_client(credentials) + safely do + client.vm_action(:start, id) + end + end + + def shutdown_instance(credentials, id) + client = new_client(credentials) + safely do + client.vm_action(:shutdown, id) + end + end + + private + + def new_client(credentials) + opts = {:verbose => true} + RHEVM::Client.new(credentials.user, credentials.password, provider_uri, opts) + end + + # I can't test this method, since 'cluster' is not a part of Mock driver + def get_realm_from_cluster(client, cluster_id) + cluster = client.clusters(:id => cluster_id) + datacenter = client.datacenter(:id => cluster.datacenter_id) + convert_realm(datacenter) + end + + def convert_instance(client, inst) + # FIXME: Remove 'DOWN', it's used just because RHEV Mock driver + # has no 'state' property in XML. + # + # FIXME2: This call will now throw 500 because I'm unable to fetch realm + # for instance. + # + state = convert_state(inst.status=='' ? 'DOWN' : inst.status) + Instance.new( + :id => inst.id, + :name => inst.name, + :state => state, + :image_id => inst.template, + # Realm aka DataCenter could be retrieved from Cluster + :realm_id => get_realm_from_cluster(client, inst.cluster), + :owner_id => 'self', + :launch_time => inst.creation_time, + :instance_profile => InstanceProfile::new(inst.profile), + :hardware_profile_id => inst.profile, + :actions=>instance_actions_for( state ) + ) + end + + # STATES: + # + # UNASSIGNED, DOWN, UP, POWERING_UP, POWERED_DOWN, PAUSED, MIGRATING_FROM, MIGRATING_TO, + # UNKNOWN, NOT_RESPONDING, WAIT_FOR_LAUNCH, REBOOT_IN_PROGRESS, SAVING_STATE, RESTORING_STATE, + # SUSPENDED, IMAGE_ILLEGAL, IMAGE_LOCKED or POWERING_DOWN + # + # FIXME: This will need to be fixed regarding to state-machine + def convert_state(state) + case state + when 'POWERING_UP', 'WAIT_FOR_LAUNCH', 'REBOOT_IN_PROGRESS', 'SAVING_STATE', + 'RESTORING_STATE', 'POWERING_DOWN' then + 'PENDING' + when 'UNASSIGNED', 'DOWN', 'POWERING_DOWN', 'PAUSED', 'NOT_RESPONDING', 'SAVING_STATE', + 'SUSPENDED', 'IMAGE_ILLEGAL', 'IMAGE_LOCKED', 'UNKNOWN' then + 'STOPPED' + when 'UP', 'MIGRATING_TO', 'MIGRATING_FROM' + 'RUNNING' + end + end + + def convert_image(img) + Image.new( + :id => img.id, + :name => img.name, + :description => img.description, + :owner_id => 'self', + :architecture => 'x86_64', # All RHEV-M VMs are x86_64 + :status => img.status + ) + end + + def convert_realm(realm) + Realm.new( + :id => realm.id, + :name => realm.name, + :state => 'AVAILABLE', + :limit => :unlimited + ) + end + + +end + + end + end +end diff --git a/server/lib/deltacloud/drivers/rhevm2/rhevm_client.rb b/server/lib/deltacloud/drivers/rhevm2/rhevm_client.rb new file mode 100644 index 0000000..856844c --- /dev/null +++ b/server/lib/deltacloud/drivers/rhevm2/rhevm_client.rb @@ -0,0 +1,217 @@ +require 'rubygems' + +#gem 'rest-client' + +require 'base64' +require 'logger' +require 'restclient' +require 'nokogiri' + +module RHEVM + + class Client + attr_reader :base_uri + attr_reader :entry_points + attr_reader :logger + + # Define a list of supported collections which will be handled automatically + # by method_missing + @@COLLECTIONS = [ :templates, :clusters, :storagedomains, :vms, :datacenters ] + + def initialize(username, password, base_uri, opts={}) + @logger = opts[:verbose] ? Logger.new(STDERR) : [] + @username, @password = username, password + URI.parse(base_uri) + @base_uri = base_uri + @entry_points = {} + discover_entry_points() + end + + def method_missing(method_name, *args) + opts = args[0] if args[0].class.eql?(Hash) + opts ||= {} + puts opts.inspect + if @@COLLECTIONS.include?(method_name.to_sym) + if opts[:id] + object = Nokogiri::XML(get("%s/%s" % @entry_points[method_name.to_s], opts[:id])) + element = method_name.to_s + element = 'data_centers' if method_name.eql?(:datacenters) + inst = RHEVM.const_get(element.classify) + puts inst.inspect + return inst::new(self, object) + else + objects = Nokogiri::XML(get(@entry_points[method_name.to_s])) + objects_arr = [] + element = method_name.to_s + # FIXME: + # This is an exception/or bug in RHEV-M API: + # (uri is /datacenters but root element it 'data_centers') + element = 'data_centers' if method_name.eql?(:datacenters) + (objects/"#{element}/#{element.singularize}").each do |item| + inst = RHEVM.const_get(element.classify) + objects_arr << inst.new(self, item) + end + return objects_arr + end + end + end + + def vm_action(action, vm) + Nokogiri::XML(post("/vms/%s/" % action.to_s)) + end + + protected + + def get(uri) + headers = { + :authorization => "Basic " + Base64.encode64("#{@username}:#{@password}"), + :accept => "application/xml" + } + @logger << "GET #{uri}\n" + RestClient.get(uri, headers).to_s() + end + + def post(uri, params={}) + headers = { + :authorization => "Basic " + Base64.encode64("#{@username}:#{@password}"), + :accept => "application/xml" + } + @logger << "POST #{uri}\n" + RestClient.post(uri, params, headers).to_s + end + + def discover_entry_points() + return if @discovered + doc = Nokogiri.XML(get(@base_uri)) + doc.xpath('api/link').each() do |link| + @entry_points[link['rel']] = @base_uri + link['href'] + end + @discovered = true + end + + private + + def singularize(str) + str.gsub(/s$/, '') + end + + end + + class BaseModel + attr_accessor(:id, :href, :name) + + def initialize(client, xml) + client.logger << "#{xml}\n" + @client = client + @id = xml[:id] + @href = @client.base_uri + xml[:href] + @name = xml.xpath('name').text + end + end + + class StorageDomain < BaseModel + attr_accessor(:type, :status, :master, :storage_type, :address, :path, :host) + + def initialize(client, xml) + super(client, xml) + @type = xml.xpath('type').text + @status = xml.xpath('master').text + @storage_type = xml.xpath('storage/type') + @address = xml.xpath('storage/address').text + @path = xml.xpath('storage/path').text + @host = xml.xpath('host')[:id] + end + end + + class Vm < BaseModel + attr_accessor(:status, :memory, :sockets, :cores, :bootdevs, :host, :cluster, :template, :vmpool, :profile) + attr_accessor(:creation_time) + + def initialize(client, xml) + super(client, xml) + @status = xml.xpath('status').text + @memory = xml.xpath('memory').text + @profile = xml.xpath('type').text + @sockets = xml.xpath('cpu/topology').first[:sockets] + @cores = xml.xpath('cpu/topology').first[:cores] + @bootdevs = [] + xml.xpath('os/boot').each do |boot| + @bootdevs << boot[:dev] + end + @host = xml.xpath('host')[:id] + @cluster = xml.xpath('cluster').first[:id] + @template = xml.xpath('template').first[:id] + @vmpool = xml.xpath('vmpool').first[:id] + @creation_time = xml.xpath('creation_time').text + end + end + + class Template < BaseModel + attr_accessor(:status, :memory, :name, :description) + + def initialize(client, xml) + super(client, xml) + @status = (xml/'status').text + @memory = (xml/'memory').text + @description = (xml/'description').text + end + end + + class DataCenter < BaseModel + attr_accessor :name, :storage_type, :description + + def initialize(client, xml) + super(client, xml) + @name, @storage_type, @description = (xml/'name').text, (xml/'storage_type').text, (xml/'description').text + end + end + + class Cluster < BaseModel + attr_accessor :name, :datacenter_id, :cpu + + def initialize(client, xml) + super(client, xml) + @name = (xml/'name').text + @datacenter_id = (xml/'data_center').first['id'] + @cpu = (xml/'cpu').first['id'] + @name = (xml/'name').text + end + end + +end + +class String + + unless method_defined?(:classify) + # Create a class name from string + def classify + self.singularize.camelize + end + end + + unless method_defined?(:camelize) + # Camelize converts strings to UpperCamelCase + def camelize + self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + end + end + + unless method_defined?(:singularize) + # Strip 's' character from end of string + def singularize + self.gsub(/s$/, '') + end + end + + # Convert string to float if string value seems like Float + def convert + return self.to_f if self.strip =~ /^([\d\.]+$)/ + self + end + + # Simply converts whitespaces and - symbols to '_' which is safe for Ruby + def sanitize + self.strip.gsub(/(\W+)/, '_') + end + +end diff --git a/server/lib/drivers.rb b/server/lib/drivers.rb index 420621c..a164f28 100644 --- a/server/lib/drivers.rb +++ b/server/lib/drivers.rb @@ -21,6 +21,7 @@ module Deltacloud :rackspace => { :name => "Rackspace" }, :gogrid => { :name => "Gogrid" }, :rhevm => { :name => "RHEVM" }, + :rhevm2 => { :name => "RHEVM2" }, :rimuhosting => { :name => "RimuHosting"}, :opennebula => { :name => "Opennebula", :class => "OpennebulaDriver" }, :terremark => { :name => "Terremark"}, -- 1.7.3.3