Merge pull request #127 from MSOpenTech/vnet-ag

KNIFE-427: Add support to create virtual networks and affinity groups
This commit is contained in:
Adam Edwards 2014-01-20 07:07:37 -08:00
Родитель 8b6d2a588f fac32b275a
Коммит 09214ac1ff
30 изменённых файлов: 1101 добавлений и 21 удалений

Просмотреть файл

@ -240,6 +240,43 @@ Outputs a list of all servers in the currently configured Azure account. PLEASE
knife azure server list
### Azure AG List Subcommand
Outputs a list of defined affinity groups in the azure subscription.
knife azure ag list
### Azure AG Create Subcommand
Creates a new affinity group in the specified service location.
knife azure ag create -a 'mynewag' -m 'West US' --azure-ag-desc 'Optional Description'
Knife options:
:azure_affinity_group Specifies new affinity group name.
:azure_service_location Specifies the geographic location.
:azure_ag_desc Optional. Description for new affinity group.
### Azure Vnet List Subcommand
Outputs a list of defined virtual networks in the azure subscription.
knife azure vnet list
### Azure Vnet Create Subcommand
Creates a new or modifies an existing virtual network. If an existing virtual network is named, the
affinity group and address space are replaced with the new values.
knife azure vnet create -n 'mynewvn' -a 'existingag' --azure_address_space '10.0.0.0/24'
Knife options:
:azure_network_name Specifies the name of the virtual network to create.
:azure_affinity_group Specifies the affinity group to associate with the vnet.
:azure_address_space Specifies the address space of the vnet using CIDR notation.
For CIDR notation, see here: http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
Address available are defined in RFC 1918: http://en.wikipedia.org/wiki/Private_network
## Alternative Management Certificate Specification
In addition to specifying the management certificate using the publishsettings
file, you can also specify it in PEM format. Follow these steps to generate the certificate in the PEM format:

97
lib/azure/ag.rb Normal file
Просмотреть файл

@ -0,0 +1,97 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#
class Azure
class AGs
def initialize(connection)
@connection = connection
end
def load
@ags ||= begin
@ags = {}
response = @connection.query_azure('affinitygroups',
'get',
'',
'',
false)
response.css('AffinityGroup').each do |ag|
item = AG.new(@connection).parse(ag)
@ags[item.name] = item
end
@ags
end
end
def all
load.values
end
def exists?(name)
load.key?(name)
end
def find(name)
load[name]
end
def create(params)
ag = AG.new(@connection)
ag.create(params)
end
end
end
class Azure
class AG
attr_accessor :name, :label, :description, :location
def initialize(connection)
@connection = connection
end
def parse(image)
@name = image.at_css('Name').content
@label = image.at_css('Label').content
@description = image.at_css('Description').content if
image.at_css('Description')
@location = image.at_css('Location').content if image.at_css('Location')
self
end
def create(params)
builder = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
xml.CreateAffinityGroup(
xmlns: 'http://schemas.microsoft.com/windowsazure'
) do
xml.Name params[:azure_ag_name]
xml.Label Base64.strict_encode64(params[:azure_ag_name])
unless params[:azure_ag_desc].nil?
xml.Description params[:azure_ag_desc]
end
xml.Location params[:azure_location]
end
end
@connection.query_azure('affinitygroups',
'post',
builder.to_xml,
'',
false)
end
end
end

Просмотреть файл

@ -24,11 +24,14 @@ require File.expand_path('../role', __FILE__)
require File.expand_path('../disk', __FILE__)
require File.expand_path('../image', __FILE__)
require File.expand_path('../certificate', __FILE__)
require File.expand_path('../ag', __FILE__)
require File.expand_path('../vnet', __FILE__)
class Azure
class Connection
include AzureAPI
attr_accessor :hosts, :rest, :images, :deploys, :roles, :disks, :storageaccounts, :certificates
attr_accessor :hosts, :rest, :images, :deploys, :roles,
:disks, :storageaccounts, :certificates, :ags, :vnets
def initialize(params={})
@rest = Rest.new(params)
@hosts = Hosts.new(self)
@ -38,11 +41,19 @@ class Azure
@roles = Roles.new(self)
@disks = Disks.new(self)
@certificates = Certificates.new(self)
@ags = AGs.new(self)
@vnets = Vnets.new(self)
end
def query_azure(service_name, verb = 'get', body = '', params = '', wait= true)
def query_azure(service_name,
verb = 'get',
body = '',
params = '',
wait = true,
services = true)
Chef::Log.info 'calling ' + verb + ' ' + service_name
Chef::Log.debug body unless body == ''
response = @rest.query_azure(service_name, verb, body, params)
response = @rest.query_azure(service_name, verb, body, params, services)
if response.code.to_i == 200
ret_val = Nokogiri::XML response.body
elsif !wait && response.code.to_i == 202
@ -53,6 +64,7 @@ class Azure
else
if response.body
ret_val = Nokogiri::XML response.body
Chef::Log.debug ret_val.to_xml
Chef::Log.warn ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
else
Chef::Log.warn 'http error: ' + response.code

