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

[WIP] Solve some concurrency issues #621

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion docs/LagoInitFile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,15 @@ domains

nets
----
``<name>``: The name of the network.
``<name>``: The name of the network, should be an alphanumeric string.
Currently we do not enforce that it is only alphanumeric,
but we might do so in the future.

type(string)
Type of the network. May be `nat` or `bridge`.



.. _Templates: Templates.html
.. _`virt-customize`: http://libguestfs.org/virt-customize.1.html
.. _lago-ost-plugin: https://github.com/lago-project/lago-ost-plugin/blob/master/setup.cfg
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
# expected with no need to download them (some are not in pip even)
autodoc_mock_imports = [
'enum',
'future',
'guestfs',
'libvirt',
'lockfile',
Expand Down
66 changes: 58 additions & 8 deletions lago/prefix.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import uuid
import warnings
import pkg_resources
from hashlib import sha256
from os.path import join
from plugins.output import YAMLOutFormatPlugin

Expand Down Expand Up @@ -130,6 +131,53 @@ def metadata(self):
self._metadata = {}
return self._metadata

@property
@sdk_utils.expose
def uuid(self):
"""
The prefix UUID

Returns:
str: UUID4
"""
if not os.path.isfile(self.paths.uuid()):
raise utils.LagoException(
'No UUID found at {0}'.format(os.paths.uuid())
)
with open(self.paths.uuid(), 'r') as uuid_fd:
uuid = uuid_fd.read().strip()
return str(uuid)

@sdk_utils.expose
def prefixed_name(self, unprefixed_name, max_length=15):
"""
Returns an alpha-numeric identifier for the `unprefixed_name` specific
to this prefix, it is composed from:
(last 8 chars from the prefix uuid) concatenated with
(last min(max_length-8,72) chars of the SHA256 of
`unprefixed_name`)
Note that it is highly recommended to use the maximal length
possible(72), otherwise, hash collisions are more likely to happen.


Args:
unprefixed_name(str): Name to hash
max_length(int): Maximum identifier length to return. 0 means 15.
Other than 0 allowed values are in range [11, 15].
Returns:
str: prefix identifier for `unprefixed_name`
"""

if max_length < 11:
raise utils.LagoException('max length must be larger than 11')

hashed_name = sha256(unprefixed_name)

return '{uuid}{hashed}'.format(
uuid=self.uuid[:8],
hashed=hashed_name.hexdigest()[:min(max_length - 8, 72)]
)

def _save_metadata(self):
"""
Write this prefix metadata to disk
Expand Down Expand Up @@ -181,19 +229,21 @@ def _create_ssh_keys(self):
)

@log_task('Initialize prefix')
def initialize(self):
def initialize(self, skip_ssh=False):
"""
Initialize this prefix, this includes creating the destination path,
and creating the uuid for the prefix, for any other actions see
:func:`Prefix.virt_conf`

Will safely roll back if any of those steps fail

Args:
skip_ssh(bool): Skip generating SSH keys
Returns:
None

Raises:
RuntimeError: If it fails to create the prefix dir
:exc:`LagoInitException`: If it fails to create the prefix dir
"""
prefix = self.paths.prefix_path()
os.environ['LAGO_PREFIX_PATH'] = prefix
Expand All @@ -204,17 +254,17 @@ def initialize(self):
try:
os.mkdir(prefix)
except OSError as error:
raise RuntimeError(
raise LagoInitException(
'Could not create prefix at %s:\n%s' % (prefix, error)
)
rollback.prependDefer(shutil.rmtree, prefix)

with open(self.paths.uuid(), 'w') as f, \
LogTask('Generate prefix uuid'):
f.write(uuid.uuid1().hex)

with LogTask('Create ssh keys'):
self._create_ssh_keys()
LogTask('Generate prefix uuid4'):
f.write(uuid.uuid4().hex)
if not skip_ssh:
with LogTask('Create ssh keys'):
self._create_ssh_keys()

with LogTask('Tag prefix as initialized'):
with open(self.paths.prefix_lagofile(), 'w') as fd:
Expand Down
10 changes: 6 additions & 4 deletions lago/providers/libvirt/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, env, spec, compat):
self._env = env
libvirt_url = config.get('libvirt_url')
self.libvirt_con = libvirt_utils.get_libvirt_connection(
name=env.uuid + libvirt_url,
name=env.prefix.prefixed_name(libvirt_url),
libvirt_url=libvirt_url,
)
self._spec = spec
Expand Down Expand Up @@ -79,8 +79,10 @@ def resolve(self, name):
def mapping(self):
return self._spec['mapping']

def _libvirt_name(self):
return self._env.prefixed_name(self.name(), max_length=15)
def _libvirt_name(self, max_length=15):
return self._env.prefix.prefixed_name(
self.name(), max_length=max_length
)

def _libvirt_xml(self):
raise NotImplementedError(
Expand Down Expand Up @@ -184,7 +186,7 @@ def _libvirt_xml(self):

replacements = {
'@NAME@': self._libvirt_name(),
'@BR_NAME@': ('%s-nic' % self._libvirt_name())[:12],
'@BR_NAME@': 'b' + self._libvirt_name(11),
'@GW_ADDR@': self.gw(),
'@SUBNET@': subnet
}
Expand Down
9 changes: 4 additions & 5 deletions lago/providers/libvirt/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __init__(self, vm):
self._has_guestfs = 'lago.guestfs_tools' in sys.modules
libvirt_url = config.get('libvirt_url')
self.libvirt_con = libvirt_utils.get_libvirt_connection(
name=self.vm.virt_env.uuid + libvirt_url,
name=vm.virt_env.prefix.prefixed_name(libvirt_url),
libvirt_url=libvirt_url,
)
self._libvirt_ver = self.libvirt_con.getLibVersion()
Expand Down Expand Up @@ -468,7 +468,7 @@ def cpu_vendor(self):
return self._cpu.vendor

def _libvirt_name(self):
return self.vm.virt_env.prefixed_name(self.vm.name())
return self.vm.virt_env.prefix.prefixed_name(self.vm.name())

def _get_qemu_kvm_path(self):
qemu_kvm_path = self._caps.findtext(
Expand Down Expand Up @@ -590,9 +590,8 @@ def _libvirt_xml(self):
interface.append(
ET.Element(
'source',
network=self.vm.virt_env.prefixed_name(
dev_spec['net'], max_length=15
),
network=self.vm.virt_env.prefix.
prefixed_name(dev_spec['net']),
),
)
interface.append(
Expand Down
71 changes: 1 addition & 70 deletions lago/virt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@
#
from copy import deepcopy
import functools
import hashlib
import json
import logging
import os
import uuid

import yaml

from lago import log_utils, plugins, utils
Expand All @@ -37,34 +34,6 @@
log_task = functools.partial(log_utils.log_task, logger=LOGGER)


def _gen_ssh_command_id():
return uuid.uuid1().hex[:8]


def _guestfs_copy_path(g, guest_path, host_path):
if g.is_file(guest_path):
with open(host_path, 'w') as f:
f.write(g.read_file(guest_path))
elif g.is_dir(guest_path):
os.mkdir(host_path)
for path in g.ls(guest_path):
_guestfs_copy_path(
g,
os.path.join(
guest_path,
path,
),
os.path.join(host_path, os.path.basename(path)),
)


def _path_to_xml(basename):
return os.path.join(
os.path.dirname(__file__),
basename,
)


class VirtEnv(object):
'''Env properties:
* prefix
Expand All @@ -81,12 +50,9 @@ def __init__(self, prefix, vm_specs, net_specs):
)
self.prefix = prefix

with open(self.prefix.paths.uuid(), 'r') as uuid_fd:
self.uuid = uuid_fd.read().strip()

libvirt_url = config.get('libvirt_url')
self.libvirt_con = libvirt_utils.get_libvirt_connection(
name=self.uuid + libvirt_url,
name=self.prefix.prefixed_name(libvirt_url),
libvirt_url=libvirt_url,
)
self._nets = {}
Expand Down Expand Up @@ -118,41 +84,6 @@ def _create_vm(self, vm_spec):
vm_spec['vm-type'] = vm_type_name
return vm_type(self, vm_spec)

def prefixed_name(self, unprefixed_name, max_length=0):
"""
Returns a uuid pefixed identifier

Args:
unprefixed_name(str): Name to add a prefix to
max_length(int): maximum length of the resultant prefixed name,
will adapt the given name and the length of the uuid ot fit it

Returns:
str: prefixed identifier for the given unprefixed name
"""
if max_length == 0:
prefixed_name = '%s-%s' % (self.uuid[:8], unprefixed_name)
else:
if max_length < 6:
raise RuntimeError(
"Can't prefix with less than 6 chars (%s)" %
unprefixed_name
)
if max_length < 16:
_uuid = self.uuid[:4]
else:
_uuid = self.uuid[:8]

name_max_length = max_length - len(_uuid) - 1

if name_max_length < len(unprefixed_name):
hashed_name = hashlib.sha1(unprefixed_name).hexdigest()
unprefixed_name = hashed_name[:name_max_length]

prefixed_name = '%s-%s' % (_uuid, unprefixed_name)

return prefixed_name

def virt_path(self, *args):
return self.prefix.paths.virt(*args)

Expand Down
Loading