Merge "Add Azure support for Nova"

This commit is contained in:
Jenkins 2017-09-08 23:25:50 +00:00 committed by Gerrit Code Review
commit 4fe87700fd
6 changed files with 1094 additions and 0 deletions

View File

@ -0,0 +1,16 @@
"""
Copyright (c) 2017 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
from nova.virt.azure import driver
AzureDriver = driver.AzureDriver

50
nova/virt/azure/config.py Normal file
View File

@ -0,0 +1,50 @@
"""
Copyright 2017 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
import nova.conf
from oslo_config import cfg
azure_group = cfg.OptGroup(
name='azure', title='Options to connect to Azure cloud')
azure_opts = [
cfg.StrOpt('tenant_id', help='Tenant id of Azure account'),
cfg.StrOpt('client_id', help='Azure client id'),
cfg.StrOpt('client_secret', help='Azure Client secret', secret=True),
cfg.StrOpt('subscription_id', help='Azure subscription id'),
cfg.StrOpt('region', help='Azure region'),
cfg.StrOpt('resource_group', help="Azure resource group"),
cfg.StrOpt(
'vm_admin_username',
default='azureuser',
help=('Specifies the name of the administrator',
'account in virtual machine')),
cfg.IntOpt('vnc_port', default=5900, help='VNC starting port'),
# 500 VCPUs
cfg.IntOpt(
'max_vcpus', default=500, help='Max number of vCPUs that can be used'),
# 1000 GB RAM
cfg.IntOpt(
'max_memory_mb',
default=1024000,
help='Max memory MB that can be used'),
# 1 TB Storage
cfg.IntOpt(
'max_disk_gb', default=1024, help='Max storage in GB that can be used')
]
CONF = nova.conf.CONF
CONF.register_group(azure_group)
CONF.register_opts(azure_opts, group=azure_group)
nova_conf = CONF
azure_conf = CONF.azure

View File

@ -0,0 +1,35 @@
"""
Copyright (c) 2017 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
from nova.compute import power_state
OMNI_STATE_MAP = {
"PowerState/starting": power_state.NOSTATE,
"PowerState/running": power_state.RUNNING,
"PowerState/stopping": power_state.NOSTATE,
"PowerState/deallocating": power_state.NOSTATE,
"PowerState/deallocated": power_state.SHUTDOWN,
"PowerState/stopped": power_state.SHUTDOWN
}
PROVISION_STATES = {
'ProvisioningState/succeeded': '',
'ProvisioningState/failed': '',
'ProvisioningState/updating': '',
'ProvisioningState/creating': '',
'ProvisioningState/deleting': ''
}
OMNI_ID = 'azure_id'
OMNI_NAME = 'azure_name'

View File

@ -0,0 +1,112 @@
"""
Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import os
import sys
import utils as azure_utils
from keystoneauth1 import loading
from keystoneauth1 import session
from novaclient import client as nova_client
def abort(message):
sys.exit(message)
def get_env_param(env_name):
if env_name in os.environ:
return os.environ[env_name]
abort("%s environment variable not set." % env_name)
def get_keystone_session(vendor_data):
username = vendor_data['username']
password = vendor_data['password']
project_name = vendor_data['tenant_name']
auth_url = vendor_data['auth_url']
loader = loading.get_plugin_loader('password')
auth = loader.load_from_options(auth_url=auth_url,
project_name=project_name,
username=username, password=password)
sess = session.Session(auth=auth)
return sess
def get_nova_client(vendor_data):
NOVA_VERSION = '2'
client = nova_client.Client(NOVA_VERSION,
session=get_keystone_session(vendor_data))
return client
class NovaOperator(object):
def __init__(self):
auth_url = get_env_param('OS_AUTH_URL')
project_name = os.environ.get('OS_PROJECT_NAME')
tenant_name = os.environ.get('OS_TENANT_NAME')
username = get_env_param('OS_USERNAME')
password = get_env_param('OS_PASSWORD')
if not project_name:
if not tenant_name:
raise Exception("Either OS_PROJECT_NAME or OS_TENANT_NAME is "
"required.")
project_name = tenant_name
self.vendor_data = {
'username': username,
'password': password,
'auth_url': auth_url,
'tenant_name': project_name
}
self.nova_client = get_nova_client(self.vendor_data)
def register_flavor(self, name, memory_mb=0, vcpus=0):
self.nova_client.flavors.create(name, memory_mb, vcpus, 0)
print("Registered flavor %s" % name)
class FlavorProvider(object):
def __init__(self):
self.nova_operator = NovaOperator()
def get_flavor_objs(self):
raise NotImplementedError()
def register_flavors(self):
for flavor_info in self.get_flavor_objs():
self.nova_operator.register_flavor(flavor_info.name,
flavor_info.memory_in_mb,
flavor_info.number_of_cores)
class AzureFlavors(FlavorProvider):
def __init__(self):
super(AzureFlavors, self).__init__()
tenant_id = get_env_param('AZURE_TENANT_ID')
client_id = get_env_param('AZURE_CLIENT_ID')
client_secret = get_env_param('AZURE_CLIENT_SECRET')
subscription_id = get_env_param('AZURE_SUBSCRIPTION_ID')
self.region = get_env_param('AZURE_REGION')
self.compute_client = azure_utils.get_compute_client(
tenant_id, client_id, client_secret, subscription_id)
def get_flavor_objs(self):
vm_sizes = self.compute_client.virtual_machine_sizes
for i in vm_sizes.list(location=self.region):
yield i
if __name__ == '__main__':
az_flavors = AzureFlavors()
az_flavors.register_flavors()