Просмотреть файл

@ -28,8 +28,15 @@ module AzureAPI
@host_name = params[:azure_api_host_name]
@verify_ssl = params[:verify_ssl_cert]
end
def query_azure(service_name, verb = 'get', body = '', params = '')
request_url = "https://#{@host_name}/#{@subscription_id}/services/#{service_name}"
def query_azure(service_name,
verb = 'get',
body = '',
params = '',
services = true)
svc_str = services ? '/services' : ''
request_url =
"https://#{@host_name}/#{@subscription_id}#{svc_str}/#{service_name}"
print '.'
uri = URI.parse(request_url)
uri.query = params
@ -72,9 +79,12 @@ module AzureAPI
request = Net::HTTP::Post.new(uri.request_uri)
elsif verb == 'delete'
request = Net::HTTP::Delete.new(uri.request_uri)
elsif verb == 'put'
request = Net::HTTP::Put.new(uri.request_uri)
end
text = verb == 'put'
request["x-ms-version"] = "2013-08-01"
request["content-type"] = "application/xml"
request["content-type"] = text ? "text/plain" : "application/xml"
request["accept"] = "application/xml"
request["accept-charset"] = "utf-8"
request.body = body

89
lib/azure/vnet.rb Normal file
Просмотреть файл

@ -0,0 +1,89 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#
class Azure
class Vnets
def initialize(connection)
@connection = connection
end
def load
@vnets ||= begin
@vnets = {}
response = @connection.query_azure('networking/virtualnetwork')
response.css('VirtualNetworkSite').each do |vnet|
item = Vnet.new(@connection).parse(vnet)
@vnets[item.name] = item
end
@vnets
end
end
def all
load.values
end
def exists?(name)
load.key?(name)
end
def find(name)
load[name]
end
def create(params)
ag = Vnet.new(@connection)
ag.create(params)
end
end
end
class Azure
class Vnet
attr_accessor :name, :affinity_group, :state
def initialize(connection)
@connection = connection
end
def parse(image)
@name = image.at_css('Name').content
@affinity_group = image.at_css('AffinityGroup').content
@state = image.at_css('State').content
self
end
def create(params)
response = @connection.query_azure('networking/media')
vnets = response.css('VirtualNetworkSite')
vnet = nil
vnets.each { |vn| vnet = vn if vn['name'] == params[:azure_vnet_name] }
add = vnet.nil?
vnet = Nokogiri::XML::Node.new('VirtualNetworkSite', response) if add
vnet['name'] = params[:azure_vnet_name]
vnet['AffinityGroup'] = params[:azure_ag_name]
addr_space = Nokogiri::XML::Node.new('AddressSpace', response)
addr_prefix = Nokogiri::XML::Node.new('AddressPrefix', response)
addr_prefix.content = params[:azure_address_space]
addr_space.children = addr_prefix
vnet.children = addr_space
vnets.last.add_next_sibling(vnet) if add
@connection.query_azure('networking/media', 'put', response.to_xml)
end
end
end

Просмотреть файл

