Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow nova driver to be boot from volume #29765

Merged
merged 3 commits into from
Dec 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 94 additions & 5 deletions salt/cloud/clouds/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,78 @@
- net-id: 00000000-0000-0000-0000-000000000000
- net-id: 11111111-1111-1111-1111-111111111111

This is an example profile.

.. code-block:: yaml

debian8-2-iad-cloudqe4:
provider: cloudqe4-iad
size: performance1-2
image: Debian 8 (Jessie) (PVHVM)
script_args: -UP -p python-zmq git 2015.8

and one using cinder volumes already attached

.. code-block:: yaml

# create the block storage device
centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
block_device:
- source: image
id: <image_id>
dest: volume
size: 100
shutdown: <preserve/remove>
bootindex: 0

# with the volume already created
centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
block_volume: <volume id>

# create the volume from a snapshot
centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
snapshot: <cinder snapshot id>

# create the create an extra ephemeral disk
centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
ephemeral:
- size: 100
format: <swap/ext4>

# create the create an extra ephemeral disk
centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
swap: <size>

Block Device can also be used for having more than one block storage device attached

.. code-block:: yaml

centos7-2-iad-rackspace:
provider: rackspace-iad
size: general1-2
block_device:
- source: image
id: <image_id>
dest: volume
size: 100
shutdown: <preserve/remove>
bootindex: 0
- source: blank
dest: volume
device: xvdc
size: 100
shutdown: <preserve/remove>

Note: You must include the default net-ids when setting networks or the server
will be created without the rest of the interfaces

Expand Down Expand Up @@ -228,11 +300,14 @@ def get_image(conn, vm_):
'''
Return the image object to use
'''
image_list = conn.image_list()

vm_image = config.get_cloud_config_value('image', vm_, __opts__).encode(
vm_image = config.get_cloud_config_value('image', vm_, __opts__, default='').encode(
'ascii', 'salt-cloud-force-ascii'
)
if not vm_image:
log.debug('No image set, must be boot from volume')
return None

image_list = conn.image_list()

for img in image_list:
if vm_image in (image_list[img]['id'], img):
Expand All @@ -250,6 +325,17 @@ def get_image(conn, vm_):
)


def get_block_mapping_opts(vm_):
ret = {}
ret['block_device_mapping'] = config.get_cloud_config_value('block_device_mapping', vm_, __opts__, default={})
ret['block_device'] = config.get_cloud_config_value('block_device', vm_, __opts__, default=[])
ret['ephemeral'] = config.get_cloud_config_value('ephemeral', vm_, __opts__, default=[])
ret['swap'] = config.get_cloud_config_value('swap', vm_, __opts__, default=None)
ret['snapshot'] = config.get_cloud_config_value('snapshot', vm_, __opts__, default=None)
ret['boot_volume'] = config.get_cloud_config_value('boot_volume', vm_, __opts__, default=None)
return ret