748
nova/virt/azure/driver.py Normal file
View File

@ -0,0 +1,748 @@
"""
Copyright (c) 2017 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
import hashlib
import uuid
from oslo_log import log as logging
from nova import exception
from nova.virt.azure.config import azure_conf as drv_conf
from nova.virt.azure.config import nova_conf
from nova.virt.azure import constants
from nova.virt.azure import utils
from nova.virt import driver
from nova.virt import hardware
LOG = logging.getLogger(__name__)
OMNI_NAME = constants.OMNI_NAME
class AzureDriver(driver.ComputeDriver):
capabilities = {
"has_imagecache": True,
"supports_recreate": True,
}
def __init__(self, virtapi, read_only=False):
super(AzureDriver, self).__init__(virtapi)
self.name = 'Azure'
self.version = '1.0'
self.host_status_base = {
'vcpus': drv_conf.max_vcpus,
'memory_mb': drv_conf.max_memory_mb,
'local_gb': drv_conf.max_disk_gb,
'vcpus_used': 0,
'memory_mb_used': 0,
'local_gb_used': 0,
'hypervisor_type': self.name,
'hypervisor_version': self.version,
'hypervisor_hostname': nova_conf.host,
'cpu_info': {},
'disk_available_least': drv_conf.max_disk_gb,
}
self._mounts = {}
self._interfaces = {}
self._uuid_to_omni_instance = {}
self._drv_nodes = None
self.flavor_info = {}
def init_host(self, host):
"""Initialize anything that is necessary for the driver to function"""
if self._drv_nodes is None:
self.set_nodes([nova_conf.host])
args = (drv_conf.tenant_id, drv_conf.client_id, drv_conf.client_secret,
drv_conf.subscription_id)
self.compute_client = utils.get_compute_client(*args)
self.resource_client = utils.get_resource_client(*args)
self.network_client = utils.get_network_client(*args)
self.flavor_info.update(
utils.get_vm_sizes(self.compute_client, drv_conf.region))
LOG.info("%s driver init with %s project, %s region" %
(self.name, drv_conf.tenant_id, drv_conf.region))
def set_nodes(self, nodes):
"""Sets Driver's node list.
It has effect on the following methods:
get_available_nodes()
get_available_resource
get_host_stats()
"""
self._drv_nodes = nodes
def _get_uuid_from_omni_id(self, omni_id):
m = hashlib.md5()
m.update(omni_id)
return str(uuid.UUID(bytes=m.digest(), version=4))
def _get_omni_name_from_instance(self, instance):
if OMNI_NAME in instance.metadata and instance.metadata[OMNI_NAME]:
return instance.metadata[OMNI_NAME]
elif instance.uuid in self._uuid_to_omni_instance:
return self._uuid_to_omni_instance[instance.uuid].name
# if none of the conditions are met we cannot map OpenStack UUID to
# Azure
raise exception.InstanceNotFound(
'Instance %s not found' % instance.uuid)
def list_instances(self):
"""
Return the names of all the instances known to the virtualization
layer, as a list.
"""
instances = utils.list_instances(self.compute_client,
drv_conf.resource_group)
self._uuid_to_omni_instance.clear()
instance_names = []
for instance in instances:
openstack_id = None
if instance.tags and 'openstack_id' in instance.tags:
openstack_id = instance.tags['openstack_id']
if openstack_id is None:
openstack_id = self._get_uuid_from_omni_id(instance.name)
self._uuid_to_omni_instance[openstack_id] = instance
instance_names.append(instance.name)
return instance_names
def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks."""
raise NotImplementedError()
def unplug_vifs(self, instance, network_info):
"""Unplug VIFs from networks."""
raise NotImplementedError()
def _get_hardware_profile(self, flavor):
return {'vm_size': flavor.name}
def _get_network_profile(self, network_info):
if not network_info:
raise exception.BuildAbortException('Network info missing')
network_profile = {'network_interfaces': []}
for net_info in network_info:
nic_name = 'nic-' + net_info['id']
nic = utils.get_nic(self.network_client, drv_conf.resource_group,
nic_name)
network_profile['network_interfaces'].append({'id': nic.id})
return network_profile
def _get_storage_profile(self, instance):
# Pick up os disk name same as instance name for instance cleanup
disk_name = self._azure_instance_name(instance)
img_link = instance.system_metadata['image_azure_link']
return {
'os_disk': {
'name': disk_name,
'caching': 'None',
'create_option': 'fromImage',
},
'image_reference': {
'id': img_link,
}
}
def _get_os_profile(self, instance, admin_password):
user = drv_conf.vm_admin_username
os_profile = {
'computer_name': instance.hostname,
'admin_username': drv_conf.vm_admin_username,
}
try:
img_link = instance.system_metadata['image_azure_link']
img_name = img_link.strip('/').split('/')[-1]
image = utils.get_image(self.compute_client,
drv_conf.resource_group, img_name)
os_type = image.storage_profile.os_disk.os_type.name
except Exception as e:
LOG.exception(
"Error occurred while finding os type for image: %s" % e)
os_type = None
key_data = instance.key_data
if key_data is None or os_type != 'linux':
os_profile['admin_password'] = admin_password
else:
ssh_key = {
'path': '/home/{0}/.ssh/authorized_keys'.format(user),
'key_data': key_data
}
os_profile['linux_configuration'] = {
'ssh': {
'public_keys': [
ssh_key,
]
}
}
return os_profile
def _prepare_vm_params(self, instance, network_info, admin_password):
os_profile = self._get_os_profile(instance, admin_password)
hardware_profile = self._get_hardware_profile(instance.flavor)
storage_profile = self._get_storage_profile(instance)
network_profile = self._get_network_profile(network_info)
vm_profile = {
'location': drv_conf.region,
'os_profile': os_profile,
'hardware_profile': hardware_profile,
'storage_profile': storage_profile,
'network_profile': network_profile
}
return vm_profile
def _azure_instance_name(self, instance):
return 'inst-' + instance.uuid
def spawn(self,
context,
instance,
image_meta,
injected_files,
admin_password,
network_info=None,
block_device_info=None):
"""Create a new instance/VM/domain on the virtualization platform.
Once this successfully completes, the instance should be
running (power_state.RUNNING). If this fails, any partial instance
should be completely cleaned up, and the virtualization platform should
be in the state that it was before this call began.
:param context: security context <Not Yet Implemented>
:param instance: nova.objects.instance.Instance
This function should use the data there to guide
the creation of the new instance.
:param image_meta: image object returned by nova.image.glance that
defines the image from which to boot this instance
:param injected_files: User files to inject into instance.
:param admin_password: set in instance. <Not Yet Implemented>
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
:param block_device_info: Information about block devices to be
attached to the instance.
"""
vm_params = self._prepare_vm_params(instance, network_info,
admin_password)
name = self._azure_instance_name(instance)
LOG.info("Spawning vm %s with params %s" % (name, vm_params))
utils.create_or_update_instance(
self.compute_client, drv_conf.resource_group, name, vm_params)
tags = {
'location': drv_conf.region,
'tags': {
'openstack_id': instance.uuid,
'openstack_project_id': context.project_id,
'openstack_user_id': context.user_id
}
}
utils.create_or_update_instance(self.compute_client,
drv_conf.resource_group, name, tags)
az_instance = utils.get_instance(self.compute_client,
drv_conf.resource_group, name)
self._uuid_to_omni_instance[instance.uuid] = az_instance
instance.metadata.update({
OMNI_NAME: name,
constants.OMNI_ID: az_instance.id
})
def snapshot(self, context, instance, image_id, update_task_state):
"""Snapshot an image of the specified instance
:param context: security context
:param instance: nova.objects.instance.Instance
:param image_id: Reference to a pre-created image holding the snapshot.
"""
raise NotImplementedError()
def reboot(self,
context,
instance,
network_info,
reboot_type,
block_device_info=None,
bad_volumes_callback=None):
"""Reboot the specified instance. After this is called successfully,
the instance's state goes back to power_state.RUNNING. The
virtualization platform should ensure that the reboot action has
completed successfully even in cases in which the underlying domain/vm
completed successfully even in cases in which the underlying domain/vm
is paused or halted/stopped.
:param instance: nova.objects.instance.Instance
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
:param reboot_type: Either a HARD or SOFT reboot
:param block_device_info: Info pertaining to attached volumes
:param bad_volumes_callback: Function to handle any bad volumes
encountered
"""
azure_name = self._get_omni_name_from_instance(instance)
utils.restart_instance(self.compute_client, drv_conf.resource_group,
azure_name)
@staticmethod
def get_host_ip_addr():
"""Retrieves the IP address of the host"""
return nova_conf.my_ip
def set_admin_password(self, instance, new_pass):
"""Set root password on specified instance"""
raise NotImplementedError()
def inject_file(self, instance, b64_path, b64_contents):
raise NotImplementedError()
def resume_state_on_host_boot(self,
context,
instance,
network_info,
block_device_info=None):
raise NotImplementedError()
def rescue(self, context, instance, network_info, image_meta,
rescue_password):
raise NotImplementedError()
def unrescue(self, instance, network_info):
raise NotImplementedError()
def poll_rebooting_instances(self, timeout, instances):
raise NotImplementedError()
def migrate_disk_and_power_off(self,
context,
instance,
dest,
instance_type,
network_info,
block_device_info=None):
raise NotImplementedError()
def finish_revert_migration(self,
context,
instance,
network_info,
block_device_info=None,
power_on=True):
raise NotImplementedError()
def post_live_migration_at_destination(self,
context,
instance,
network_info,
block_migration=False,
block_device_info=None):
raise NotImplementedError()
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance.
:param instance: nova.objects.instance.Instance
:param timeout: time to wait for GuestOS to shutdown
:param retry_interval: How often to signal guest while
waiting for it to shutdown
"""
azure_name = self._get_omni_name_from_instance(instance)
utils.stop_instance(self.compute_client, drv_conf.resource_group,
azure_name)
def power_on(self, context, instance, network_info, block_device_info):
"""Power on the specified instance."""
azure_name = self._get_omni_name_from_instance(instance)
utils.start_instance(self.compute_client, drv_conf.resource_group,
azure_name)
def soft_delete(self, instance):
"""Deleting the specified instance"""
self.destroy(instance)
def restore(self, instance):
raise NotImplementedError()
def pause(self, instance):
"""
Azure doesn't support pause and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
self.power_off(instance)
def unpause(self, instance):
"""
Since Azure doesn't support pause and cannot save system state, we
had implemented the closest functionality which is to poweroff the
instance. and powering on such an instance in this method.
:param instance: nova.objects.instance.Instance
"""
self.power_on(
context=None,
instance=instance,
network_info=None,
block_device_info=None)
def suspend(self, context, instance):
"""
Azure doesn't support suspend and cannot save system state and hence
Azure doesn't support suspend and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Suspending instance %s" % instance.uuid)
self.power_off(instance)
def resume(self, context, instance, network_info, block_device_info=None):
"""
Since Azure doesn't support resume and we cannot save system state,
Since Azure doesn't support resume and we cannot save system state,
we've implemented the closest functionality which is to power on the
instance.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Resuming instance %s" % instance.uuid)
self.power_on(context, instance, network_info, block_device_info)
def destroy(self,
context,
instance,
network_info,
block_device_info=None,
destroy_disks=True,
migrate_data=None):
"""Destroy the specified instance from the Hypervisor.
If the instance is not found (for example if networking failed), this
function should still succeed. It's probably a good idea to log a
warning in that case.
:param context: security context
:param instance: Instance object as returned by DB layer.
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
:param block_device_info: Information about block devices that should
be detached from the instance.
:param destroy_disks: Indicates if disks should be destroyed
:param migrate_data: implementation specific params
"""
LOG.info("Destroying instance %s" % instance.uuid)
try:
azure_name = self._get_omni_name_from_instance(instance)
except exception.InstanceNotFound:
LOG.error(
"Unable to find Azure mapping for instance %s" % instance.uuid)
return
try:
utils.delete_instance(self.compute_client, drv_conf.resource_group,
azure_name)
except utils.CloudError:
LOG.error(
"Instance %s not found in Azure, removing from openstack." %
(instance.uuid, ))
# Delete disk handles exception if disk not found
utils.delete_disk(self.compute_client, drv_conf.resource_group,
azure_name)
LOG.info("Destroy complete %s" % instance.uuid)
def attach_volume(self,
context,
connection_info,
instance,
mountpoint,
disk_bus=None,
device_type=None,
encryption=None):
"""Attach the disk to the instance at mountpoint using info."""
raise NotImplementedError()
def detach_volume(self,
connection_info,
instance,
mountpoint,
encryption=None):
"""Detach the disk attached to the instance."""
raise NotImplementedError()
def swap_volume(self, old_connection_info, new_connection_info, instance,
mountpoint, resize_to):
"""Replace the disk attached to the instance."""
raise NotImplementedError()
def attach_interface(self, instance, image_meta, vif):
raise NotImplementedError()
def detach_interface(self, instance, vif):
raise NotImplementedError()
def _get_power_state(self, azure_instance):
statuses = azure_instance.instance_view.statuses
state = constants.power_state.NOSTATE
for i in statuses:
if hasattr(i, 'code') and 'PowerState' in i.code:
state = constants.OMNI_STATE_MAP[i.code]
return state
def get_info(self, instance):
"""Get the current status of an instance, by name (not ID!)
:param instance: nova.objects.instance.Instance object
Returns a dict containing:
:state: the running state, one of the power_state codes
:max_mem: (int) the maximum memory in KBytes allowed
:mem: (int) the memory in KBytes used by the domain
:num_cpu: (int) the number of virtual CPUs for the domain
:cpu_time: (int) the CPU time used in nanoseconds
"""
azure_name = self._get_omni_name_from_instance(instance)
azure_instance = utils.get_instance(
self.compute_client, drv_conf.resource_group, azure_name)
state = self._get_power_state(azure_instance)
flavor = self.flavor_info[instance.flavor.name]
memory = flavor.memory_in_mb * 1024
cpus = flavor.number_of_cores
return hardware.InstanceInfo(
state=state,
max_mem_kb=memory,
mem_kb=memory,
num_cpu=cpus,
cpu_time_ns=0,
id=instance.id)
def allow_key(self, key):
DIAGNOSTIC_KEYS_TO_FILTER = ['group', 'block_device_mapping']
if key in DIAGNOSTIC_KEYS_TO_FILTER:
return False
return True
def get_diagnostics(self, instance):
"""Return data about VM diagnostics."""
# Fake diagnostics
return {
'cpu0_time': 17300000000,
'memory': 524288,
'vda_errors': -1,
'vda_read': 262144,
'vda_read_req': 112,
'vda_write': 5778432,
'vda_write_req': 488,
'vnet1_rx': 2070139,
'vnet1_rx_drop': 0,
'vnet1_rx_errors': 0,
'vnet1_rx_packets': 26701,
'vnet1_tx': 140208,
'vnet1_tx_drop': 0,
'vnet1_tx_errors': 0,
'vnet1_tx_packets': 662,
}
def get_all_bw_counters(self, instances):
"""Return bandwidth usage counters for each interface on each
running VM.
"""
bw = []
return bw
def get_all_volume_usage(self, context, compute_host_bdms):
"""Return usage info for volumes attached to vms on a given host."""
volusage = []
return volusage
def block_stats(self, instance_name, disk_id):
return [0L, 0L, 0L, 0L, None]
def interface_stats(self, instance_name, iface_id):
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
def get_vnc_console(self, context, instance):
raise NotImplementedError()
def get_spice_console(self, instance):
"""Simple Protocol for Independent Computing Environments"""
raise NotImplementedError()
def get_console_pool_info(self, console_type):
raise NotImplementedError()
def refresh_provider_fw_rules(self):
raise NotImplementedError()
def get_available_resource(self, nodename):
"""Retrieve resource information. Updates compute manager resource info
on ComputeNode table. This method is called when nova-compute launches
and as part of a periodic task that records results in the DB. Without
real hypervisor, pretend we have lots of disk and ram.
:param nodename: node which the caller want to get resources from
a driver that manages only one node can safely ignore this
:returns: Dictionary describing resources
"""
if nodename not in self._drv_nodes:
return {}
supported_tuple = ('IA64', 'kvm', 'hvm')
return {
'vcpus': drv_conf.max_vcpus,
'memory_mb': drv_conf.max_memory_mb,
'local_gb': drv_conf.max_disk_gb,
'vcpus_used': 0,
'memory_mb_used': 0,
'local_gb_used': 0,
'hypervisor_type': self.name,
'hypervisor_version': '1',
'hypervisor_hostname': nodename,
'disk_available_least': 0,
'cpu_info': '?',
'numa_topology': None,
'supported_instances': [supported_tuple]
}
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
return
def get_instance_disk_info(self, instance_name):
return
def live_migration(self,
context,
instance_ref,
dest,
post_method,
recover_method,
block_migration=False,
migrate_data=None):
post_method(context, instance_ref, dest, block_migration, migrate_data)
return
def check_can_live_migrate_destination_cleanup(self, ctxt,
dest_check_data):
return
def check_can_live_migrate_destination(self,
ctxt,
instance_ref,
src_compute_info,
dst_compute_info,
block_migration=False,
disk_over_commit=False):
return {}
def check_can_live_migrate_source(self, ctxt, instance_ref,
dest_check_data):
return
def finish_migration(self,
context,
migration,
instance,
disk_info,
network_info,
image_meta,
resize_instance,
block_device_info=None,
power_on=True):
"""Completes a resize
:param migration: the migrate/resize information
:param instance: nova.objects.instance.Instance being migrated/resized
:param power_on: is True the instance should be powered on
"""
raise NotImplementedError()
def confirm_migration(self, migration, instance, network_info):
"""Confirms a resize, destroying the source VM.
:param instance: nova.objects.instance.Instance
"""
raise NotImplementedError()
def pre_live_migration(self,
context,
instance_ref,
block_device_info,
network_info,
disk,
migrate_data=None):
return
def unfilter_instance(self, instance_ref, network_info):
return
def get_host_stats(self, refresh=False):
"""Return Azure Host Status of name, ram, disk, network."""
stats = []
for nodename in self._drv_nodes:
host_status = self.host_status_base.copy()
host_status['hypervisor_hostname'] = nodename
host_status['host_hostname'] = nodename
host_status['host_name_label'] = nodename
host_status['hypervisor_type'] = self.name
host_status['vcpus'] = drv_conf.max_vcpus
host_status['memory_mb'] = drv_conf.max_memory_mb
host_status['local_gb'] = drv_conf.max_disk_gb
stats.append(host_status)
if len(stats) == 0:
raise exception.NovaException("Azure Driver has no node")
elif len(stats) == 1:
return stats[0]
else:
return stats
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
return action
def host_maintenance_mode(self, host, mode):
"""Start/Stop host maintenance window. On start, it triggers
guest VMs evacuation.
"""
if not mode:
return 'off_maintenance'
return 'on_maintenance'
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
if enabled:
return 'enabled'
return 'disabled'
def get_disk_available_least(self):
raise NotImplementedError()
def add_to_aggregate(self, context, aggregate, host, **kwargs):
raise NotImplementedError()
def remove_from_aggregate(self, context, aggregate, host, **kwargs):
raise NotImplementedError()
def get_volume_connector(self, instance):
return {
'ip': '127.0.0.1',
'initiator': self.name,
'host': '%shost' % self.name
}
def get_available_nodes(self, refresh=False):
return self._drv_nodes
def instance_on_disk(self, instance):
return False
def list_instance_uuids(self, node=None, template_uuids=None, force=False):
self.list_instances()
return self._uuid_to_omni_instance.keys()