@ -0,0 +1,76 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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 File.expand_path('../azure_base', __FILE__)
class Chef
class Knife
class AzureAgCreate < Knife
include Knife::AzureBase
banner 'knife azure ag create (options)'
option :azure_affinity_group,
:short => '-a GROUP',
:long => '--azure-affinity-group GROUP',
:description => 'Specifies new affinity group name.'
option :azure_ag_desc,
:long => '--azure-ag-desc DESC',
:description => 'Optional. Description for new affinity group.'
option :azure_service_location,
:short => '-m LOCATION',
:long => '--azure-service-location LOCATION',
:description => 'Specifies the geographic location - the name of '\
'the data center location that is valid for your '\
'subscription. Eg: West US, East US, '\
'East Asia, Southeast Asia, North Europe, West Europe'
def run
$stdout.sync = true
Chef::Log.info('validating...')
validate!([:azure_subscription_id,
:azure_mgmt_cert,
:azure_api_host_name,
:azure_affinity_group,
:azure_service_location])
params = {
azure_ag_name: locate_config_value(:azure_affinity_group),
azure_ag_desc: locate_config_value(:azure_ag_desc),
azure_location: locate_config_value(:azure_service_location),
}
rsp = connection.ags.create(params)
print "\n"
if rsp.at_css('Status').nil?
if rsp.at_css('Code').nil? || rsp.at_css('Message').nil?
puts 'Unknown Error. try -VV'
else
puts "#{rsp.at_css('Code').content}: "\
"#{rsp.at_css('Message').content}"
end
else
puts "Creation status: #{rsp.at_css('Status').content}"
end
end
end
end
end

Просмотреть файл

@ -0,0 +1,51 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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 File.expand_path('../azure_base', __FILE__)
class Chef
class Knife
class AzureAgList < Knife
include Knife::AzureBase
deps { require 'highline' }
banner 'knife azure ag list (options)'
def hl
@highline ||= HighLine.new
end
def run
$stdout.sync = true
validate!
cols = %w{Name Location Description}
the_list = cols.map { |col| ui.color(col, :bold) }
connection.ags.all.each do |ag|
cols.each { |attr| the_list << ag.send(attr.downcase).to_s }
end
puts "\n"
puts hl.list(the_list, :uneven_columns_across, cols.size)
end
end
end
end

Просмотреть файл

@ -50,8 +50,16 @@ class Chef
details << role.deployname
details << ui.color('Host name', :bold, :blue)
details << role.hostname
details << ui.color('SSH', :bold, :blue)
details << role.sshipaddress + ':' + role.sshport
unless role.sshport.nil?
details << ui.color('SSH port', :bold, :blue)
details << role.sshport
end
unless role.winrmport.nil?
details << ui.color('WinRM port', :bold, :blue)
details << role.winrmport
end
details << ui.color('Public IP', :bold, :blue)
details << role.publicipaddress
puts ui.list(details, :columns_across, 2)
if role.tcpports.length > 0 || role.udpports.length > 0
details.clear

Просмотреть файл

@ -0,0 +1,77 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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 File.expand_path('../azure_base', __FILE__)
class Chef
class Knife
class AzureVnetCreate < Knife
include Knife::AzureBase
banner 'knife azure vnet create (options)'
option :azure_network_name,
:short => '-n NETWORK_NAME',
:long => '--azure-network-name NETWORK_NAME',
:description =>
'Specifies the name of the virtual network to create.'
option :azure_affinity_group,
:short => '-a GROUP',
:long => '--azure-affinity-group GROUP',
:description =>
'Specifies the affinity group to associate with the vnet.'
option :azure_address_space,
:long => '--azure-address-space CIDR',
:description =>
'Specifies the address space of the vnet using CIDR notation.'
def run
$stdout.sync = true
Chef::Log.info('validating...')
validate!([:azure_subscription_id,
:azure_mgmt_cert,
:azure_api_host_name,
:azure_network_name,
:azure_affinity_group,
:azure_address_space])
params = {
azure_vnet_name: locate_config_value(:azure_network_name),
azure_ag_name: locate_config_value(:azure_affinity_group),
azure_address_space: locate_config_value(:azure_address_space),
}
rsp = connection.vnets.create(params)
print "\n"
if rsp.at_css('Status').nil?
if rsp.at_css('Code').nil? || rsp.at_css('Message').nil?
puts 'Unknown Error. try -VV'
else
puts "#{rsp.at_css('Code').content}: "\
"#{rsp.at_css('Message').content}"
end
else
puts "Creation status: #{rsp.at_css('Status').content}"
end
end
end
end
end

Просмотреть файл

@ -0,0 +1,52 @@
#
# Author:: Jeff Mendoza (jeffmendoza@live.com)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# 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 File.expand_path('../azure_base', __FILE__)
class Chef
class Knife
class AzureVnetList < Knife
include Knife::AzureBase
deps { require 'highline' }
banner 'knife azure vnet list (options)'
def hl
@highline ||= HighLine.new
end
def run
$stdout.sync = true
validate!
cols = ['Name', 'Affinity Group', 'State']
the_list = cols.map { |col| ui.color(col, :bold) }
connection.vnets.all.each do |vnet|
%w(name affinity_group state).each do |attr|
the_list << vnet.send(attr).to_s
end
end
puts "\n"
puts hl.list(the_list, :uneven_columns_across, cols.size)
end
end
end
end