def show_instance(name, call=None):
'''
Show the details from the provider concerning an instance
Expand Down Expand Up @@ -525,12 +611,14 @@ def request_instance(vm_=None, call=None):
'config_drive', vm_, __opts__, search_global=False
)

kwargs.update(get_block_mapping_opts(vm_))

salt.utils.cloud.fire_event(
'event',
'requesting instance',
'salt/cloud/{0}/requesting'.format(vm_['name']),
{'kwargs': {'name': kwargs['name'],
'image': kwargs['image_id'],
'image': kwargs.get('image_id', 'Boot From Volume'),
'size': kwargs['flavor_id']}},
transport=__opts__['transport']
)
Expand Down Expand Up @@ -661,6 +749,7 @@ def __query_node_data(vm_, data):
for network in node['addresses'].get(networkname, []):
if network['version'] is 4:
node['extra']['access_ip'] = network['addr']
access_ip = network['addr']
break
vm_['cloudnetwork'] = True

Expand Down Expand Up @@ -717,7 +806,7 @@ def __query_node_data(vm_, data):
result.append(private_ip)

if rackconnect(vm_) is True and (ssh_interface(vm_) != 'private_ips' or rackconnectv3):
data.public_ips = access_ip
data.public_ips = [access_ip, ]
return data

# populate return data with private_ips
Expand Down
12 changes: 8 additions & 4 deletions salt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2575,23 +2575,27 @@ def is_profile_configured(opts, provider, profile_name):
alias, driver = provider.split(':')

# Most drivers need an image to be specified, but some do not.
non_image_drivers = ['vmware']
non_image_drivers = ['vmware', 'nova']

# Most drivers need a size, but some do not.
non_size_drivers = ['opennebula', 'parallels', 'proxmox', 'scaleway',
'softlayer', 'softlayer_hw', 'vmware', 'vsphere']

provider_key = opts['providers'][alias][driver]
profile_key = opts['providers'][alias][driver]['profiles'][profile_name]

if driver not in non_image_drivers:
required_keys.append('image')
elif driver == 'vmware':
required_keys.append('clonefrom')
elif driver == 'nova':
nova_image_keys = ['image', 'block_device_mapping', 'block_device']
if not any([key in provider_key for key in nova_image_keys]) and not any([key in profile_key for key in nova_image_keys]):
required_keys.extend(nova_image_keys)

if driver not in non_size_drivers:
required_keys.append('size')

provider_key = opts['providers'][alias][driver]
profile_key = opts['providers'][alias][driver]['profiles'][profile_name]

# Check if image and/or size are supplied in the provider config. If either
# one is present, remove it from the required_keys list.
for item in required_keys:
Expand Down
95 changes: 85 additions & 10 deletions salt/utils/openstack/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ class OpenStackComputeShell(object):
# Version added to novaclient.client.Client function
NOVACLIENT_MINVER = '2.6.1'

# dict for block_device_mapping_v2
CLIENT_BDM2_KEYS = {
'id': 'uuid',
'source': 'source_type',
'dest': 'destination_type',
'bus': 'disk_bus',
'device': 'device_name',
'size': 'volume_size',
'format': 'guest_format',
'bootindex': 'boot_index',
'type': 'device_type',
'shutdown': 'delete_on_termination',
}


def check_nova():
if HAS_NOVA:
Expand All @@ -56,14 +70,68 @@ def __init__(self, **entries):
self.__dict__.update(entries)


def _parse_block_device_mapping_v2(block_device=None, boot_volume=None, snapshot=None, ephemeral=None, swap=None):
bdm = []
if block_device is None:
block_device = []
if ephemeral is None:
ephemeral = []

if boot_volume is not None:
bdm_dict = {'uuid': boot_volume, 'source_type': 'volume',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)

if snapshot is not None:
bdm_dict = {'uuid': snapshot, 'source_type': 'snapshot',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)

for device_spec in block_device:
bdm_dict = {}

for key, value in six.iteritems(device_spec):
bdm_dict[CLIENT_BDM2_KEYS[key]] = value

# Convert the delete_on_termination to a boolean or set it to true by
# default for local block devices when not specified.
if 'delete_on_termination' in bdm_dict:
action = bdm_dict['delete_on_termination']
bdm_dict['delete_on_termination'] = (action == 'remove')
elif bdm_dict.get('destination_type') == 'local':
bdm_dict['delete_on_termination'] = True

bdm.append(bdm_dict)

for ephemeral_spec in ephemeral:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True}
if 'size' in ephemeral_spec:
bdm_dict['volume_size'] = ephemeral_spec['size']
if 'format' in ephemeral_spec:
bdm_dict['guest_format'] = ephemeral_spec['format']

bdm.append(bdm_dict)

if swap is not None:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True,
'guest_format': 'swap', 'volume_size': swap}
bdm.append(bdm_dict)

return bdm


class NovaServer(object):
def __init__(self, name, server, password=None):
'''
Make output look like libcloud output for consistency
'''
self.name = name
self.id = server['id']
self.image = server['image']['id']
self.image = server.get('image', {}).get('id', 'Boot From Volume')
self.size = server['flavor']['id']
self.state = server['state']
self._uuid = None
Expand Down Expand Up @@ -254,12 +322,19 @@ def boot(self, name, flavor_id=0, image_id=0, timeout=300, **kwargs):
Boot a cloud server.
'''
nt_ks = self.compute_conn
for key in ('name', 'flavor', 'image'):
if key in kwargs:
del kwargs[key]
response = nt_ks.servers.create(
name=name, flavor=flavor_id, image=image_id, **kwargs
kwargs['name'] = name
kwargs['flavor'] = flavor_id
kwargs['image'] = image_id or None
ephemeral = kwargs.pop('ephemeral', [])
block_device = kwargs.pop('block_device', [])
boot_volume = kwargs.pop('boot_volume', None)
snapshot = kwargs.pop('snapshot', None)
swap = kwargs.pop('swap', None)
kwargs['block_device_mapping_v2'] = _parse_block_device_mapping_v2(
block_device=block_device, boot_volume=boot_volume, snapshot=snapshot,
ephemeral=ephemeral, swap=swap
)
response = nt_ks.servers.create(**kwargs)
self.uuid = response.id
self.password = response.adminPass

Expand Down Expand Up @@ -694,8 +769,8 @@ def server_list(self):
'accessIPv6': item.accessIPv6,
'flavor': {'id': item.flavor['id'],
'links': item.flavor['links']},
'image': {'id': item.image['id'],
'links': item.image['links']},
'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
'links': item.image['links'] if item.image else ''},
}
except TypeError:
pass
Expand All @@ -720,8 +795,8 @@ def server_list_detailed(self):
'links': item.flavor['links']},
'hostId': item.hostId,
'id': item.id,
'image': {'id': item.image['id'],
'links': item.image['links']},
'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
'links': item.image['links'] if item.image else ''},
'key_name': item.key_name,
'links': item.links,
'metadata': item.metadata,
Expand Down