From 99cfdf7843f712c47099e01a8a9b60e9c2c05ad2 Mon Sep 17 00:00:00 2001 From: Jan Orel Date: Tue, 21 Nov 2023 17:11:20 +0100 Subject: [PATCH] B #6318: OneProvision: update equinix provider (#2826) --- install.sh | 1 + .../{equinix-am6.yml => equinix-am.yml} | 2 +- .../{equinix-ny5.yml => equinix-ny.yml} | 2 +- .../{equinix-sv15.yml => equinix-sv.yml} | 2 +- .../{equinix-ty11.yml => equinix-ty.yml} | 2 +- .../etc/provision/providers.d/equinix.yaml | 4 +- .../server/routes/api/oneprovision/schemas.js | 6 +- .../remotes/equinix/register_address_range | 52 +++++++------ .../remotes/equinix/unregister_address_range | 18 +++-- .../lib/terraform/providers/equinix.rb | 6 +- .../providers/templates/equinix/datastore.erb | 2 +- .../providers/templates/equinix/host.erb | 8 +- .../providers/templates/equinix/network.erb | 2 +- .../providers/templates/equinix/provider.erb | 9 +-- src/vnm_mad/remotes/elastic/aws_vnm.rb | 4 +- src/vnm_mad/remotes/elastic/equinix.rb | 42 +++++++++++ src/vnm_mad/remotes/elastic/equinix_vnm.rb | 75 ++++++++++++++----- src/vnm_mad/remotes/elastic/vultr_vnm.rb | 4 +- 18 files changed, 166 insertions(+), 75 deletions(-) rename share/oneprovision/edge-clusters/metal/providers/equinix/{equinix-am6.yml => equinix-am.yml} (96%) rename share/oneprovision/edge-clusters/metal/providers/equinix/{equinix-ny5.yml => equinix-ny.yml} (96%) rename share/oneprovision/edge-clusters/metal/providers/equinix/{equinix-sv15.yml => equinix-sv.yml} (96%) rename share/oneprovision/edge-clusters/metal/providers/equinix/{equinix-ty11.yml => equinix-ty.yml} (95%) create mode 100644 src/vnm_mad/remotes/elastic/equinix.rb diff --git a/install.sh b/install.sh index b5bfd13351..92fd838c1b 100755 --- a/install.sh +++ b/install.sh @@ -1033,6 +1033,7 @@ RUBY_LIB_FILES="src/mad/ruby/ActionManager.rb \ src/vmm_mad/remotes/equinix/equinix_driver.rb \ src/vnm_mad/remotes/elastic/aws_vnm.rb \ src/vnm_mad/remotes/elastic/equinix_vnm.rb \ + src/vnm_mad/remotes/elastic/equinix.rb \ src/vnm_mad/remotes/elastic/vultr_vnm.rb" #------------------------------------------------------------------------------- diff --git a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am6.yml b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am.yml similarity index 96% rename from share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am6.yml rename to share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am.yml index 4976070a94..de82e7c96a 100644 --- a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am6.yml +++ b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-am.yml @@ -9,7 +9,7 @@ plain: connection: token: 'Equinix token' project: 'Equinix project' - facility: 'am6' + metro: 'am' inputs: - name: 'equinix_os' diff --git a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny5.yml b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny.yml similarity index 96% rename from share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny5.yml rename to share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny.yml index 2c8fec3179..dd8fe16b67 100644 --- a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny5.yml +++ b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ny.yml @@ -9,7 +9,7 @@ plain: connection: token: 'Equinix token' project: 'Equinix project' - facility: 'ny5' + metro: 'ny' inputs: - name: 'equinix_os' diff --git a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv15.yml b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv.yml similarity index 96% rename from share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv15.yml rename to share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv.yml index 6e6837c0ea..f60d1fde12 100644 --- a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv15.yml +++ b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-sv.yml @@ -9,7 +9,7 @@ plain: connection: token: 'Equinix token' project: 'Equinix project' - facility: 'sv15' + metro: 'sv' inputs: - name: 'equinix_os' diff --git a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty11.yml b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty.yml similarity index 95% rename from share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty11.yml rename to share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty.yml index a527a8b936..8c1f788850 100644 --- a/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty11.yml +++ b/share/oneprovision/edge-clusters/metal/providers/equinix/equinix-ty.yml @@ -9,7 +9,7 @@ plain: connection: token: 'Equinix token' project: 'Equinix project' - facility: 'ty11' + metro: 'ty' inputs: - name: 'equinix_os' diff --git a/src/fireedge/etc/provision/providers.d/equinix.yaml b/src/fireedge/etc/provision/providers.d/equinix.yaml index 775b743ddb..cf95cfd7e7 100644 --- a/src/fireedge/etc/provision/providers.d/equinix.yaml +++ b/src/fireedge/etc/provision/providers.d/equinix.yaml @@ -1,5 +1,5 @@ name: 'Equinix' image: 'EQUINIX' provision_type: 'metal' -location_key: 'facility' -color: '#364562' \ No newline at end of file +location_key: 'metro' +color: '#364562' diff --git a/src/fireedge/src/server/routes/api/oneprovision/schemas.js b/src/fireedge/src/server/routes/api/oneprovision/schemas.js index b65e3c4fe0..247880019e 100644 --- a/src/fireedge/src/server/routes/api/oneprovision/schemas.js +++ b/src/fireedge/src/server/routes/api/oneprovision/schemas.js @@ -38,7 +38,7 @@ const provision = { packet_project: { type: 'string', }, - facility: { + metro: { type: 'string', }, plan: { @@ -66,12 +66,12 @@ const provision = { }, im_mad: { type: 'string', - enum: ['kvm', 'firecraker'], + enum: ['kvm', 'firecracker'], required: true, }, vm_mad: { type: 'string', - enum: ['kvm', 'firecraker'], + enum: ['kvm', 'firecracker'], required: true, }, provision: { diff --git a/src/ipamm_mad/remotes/equinix/register_address_range b/src/ipamm_mad/remotes/equinix/register_address_range index dd8f0b9024..a576416974 100755 --- a/src/ipamm_mad/remotes/equinix/register_address_range +++ b/src/ipamm_mad/remotes/equinix/register_address_range @@ -102,7 +102,10 @@ $LOAD_PATH << EQUINIX_LOCATION $LOAD_PATH << RUBY_LIB_LOCATION $LOAD_PATH << LIB_LOCATION + '/oneprovision/lib' -require 'packet' +require 'net/http' +require 'uri' +require 'json' +require 'equinix' require 'base64' require 'nokogiri' require 'opennebula' @@ -168,9 +171,9 @@ begin provider = provision.provider connect = provider.body['connection'] - pk_token = connect['token'] - pk_project = connect['project'] - pk_facility = connect['facility'] + eq_token = connect['token'] + eq_project = connect['project'] + eq_metro = connect['metro'] # -------------------------------------------------------------------------- # Connect to Equinix and allocate a new IP @@ -184,31 +187,36 @@ begin cidr = IPAddr.new(private_cidr) mask = 0x0FFFFFFFF >> cidr.prefix - equinix = Packet::Client.new - equinix.auth_token = pk_token + ip_req = { 'type' => data.xpath('//AR/EQUINIX_IP_TYPE').text, + 'quantity' => data.xpath('//AR/SIZE').text.to_i, + 'metro' => eq_metro, + 'fail_on_approval_required' => true } - ip = Packet::Ip.new - ip.project_id = pk_project - ip.facility = pk_facility - ip.type = data.xpath('//AR/EQUINIX_IP_TYPE').text - ip.quantity = data.xpath('//AR/SIZE').text.to_i - - if ip.quantity != 1 + if ip_req['quantity'] != 1 STDERR.puts 'Only address ranges of size 1 are supported' exit(-1) end - if ip.type.empty? - ip.type = IP_TYPE[0] - elsif !IP_TYPE.include?(ip.type) - STDERR.puts "Type #{ip.type} not supported. " \ + if ip_req['type'].empty? + ip_req['type'] = IP_TYPE[0] + elsif !IP_TYPE.include?(ip_req['type']) + STDERR.puts "Type #{ip_req['type']} not supported. " \ "Must be: #{IP_TYPE.join(', ')}" exit(-1) end - equinix.create_ip(ip) + eq = Equinix.new(eq_token) - ipmd5 = Digest::MD5.hexdigest(ip.address.to_s).to_i(16) & mask + resp = eq.api_call("/projects/#{eq_project}/ips", Net::HTTP::Post, ip_req) + + unless resp.code == '201' + STDERR.puts "Equinix API failure, HTTP #{resp.code}, #{resp.message}, #{resp.body}" + exit(-1) + end + + addr, id = JSON.parse(resp.body).fetch_values('address', 'id') + + ipmd5 = Digest::MD5.hexdigest(addr).to_i(16) & mask eip = IPAddr.new(ipmd5, Socket::AF_INET) ipvm = (eip & mask) | cidr @@ -218,12 +226,12 @@ begin AR = [ TYPE = "IP4", IP = "#{ipvm}", - SIZE = "#{ip.quantity}", + SIZE = "#{ip_req['quantity']}", IPAM_MAD = "equinix", GATEWAY = "#{ipgw}", - EXTERNAL_IP = "#{ip.address}", + EXTERNAL_IP = "#{addr}", NETWORK_MASK = "255.255.255.254", - EQUINIX_IP_ID = "#{ip.id}", + EQUINIX_IP_ID = "#{id}", PROVISION_ID = "#{provision_id}" ] EOF diff --git a/src/ipamm_mad/remotes/equinix/unregister_address_range b/src/ipamm_mad/remotes/equinix/unregister_address_range index ad39d3bf91..de6cab6db7 100755 --- a/src/ipamm_mad/remotes/equinix/unregister_address_range +++ b/src/ipamm_mad/remotes/equinix/unregister_address_range @@ -69,7 +69,10 @@ $LOAD_PATH << EQUINIX_LOCATION $LOAD_PATH << RUBY_LIB_LOCATION $LOAD_PATH << LIB_LOCATION + '/oneprovision/lib' -require 'packet' +require 'net/http' +require 'uri' +require 'json' +require 'equinix' require 'base64' require 'nokogiri' require 'opennebula' @@ -99,13 +102,12 @@ begin provider = provision.provider connect = provider.body['connection'] - pk_token = connect['token'] + + eq_token = connect['token'] # -------------------------------------------------------------------------- # Connect to Equinix and delete the IP # -------------------------------------------------------------------------- - equinix = Packet::Client.new - equinix.auth_token = pk_token equinix_id = data.xpath('//AR/EQUINIX_IP_ID').text.to_s @@ -114,7 +116,13 @@ begin exit(-1) end - equinix.delete_ip(equinix_id) + eq = Equinix.new(eq_token) + resp = eq.api_call("/ips/#{equinix_id}", Net::HTTP::Delete) + + unless resp.code == '204' + STDERR.puts "Equinix API failure, HTTP #{resp.code}, #{resp.message}, #{resp.body}" + exit(-1) + end rescue StandardError => e STDERR.puts e.to_s exit(-1) diff --git a/src/oneprovision/lib/terraform/providers/equinix.rb b/src/oneprovision/lib/terraform/providers/equinix.rb index 9af076f214..862fab3a44 100644 --- a/src/oneprovision/lib/terraform/providers/equinix.rb +++ b/src/oneprovision/lib/terraform/providers/equinix.rb @@ -26,12 +26,10 @@ module OneProvision # OpenNebula - Terraform equivalence TYPES = { - :datastore => 'packet_volume', - :host => 'packet_device', - :network => 'packet_reserved_ip_block' + :host => 'equinix_metal_device' } - KEYS = ['project', 'token', 'facility'] + KEYS = ['project', 'token', 'metro'] # Class constructor # diff --git a/src/oneprovision/lib/terraform/providers/templates/equinix/datastore.erb b/src/oneprovision/lib/terraform/providers/templates/equinix/datastore.erb index b8b0092c60..e5a565d2d1 100644 --- a/src/oneprovision/lib/terraform/providers/templates/equinix/datastore.erb +++ b/src/oneprovision/lib/terraform/providers/templates/equinix/datastore.erb @@ -1,6 +1,6 @@ #resource "packet_volume" "device_<%= obj['ID'] %>" { # description = "<%= obj['ID'] %>_volume" -# facility = "<%= provision['FACILITY'] %>" +# metro = "<%= provision['metro'] %>" # project_id = "<%= provision['PROJECT'] %>" # plan = "<%= provision['PLAN'] %>" # size = "<%= obj['TOTAL_MB'] %>" diff --git a/src/oneprovision/lib/terraform/providers/templates/equinix/host.erb b/src/oneprovision/lib/terraform/providers/templates/equinix/host.erb index 90ddbd0b13..71e083fa71 100644 --- a/src/oneprovision/lib/terraform/providers/templates/equinix/host.erb +++ b/src/oneprovision/lib/terraform/providers/templates/equinix/host.erb @@ -1,7 +1,7 @@ -resource "packet_device" "device_<%= obj['ID'] %>" { +resource "equinix_metal_device" "device_<%= obj['ID'] %>" { hostname = "<%= provision['HOSTNAME'] %>" plan = "<%= provision['PLAN'] %>" - facilities = ["<%= provision['FACILITY'] %>"] + metro = "<%= provision['METRO'] %>" operating_system = "<%= provision['OS'] %>" project_id = "<%= provision['PROJECT']%>" billing_cycle = "hourly" @@ -10,10 +10,10 @@ resource "packet_device" "device_<%= obj['ID'] %>" { } output "device_<%= obj['ID'] %>_ip" { - value = packet_device.device_<%= obj['ID'] %>.network[0].address + value = equinix_metal_device.device_<%= obj['ID'] %>.network[0].address } output "device_<%= obj['ID'] %>_id" { - value = packet_device.device_<%= obj['ID'] %>.id + value = equinix_metal_device.device_<%= obj['ID'] %>.id } diff --git a/src/oneprovision/lib/terraform/providers/templates/equinix/network.erb b/src/oneprovision/lib/terraform/providers/templates/equinix/network.erb index 08d9314eaf..214ed7e325 100644 --- a/src/oneprovision/lib/terraform/providers/templates/equinix/network.erb +++ b/src/oneprovision/lib/terraform/providers/templates/equinix/network.erb @@ -1,6 +1,6 @@ <%# resource "packet_reserved_ip_block" "device_<%= obj['ID'] %1>" { %> <%# project_id = "<%= provision['PROJECT'] %1>" %> -<%# facility = "<%= provision['FACILITY'] %1>" %> +<%# metro = "<%= provision['METRO'] %1>" %> <%# <% if obj['AR_POOL'] && obj['AR_POOL']['AR'] && obj['AR_POOL']['AR']['SIZE'] %1> %> <%# quantity = "<%= obj['AR_POOL']['AR']['SIZE'] %1>" %> <%# <% else %1> %> diff --git a/src/oneprovision/lib/terraform/providers/templates/equinix/provider.erb b/src/oneprovision/lib/terraform/providers/templates/equinix/provider.erb index e152b9e967..dbca5954b2 100644 --- a/src/oneprovision/lib/terraform/providers/templates/equinix/provider.erb +++ b/src/oneprovision/lib/terraform/providers/templates/equinix/provider.erb @@ -1,13 +1,12 @@ terraform { required_providers { - packet = { - source = "packethost/packet" + equinix = { + source = "equinix/equinix" } } required_version = ">= 0.13" } -provider "packet" { - auth_token = "<%= conn['TOKEN'] %>" +provider "equinix" { + auth_token = "<%= conn['TOKEN'] %>" } - diff --git a/src/vnm_mad/remotes/elastic/aws_vnm.rb b/src/vnm_mad/remotes/elastic/aws_vnm.rb index 140f43c5e5..d96fd6a480 100644 --- a/src/vnm_mad/remotes/elastic/aws_vnm.rb +++ b/src/vnm_mad/remotes/elastic/aws_vnm.rb @@ -110,7 +110,7 @@ class AWSProvider 0 rescue StandardError => e - OpenNebula.log_error("Error assiging #{ip}:#{e.message}") + OpenNebula.log_error("Error assigning #{ip}:#{e.message}") 1 end @@ -133,7 +133,7 @@ class AWSProvider :private_ip_addresses => [aws_ip.private_ip_address] } ) rescue StandardError - OpenNebula.log_error("Error unassiging #{ip}:#{e.message}") + OpenNebula.log_error("Error unassigning #{ip}:#{e.message}") end end diff --git a/src/vnm_mad/remotes/elastic/equinix.rb b/src/vnm_mad/remotes/elastic/equinix.rb new file mode 100644 index 0000000000..e6cc2734fe --- /dev/null +++ b/src/vnm_mad/remotes/elastic/equinix.rb @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2023, OpenNebula Project, OpenNebula Systems # +# # +# Licensed 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 'net/http' +require 'uri' +require 'json' + +ENDPOINT = 'https://api.equinix.com/metal/v1' + +# Class covering Equinix client +class Equinix + + def initialize(token) + @eq_token = token + @eq_endpoint = ENDPOINT + end + + def api_call(path, type = Net::HTTP::Get, data = {}) + uri = URI.parse(@eq_endpoint + path) + https = Net::HTTP.new(uri.host, uri.port) + https.use_ssl = true + req = type.new(uri.path) + req['Content-Type'] = 'application/json' + req['X-Auth-Token'] = @eq_token + req.body = data.to_json unless data.empty? + + return https.request(req) + end + +end diff --git a/src/vnm_mad/remotes/elastic/equinix_vnm.rb b/src/vnm_mad/remotes/elastic/equinix_vnm.rb index 064cda11af..2f1ac79fc3 100644 --- a/src/vnm_mad/remotes/elastic/equinix_vnm.rb +++ b/src/vnm_mad/remotes/elastic/equinix_vnm.rb @@ -51,46 +51,81 @@ end $LOAD_PATH << RUBY_LIB_LOCATION $LOAD_PATH << EQUINIX_LOCATION -require 'packet' +require 'net/http' +require 'uri' +require 'json' +require 'equinix' # Class covering Equinix functionality for Elastic driver class EquinixProvider def initialize(provider, host) - connect = provider.body['connection'] - - @client = Packet::Client.new(connect['token']) + @eq_token = provider.body['connection']['token'] @deploy_id = host['TEMPLATE/PROVISION/DEPLOY_ID'] end def assign(_ip, external, _opts = {}) - @client.assign_cidr_device("#{external}/32", @deploy_id) - 0 - rescue Packet::Error => e - # potential VM poweroff(itself) + resume - if e.message == '{"errors"=>["Address has already been taken"]}' - return 0 + ip_req = { :manageable => true, + :address => external } + + eq = Equinix.new(@eq_token) + + resp = eq.api_call("/devices/#{@deploy_id}/ips", + Net::HTTP::Post, + ip_req) + + # HTTP 422 is returned if IP already assigned to the device + # e.g. VM poweroff from inside + onevm resume + unless resp.code == '201' || resp.code == '422' + STDERR.puts "Error assigning #{external}:#{resp.message}" + return 1 end - OpenNebula.log_error("Error assiging #{external}:#{e.message}") - 1 + return 0 rescue StandardError => e - OpenNebula.log_error("Error assiging #{external}:#{e.message}") + OpenNebula.log_error("Error assigning #{external}:#{e.message}") 1 end def unassign(_ip, external, _opts = {}) - dev = @client.get_device(@deploy_id) + eq = Equinix.new(@eq_token) - ip = dev.ip_addresses.select do |i| - i['address'] == external && - i['cidr'] == 32 && - i['address_family'] == 4 + # find assignment id for external in device + resp = eq.api_call("/devices/#{@deploy_id}/ips") + + unless resp.code == '200' + STDERR.puts "Error unassigning #{external}:#{resp.message}" + return 1 end - @client.delete_ip(ip[0]['id']) + assignment_id = nil + JSON.parse(resp.body)['ip_addresses'].each do |ip| + if ip['address'] == external + assignment_id = ip['id'] + break + end + end + + unless assignment_id + STDERR.puts "Error unassigning #{external}:Can not find " << + "{external} ip in device #{@deploy_id}" + # although unexpected still harmless, consider OK + return 0 + end + + # unassign external ip + resp = eq.api_call("/ips/#{assignment_id}", Net::HTTP::Delete) + + unless resp.code == '204' + STDERR.puts "Equinix API failure, HTTP #{resp.code}, " << + "#{resp.message}, #{resp.body}" + return 1 + end + + return 0 rescue StandardError => e - OpenNebula.log_error("Error assiging #{external}:#{e.message}") + OpenNebula.log_error("Error unassigning #{external}:#{e.message}") + 1 end def activate(cmds, nic) diff --git a/src/vnm_mad/remotes/elastic/vultr_vnm.rb b/src/vnm_mad/remotes/elastic/vultr_vnm.rb index 93885460a7..fa5032e9aa 100644 --- a/src/vnm_mad/remotes/elastic/vultr_vnm.rb +++ b/src/vnm_mad/remotes/elastic/vultr_vnm.rb @@ -78,7 +78,7 @@ class VultrProvider if VultrError.error?(rc) return 0 if rc.message == 'IP is already attached to a server' - OpenNebula.log_error("Error assiging #{rc.message}") + OpenNebula.log_error("Error assigning #{rc.message}") return 1 end @@ -94,7 +94,7 @@ class VultrProvider rc = @client.detach_nic(@deploy_id, opts[:vultr_id]) if VultrError.error?(rc) - OpenNebula.log_error("Error unassiging #{rc.message}") + OpenNebula.log_error("Error unassigning #{rc.message}") return 1 end