Просмотреть файл

@ -0,0 +1,25 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe 'ags' do
before(:all) do
@connection = Azure::Connection.new(TEST_PARAMS)
end
it 'create' do
rsp = @connection.ags.create(azure_ag_name: 'func-test-new-ag',
azure_location: 'West US')
rsp.at_css('Status').should_not be_nil
rsp.at_css('Status').content.should eq('Succeeded')
end
specify { @connection.ags.exists?('notexist').should eq(false) }
specify { @connection.ags.exists?('func-test-new-ag').should eq(true) }
it 'run through' do
@connection.ags.all.each do |ag|
ag.name.should_not be_nil
ag.location.should_not be_nil
end
end
end

Просмотреть файл

@ -25,14 +25,12 @@ describe "deploys" do
role.size.should_not be_nil
role.ipaddress.should_not be_nil
role.sshport.should_not be_nil
role.sshipaddress.should_not be_nil
Chef::Log.info '============================='
Chef::Log.info 'role: ' + role.name
Chef::Log.info 'status: ' + role.status
Chef::Log.info 'size: ' + role.size
Chef::Log.info 'ip address: ' + role.ipaddress
Chef::Log.info 'ssh port: ' + role.sshport
Chef::Log.info 'ssh ip address: ' + role.sshipaddress
end
end
end

Просмотреть файл

@ -7,9 +7,9 @@ describe "Connection" do
@items = @connection.hosts.all
end
specify {@items.length.should be > 0}
specify {@connection.hosts.exists("thisServiceShouldNotBeThere").should == false}
specify{@connection.hosts.exists("service002").should == true}
specify { @items.length.should be > 0 }
specify { @connection.hosts.exists?('thisServiceShouldNotBeThere').should == false }
specify { @connection.hosts.exists?('service002').should == true }
it "looking for a specific host" do
foundNamedHost = false
@items.each do |host|

Просмотреть файл

@ -6,8 +6,8 @@ describe "roles" do
@roles = @connection.roles.all
end
specify {@connection.roles.exists('notexist').should == false}
specify {@connection.roles.exists('role126').should == true}
specify { @connection.roles.exists?('notexist').should == false }
specify { @connection.roles.exists?('role126').should == true }
it 'run through roles' do
@connection.roles.roles.each do |role|
role.name.should_not be_nil

Просмотреть файл

@ -0,0 +1,30 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe 'vnets' do
before(:all) do
@connection = Azure::Connection.new(TEST_PARAMS)
@connection.ags.create(azure_ag_name: 'func-test-agforvnet',
azure_location: 'West US')
end
it 'create' do
rsp = @connection.vnets.create(
azure_vnet_name: 'func-test-new-vnet',
azure_ag_name: 'func-test-agforvnet',
azure_address_space: '10.0.0.0/16')
rsp.at_css('Status').should_not be_nil
rsp.at_css('Status').content.should eq('Succeeded')
end
specify { @connection.vnets.exists?('notexist').should eq(false) }
specify { @connection.vnets.exists?('func-test-new-vnet').should eq(true) }
it 'run through' do
@connection.vnets.all.each do |vnet|
vnet.name.should_not be_nil
vnet.affinity_group.should_not be_nil
vnet.state.should_not be_nil
end
end
end

Просмотреть файл

@ -10,11 +10,15 @@ require 'azure/role'
require 'azure/disk'
require 'azure/utility'
require 'chef/knife/azure_server_list'
require 'chef/knife/azure_server_delete'
require 'chef/knife/azure_server_create'
require 'chef/knife/azure_server_describe'
require 'chef/knife/azure_ag_create'
require 'chef/knife/azure_ag_list'
require 'chef/knife/azure_image_list'
require 'chef/knife/azure_server_create'
require 'chef/knife/azure_server_delete'
require 'chef/knife/azure_server_describe'
require 'chef/knife/azure_server_list'
require 'chef/knife/azure_vnet_create'
require 'chef/knife/azure_vnet_list'
require 'fileutils'
require "securerandom"

48
spec/unit/ags_spec.rb Normal file
Просмотреть файл

