Merge "Add Azure support for Nova"
This commit is contained in:
commit
4fe87700fd
|
@ -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
|
|
@ -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
|
|
@ -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'
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
Loading…
Reference in New Issue