133
nova/virt/azure/utils.py Normal file
View File

@ -0,0 +1,133 @@
"""
Copyright (c) 2017 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
from functools import partial
from azure.common.credentials import ServicePrincipalCredentials
from azure.mgmt.compute import ComputeManagementClient
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.resource import ResourceManagementClient
from msrestazure.azure_exceptions import CloudError
from oslo_log import log as logging
from nova import exception
LOG = logging.getLogger(__name__)
def get_credentials(tenant_id, client_id, client_secret):
credentials = ServicePrincipalCredentials(
client_id=client_id, secret=client_secret, tenant=tenant_id)
return credentials
def _get_client(tenant_id, client_id, client_secret, subscription_id,
cls=None):
"""Returns Azure compute resource object for interacting with Azure API
:param tenant_id: string, tenant_id from azure account
:param client_id: string, client_id (application id)
:param client_secret: string, secret key of application
:param subscription_id: string, unique identification id of account
:return: :class:`Resource <Resource>` object
"""
credentials = get_credentials(tenant_id, client_id, client_secret)
client = cls(credentials, subscription_id)
return client
get_compute_client = partial(_get_client, cls=ComputeManagementClient)
get_resource_client = partial(_get_client, cls=ResourceManagementClient)
get_network_client = partial(_get_client, cls=NetworkManagementClient)
def azure_handle_exception(fn):
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as e:
LOG.exception("Exception occurred in Azure operation: %s" %
(e.message))
return wrapper
def _perform_and_wait(operation, args=(), kwargs={}, timeout=300):
operation(*args, **kwargs).wait(timeout=timeout)
def get_vm_sizes(compute, region):
vmsize_dict = {}
for i in compute.virtual_machine_sizes.list(location=region):
vmsize_dict[i.name] = i
return vmsize_dict
def list_instances(compute, resource_group):
"""Returns list of Azure instance resources for specified resource_group
:param compute: Azure object using ComputeManagementClient
:param resource_group: string, name of Azure resource group
:return: list of Azure VMs in resource_group
:rtype: list
"""
return compute.virtual_machines.list(resource_group)
def get_instance(compute, resource_group, instance_name):
"""Get Azure instance information
:param compute: Azure object using ComputeManagementClient
:param resource_group: string, name of Azure resource group
:param instance_name: string, name of Azure instance
"""
return compute.virtual_machines.get(
resource_group, instance_name, expand='instanceView')
def get_nic(network, resource_group, name):
try:
return network.network_interfaces.get(resource_group, name)
except CloudError:
raise exception.PortNotFound(port_id=name)
def create_or_update_instance(compute, resource_group, name, body):
_perform_and_wait(compute.virtual_machines.create_or_update,
(resource_group, name, body))
def delete_instance(compute, resource_group, name):
_perform_and_wait(compute.virtual_machines.delete, (resource_group, name))
def restart_instance(compute, resource_group, name):
_perform_and_wait(compute.virtual_machines.restart, (resource_group, name))
def start_instance(compute, resource_group, name):
_perform_and_wait(compute.virtual_machines.start, (resource_group, name))
def stop_instance(compute, resource_group, name):
_perform_and_wait(compute.virtual_machines.power_off, (resource_group,
name))
def get_image(compute, resource_group, name):
return compute.images.get(resource_group, name)
@azure_handle_exception
def delete_disk(compute, resource_group, name):
return compute.disks.delete(resource_group, name)