@ -0,0 +1,48 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/query_azure_mock')
describe 'ags' do
include AzureSpecHelper
include QueryAzureMock
before 'setup connection' do
setup_query_azure_mock
end
context 'mock with actually retrieved values' do
it 'should find strings' do
items = @connection.ags.all
items.length.should be > 1
items.each do |ag|
ag.name.should_not be_nil
ag.label.should_not be_nil
ag.location.should_not be_nil
end
end
it 'should contain West US ag' do
items = @connection.ags.all
found_us = false
items.each do |item|
found_us = true if item.location == 'West US'
end
found_us.should == true
end
end
context 'create a new affinity group' do
it 'using explicity parameters it should pass in expected body' do
params = {
azure_ag_name: 'new-ag',
azure_ag_desc: 'ag description',
azure_location: 'West US'
}
@connection.ags.create(params)
@postname.should eq('affinitygroups')
@postverb.should eq('post')
Nokogiri::XML(@postbody).should be_equivalent_to(
Nokogiri::XML readFile('create_ag_for_new-ag.xml')
)
end
end
end

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<CreateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure">
<Name>new-ag</Name>
<Label>bmV3LWFn</Label>
<Description>ag description</Description>
<Location>West US</Location>
</CreateAffinityGroup>

Просмотреть файл

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<NetworkConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration">
<VirtualNetworkConfiguration>
<Dns />
<VirtualNetworkSites>
<VirtualNetworkSite name="jm-vnet-test" AffinityGroup="jm-affinity-group">
<AddressSpace>
<AddressPrefix>192.168.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>192.168.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnet-test-2" AffinityGroup="test">
<AddressSpace>
<AddressPrefix>172.16.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>172.16.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnname" AffinityGroup="agname">
<AddressSpace>
<AddressPrefix>10.0.0.0/16</AddressPrefix>
<AddressPrefix>172.16.0.0/20</AddressPrefix>
</AddressSpace>
</VirtualNetworkSite>
</VirtualNetworkSites>
</VirtualNetworkConfiguration>
</NetworkConfiguration>

Просмотреть файл

@ -0,0 +1 @@
<AffinityGroups xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><AffinityGroup><Name>agname</Name><Label>YWdsYWJlbA==</Label><Description>agdesc</Description><Location>West US</Location><Capabilities><Capability>PersistentVMRole</Capability><Capability>HighMemory</Capability></Capabilities></AffinityGroup><AffinityGroup><Name>jm-affinity-group</Name><Label>am0tYWZmaW5pdHktZ3JvdXA=</Label><Description/><Location>West US</Location><Capabilities><Capability>PersistentVMRole</Capability><Capability>HighMemory</Capability></Capabilities></AffinityGroup><AffinityGroup><Name>test</Name><Label>dGVzdA==</Label><Description>testdesc</Description><Location>West US</Location><Capabilities><Capability>PersistentVMRole</Capability><Capability>HighMemory</Capability></Capabilities></AffinityGroup></AffinityGroups>

Просмотреть файл

@ -0,0 +1 @@
<VirtualNetworkSites xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><VirtualNetworkSite><Name>jm-vnet-test</Name><Id>dba15bb3-6a60-4fd3-bc86-c79af3301f66</Id><AffinityGroup>jm-affinity-group</AffinityGroup><State>Created</State><AddressSpace><AddressPrefixes><AddressPrefix>192.168.0.0/20</AddressPrefix></AddressPrefixes></AddressSpace><Subnets><Subnet><Name>Subnet-1</Name><AddressPrefix>192.168.0.0/23</AddressPrefix></Subnet></Subnets></VirtualNetworkSite><VirtualNetworkSite><Name>vnet-test-2</Name><Id>ee225a95-04be-4943-ae5e-f0ec2a5d522b</Id><AffinityGroup>test</AffinityGroup><State>Created</State><AddressSpace><AddressPrefixes><AddressPrefix>172.16.0.0/20</AddressPrefix></AddressPrefixes></AddressSpace><Subnets><Subnet><Name>Subnet-1</Name><AddressPrefix>172.16.0.0/23</AddressPrefix></Subnet></Subnets></VirtualNetworkSite><VirtualNetworkSite><Name>vnname</Name><Id>5d77967b-d740-44e0-aa72-5f21b045060b</Id><AffinityGroup>agname</AffinityGroup><State>Created</State><AddressSpace><AddressPrefixes><AddressPrefix>10.0.0.0/16</AddressPrefix></AddressPrefixes></AddressSpace><Subnets/></VirtualNetworkSite></VirtualNetworkSites>

Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<NetworkConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration">
<VirtualNetworkConfiguration>
<Dns />
<VirtualNetworkSites>
<VirtualNetworkSite name="jm-vnet-test" AffinityGroup="jm-affinity-group">
<AddressSpace>
<AddressPrefix>192.168.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>192.168.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnet-test-2" AffinityGroup="test">
<AddressSpace>
<AddressPrefix>172.16.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>172.16.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnname" AffinityGroup="new-agname">
<AddressSpace>
<AddressPrefix>192.168.0.0/20</AddressPrefix>
</AddressSpace>
</VirtualNetworkSite>
</VirtualNetworkSites>
</VirtualNetworkConfiguration>
</NetworkConfiguration>

Просмотреть файл

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<NetworkConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration">
<VirtualNetworkConfiguration>
<Dns />
<VirtualNetworkSites>
<VirtualNetworkSite name="jm-vnet-test" AffinityGroup="jm-affinity-group">
<AddressSpace>
<AddressPrefix>192.168.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>192.168.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnet-test-2" AffinityGroup="test">
<AddressSpace>
<AddressPrefix>172.16.0.0/20</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet-1">
<AddressPrefix>172.16.0.0/23</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
<VirtualNetworkSite name="vnname" AffinityGroup="agname">
<AddressSpace>
<AddressPrefix>10.0.0.0/16</AddressPrefix>
<AddressPrefix>172.16.0.0/20</AddressPrefix>
</AddressSpace>
</VirtualNetworkSite>
<VirtualNetworkSite name="new-vn" AffinityGroup="someag">
<AddressSpace>
<AddressPrefix>10.0.0.0/16</AddressPrefix>
</AddressSpace>
</VirtualNetworkSite>
</VirtualNetworkSites>
</VirtualNetworkConfiguration>
</NetworkConfiguration>

Просмотреть файл

@ -0,0 +1,40 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../unit/query_azure_mock')
describe Chef::Knife::AzureAgCreate do
include AzureSpecHelper
include QueryAzureMock
before do
@server_instance = Chef::Knife::AzureAgCreate.new
{
azure_subscription_id: 'azure_subscription_id',
azure_mgmt_cert: @cert_file,
azure_api_host_name: 'preview.core.windows-int.net',
}.each do |key, value|
Chef::Config[:knife][key] = value
end
stub_query_azure(@server_instance.connection)
@server_instance.stub(:puts)
@server_instance.stub(:print)
end
it 'should fail missing args.' do
@server_instance.connection.ags.should_not_receive(:create)
@server_instance.ui.should_receive(:error).twice
expect { @server_instance.run }.to raise_error
end
it 'should succeed.' do
Chef::Config[:knife][:azure_affinity_group] = 'new-ag'
Chef::Config[:knife][:azure_service_location] = 'West US'
@server_instance.connection.ags.should_receive(:create).with(
azure_ag_name: 'new-ag',
azure_ag_desc: nil,
azure_location: 'West US',
).and_call_original
@server_instance.ui.should_not_receive(:warn)
@server_instance.ui.should_not_receive(:error)
@server_instance.run
end
end

Просмотреть файл

@ -0,0 +1,40 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../unit/query_azure_mock')
describe Chef::Knife::AzureAgList do
include AzureSpecHelper
include QueryAzureMock
before do
@server_instance = Chef::Knife::AzureAgList.new
{
:azure_subscription_id => 'azure_subscription_id',
:azure_mgmt_cert => @cert_file,
:azure_api_host_name => 'preview.core.windows-int.net',
:azure_service_location => 'West Europe',
:azure_source_image =>
'SUSE__SUSE-Linux-Enterprise-Server-11SP2-20120521-en-us-30GB.vhd',
:azure_dns_name => 'service001',
:azure_vm_name => 'vm01',
:azure_storage_account => 'ka001testeurope',
:azure_vm_size => 'Small'
}.each do |key, value|
Chef::Config[:knife][key] = value
end
stub_query_azure(@server_instance.connection)
@server_instance.stub(:items).and_return(:true)
@server_instance.stub(:puts)
end
it 'should display Name, Location, and Description columns.' do
@server_instance.hl.should_receive(:list).with(
['Name', 'Location', 'Description',
'agname', 'West US', 'agdesc',
'jm-affinity-group', 'West US', '',
'test', 'West US', 'testdesc',
],
:uneven_columns_across,
3)
@server_instance.run
end
end

Просмотреть файл

@ -0,0 +1,117 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../unit/query_azure_mock')
describe Chef::Knife::AzureAgList do
include AzureSpecHelper
include QueryAzureMock
before do
@server_instance = Chef::Knife::AzureServerDescribe.new
{
:azure_subscription_id => 'azure_subscription_id',
:azure_mgmt_cert => @cert_file,
:azure_api_host_name => 'preview.core.windows-int.net',
}.each do |key, value|
Chef::Config[:knife][key] = value
end
stub_query_azure(@server_instance.connection)
@server_instance.stub(:puts)
end
it 'should display server information.' do
@server_instance.name_args = %w(role206 role001 role002 vm002 vm01 ssh-vm
winrm-vm vmname)
@server_instance.ui.should_receive(:list).with(
['Role name', 'role001',
'Status', 'ReadyRole',
'Size', 'Small',
'Hosted service name', 'service001',
'Deployment name', 'deployment001',
'Host name', 'role001',
'SSH port', '22',
'Public IP', '65.52.249.191'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Role name', 'role002',
'Status', 'RoleStateUnknown',
'Size', 'Small',
'Hosted service name', 'service001',
'Deployment name', 'deployment001',
'Host name', 'role002',
'SSH port', '23',
'Public IP', '65.52.249.191'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Role name', 'vm002',
'Status', 'ReadyRole',
'Size', 'ExtraSmall',
'Hosted service name', 'service001',
'Deployment name', 'deployment001',
'Host name', 'myVm2',
'SSH port', '22',
'Public IP', '65.52.251.57'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Ports open', 'Local port', 'IP', 'Public port',
'tcp', '66', '65.52.251.57', '66'],
:columns_across,
4
).exactly(3).times
@server_instance.ui.should_receive(:list).with(
['Role name', 'vm01',
'Status', 'ReadyRole',
'Size', 'ExtraSmall',
'Hosted service name', 'service002',
'Deployment name', 'testrequest',
'Host name', 'myVm',
'SSH port', '54047',
'Public IP', '65.52.251.144'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Role name', 'ssh-vm',
'Status', 'ReadyRole',
'Size', 'ExtraSmall',
'Hosted service name', 'service004',
'Deployment name', 'deployment004',
'Host name', 'ssh-vm',
'SSH port', '22',
'Public IP', '65.52.251.57'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Role name', 'winrm-vm',
'Status', 'ReadyRole',
'Size', 'Small',
'Hosted service name', 'service004',
'Deployment name', 'deployment004',
'Host name', 'winrm-vm',
'WinRM port', '5985',
'Public IP', '65.52.249.191'],
:columns_across,
2
)
@server_instance.ui.should_receive(:list).with(
['Role name', 'vmname',
'Status', 'ReadyRole',
'Size', 'ExtraSmall',
'Hosted service name', 'vmname',
'Deployment name', 'deployment001',
'Host name', 'myVm2',
'SSH port', '22',
'Public IP', '65.52.251.57'],
:columns_across,
2
)
@server_instance.run
end
end

Просмотреть файл

@ -0,0 +1,41 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../unit/query_azure_mock')
describe Chef::Knife::AzureVnetCreate do
include AzureSpecHelper
include QueryAzureMock
before do
@server_instance = Chef::Knife::AzureVnetCreate.new
{
azure_subscription_id: 'azure_subscription_id',
azure_mgmt_cert: @cert_file,
azure_api_host_name: 'preview.core.windows-int.net',
}.each do |key, value|
Chef::Config[:knife][key] = value
end
stub_query_azure(@server_instance.connection)
@server_instance.stub(:puts)
@server_instance.stub(:print)
end
it 'should fail missing args.' do
@server_instance.connection.vnets.should_not_receive(:create)
@server_instance.ui.should_receive(:error).exactly(3).times
expect { @server_instance.run }.to raise_error
end
it 'should succeed.' do
Chef::Config[:knife][:azure_network_name] = 'new-net'
Chef::Config[:knife][:azure_affinity_group] = 'ag'
Chef::Config[:knife][:azure_address_space] = '10.0.0.0/24'
@server_instance.connection.vnets.should_receive(:create).with(
azure_vnet_name: 'new-net',
azure_ag_name: 'ag',
azure_address_space: '10.0.0.0/24',
).and_call_original
@server_instance.ui.should_not_receive(:warn)
@server_instance.ui.should_not_receive(:error)
@server_instance.run
end
end

Просмотреть файл

@ -0,0 +1,32 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../unit/query_azure_mock')
describe Chef::Knife::AzureAgList do
include AzureSpecHelper
include QueryAzureMock
before do
@server_instance = Chef::Knife::AzureVnetList.new
{
:azure_subscription_id => 'azure_subscription_id',
:azure_mgmt_cert => @cert_file,
:azure_api_host_name => 'preview.core.windows-int.net',
}.each do |key, value|
Chef::Config[:knife][key] = value
end
stub_query_azure(@server_instance.connection)
@server_instance.stub(:puts)
end
it 'should display Name, Affinity Group, and State columns.' do
@server_instance.hl.should_receive(:list).with(
['Name', 'Affinity Group', 'State',
'jm-vnet-test', 'jm-affinity-group', 'Created',
'vnet-test-2', 'test', 'Created',
'vnname', 'agname', 'Created'],
:uneven_columns_across,
3
)
@server_instance.run
end
end

Просмотреть файл

@ -47,7 +47,13 @@ module QueryAzureMock
Chef::Log.info 'calling web service:' + name
if verb == 'get' || verb == nil
retval = ''
if name == 'images'
if name == 'affinitygroups'
retval = Nokogiri::XML readFile('list_affinitygroups.xml')
elsif name == 'networking/virtualnetwork'
retval = Nokogiri::XML readFile('list_vnets.xml')
elsif name == 'networking/media'
retval = Nokogiri::XML readFile('get_network.xml')
elsif name == 'images'
retval = Nokogiri::XML readFile('list_images.xml')
elsif name == 'disks'
retval = Nokogiri::XML readFile('list_disks.xml')
@ -86,7 +92,10 @@ module QueryAzureMock
@getverb = verb
@getbody = body
elsif verb == 'post'
if name == 'hostedservices'
if name == 'affinitygroups'
retval = Nokogiri::XML readFile('post_success.xml')
@receivedXML = body
elsif name == 'hostedservices'
retval = Nokogiri::XML readFile('post_success.xml')
@receivedXML = body
elsif name == 'hostedservices/unknown_yet/deployments'
@ -117,6 +126,14 @@ module QueryAzureMock
@deletebody = body
@deleteparams = params
@deletecount += 1
elsif verb == 'put'
if name == 'networking/media'
retval = Nokogiri::XML readFile('post_success.xml')
@receivedXML = body
end
@postname = name
@postverb = verb
@postbody = body
else
Chef::Log.warn 'unknown verb:' + verb
end

64
spec/unit/vnet_spec.rb Normal file
Просмотреть файл

@ -0,0 +1,64 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/query_azure_mock')
describe 'vnets' do
include AzureSpecHelper
include QueryAzureMock
before 'setup connection' do
setup_query_azure_mock
end
context 'mock with actually retrieved values' do
it 'should find strings' do
items = @connection.vnets.all
items.length.should be > 1
items.each do |vnet|
vnet.name.should_not be_nil
vnet.affinity_group.should_not be_nil
vnet.state.should_not be_nil
end
end
it 'should find correct vnets.' do
expect(@connection.vnets.exists?('jm-vnet-test')).to eq(true)
expect(@connection.vnets.exists?('not-there')).to eq(false)
end
it 'should contain Created state' do
@connection.vnets.all.each do |item|
item.state.should eq('Created')
end
end
end
context 'create should' do
it 'create a vnet that does not already exist' do
params = {
azure_vnet_name: 'new-vn',
azure_ag_name: 'someag',
azure_address_space: '10.0.0.0/16',
}
@connection.vnets.create(params)
@postname.should eq('networking/media')
@postverb.should eq('put')
Nokogiri::XML(@postbody).should be_equivalent_to(
Nokogiri::XML readFile('set_network_new.xml')
)
end
it 'modify an existing vnet' do
params = {
azure_vnet_name: 'vnname',
azure_ag_name: 'new-agname',
azure_address_space: '192.168.0.0/20',
}
@connection.vnets.create(params)
@postname.should eq('networking/media')
@postverb.should eq('put')
Nokogiri::XML(@postbody).should be_equivalent_to(
Nokogiri::XML readFile('set_network_existing.xml')
)
end
end
end