538 lines
18 KiB
Python
538 lines
18 KiB
Python
# Copyright 2013 - 2016 Mirantis, 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 express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import time
|
|
import warnings
|
|
|
|
from django.conf import settings
|
|
from django.db import IntegrityError
|
|
from django.db import models
|
|
import exec_helpers
|
|
import netaddr
|
|
import paramiko
|
|
|
|
from devops import error
|
|
from devops.helpers import decorators
|
|
from devops.helpers import network as network_helpers
|
|
from devops import logger
|
|
from devops.models import base
|
|
from devops.models import driver
|
|
from devops.models import group
|
|
from devops.models import network
|
|
from devops.models import node
|
|
|
|
|
|
class Environment(base.BaseModel):
|
|
class Meta(object):
|
|
db_table = 'devops_environment'
|
|
app_label = 'devops'
|
|
|
|
name = models.CharField(max_length=255, unique=True, null=False)
|
|
|
|
def __repr__(self):
|
|
return 'Environment(name={name!r})'.format(name=self.name)
|
|
|
|
@property
|
|
def admin_net(self):
|
|
msg = (
|
|
'Environment.admin_net is deprecated. '
|
|
'Replace by string "admin".'
|
|
)
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
return 'admin'
|
|
|
|
@property
|
|
def admin_net2(self):
|
|
msg = (
|
|
'Environment.admin_net2 is deprecated. '
|
|
'Replace by string "admin2".'
|
|
)
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
return 'admin2'
|
|
|
|
@property
|
|
def nat_interface(self):
|
|
msg = (
|
|
'Environment.nat_interface is deprecated.'
|
|
)
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
return ''
|
|
|
|
def get_allocated_networks(self):
|
|
allocated_networks = []
|
|
for grp in self.get_groups():
|
|
allocated_networks += grp.get_allocated_networks()
|
|
return allocated_networks
|
|
|
|
def get_address_pool(self, **kwargs):
|
|
try:
|
|
return self.addresspool_set.get(**kwargs)
|
|
except network.AddressPool.DoesNotExist:
|
|
raise error.DevopsObjNotFound(network.AddressPool, **kwargs)
|
|
|
|
def get_address_pools(self, **kwargs):
|
|
return self.addresspool_set.filter(**kwargs).order_by('id')
|
|
|
|
def get_group(self, **kwargs):
|
|
try:
|
|
return self.group_set.get(**kwargs)
|
|
except group.Group.DoesNotExist:
|
|
raise error.DevopsObjNotFound(group.Group, **kwargs)
|
|
|
|
def get_groups(self, **kwargs):
|
|
return self.group_set.filter(**kwargs).order_by('id')
|
|
|
|
def add_groups(self, groups):
|
|
for group_data in groups:
|
|
driver_data = group_data['driver']
|
|
if driver_data['name'] == 'devops.driver.libvirt.libvirt_driver':
|
|
warnings.warn(
|
|
"Driver 'devops.driver.libvirt.libvirt_driver' "
|
|
"has been renamed to 'devops.driver.libvirt', "
|
|
"please update the tests!",
|
|
DeprecationWarning)
|
|
logger.warning(
|
|
"Driver 'devops.driver.libvirt.libvirt_driver' "
|
|
"has been renamed to 'devops.driver.libvirt', "
|
|
"please update the tests!")
|
|
driver_data['name'] = 'devops.driver.libvirt'
|
|
self.add_group(
|
|
group_name=group_data['name'],
|
|
driver_name=driver_data['name'],
|
|
**driver_data.get('params', {})
|
|
)
|
|
|
|
def add_group(self, group_name, driver_name, **driver_params):
|
|
drv = driver.Driver.driver_create(
|
|
name=driver_name,
|
|
**driver_params
|
|
)
|
|
return group.Group.group_create(
|
|
name=group_name,
|
|
environment=self,
|
|
driver=drv,
|
|
)
|
|
|
|
def add_address_pools(self, address_pools):
|
|
for name, data in address_pools.items():
|
|
self.add_address_pool(
|
|
name=name,
|
|
net=data['net'],
|
|
**data.get('params', {})
|
|
)
|
|
|
|
def add_address_pool(self, name, net, **params):
|
|
|
|
networks, prefix = net.split(':')
|
|
ip_networks = [netaddr.IPNetwork(x) for x in networks.split(',')]
|
|
|
|
pool = network_helpers.IpNetworksPool(
|
|
networks=ip_networks,
|
|
prefix=int(prefix),
|
|
allocated_networks=self.get_allocated_networks())
|
|
|
|
return network.AddressPool.address_pool_create(
|
|
environment=self,
|
|
name=name,
|
|
pool=pool,
|
|
**params
|
|
)
|
|
|
|
@classmethod
|
|
def create(cls, name):
|
|
"""Create Environment instance with given name.
|
|
|
|
:rtype: devops.models.Environment
|
|
"""
|
|
try:
|
|
return cls.objects.create(name=name)
|
|
except IntegrityError:
|
|
raise error.DevopsError(
|
|
'Environment with name {!r} already exists. '
|
|
'Please, set another environment name.'
|
|
''.format(name))
|
|
|
|
@classmethod
|
|
def get(cls, *args, **kwargs):
|
|
try:
|
|
return cls.objects.get(*args, **kwargs)
|
|
except Environment.DoesNotExist:
|
|
raise error.DevopsObjNotFound(Environment, *args, **kwargs)
|
|
|
|
@classmethod
|
|
def list_all(cls):
|
|
return cls.objects.all()
|
|
|
|
# LEGACY
|
|
def has_snapshot(self, name):
|
|
if self.get_nodes():
|
|
return all(n.has_snapshot(name) for n in self.get_nodes())
|
|
|
|
return False
|
|
|
|
@decorators.proc_lock()
|
|
def define(self):
|
|
for grp in self.get_groups():
|
|
grp.define_networks()
|
|
for grp in self.get_groups():
|
|
grp.define_volumes()
|
|
for grp in self.get_groups():
|
|
grp.define_nodes()
|
|
|
|
def start(self, nodes=None):
|
|
for grp in self.get_groups():
|
|
grp.start_networks()
|
|
for grp in self.get_groups():
|
|
grp.start_nodes(nodes)
|
|
|
|
def destroy(self):
|
|
for grp in self.get_groups():
|
|
grp.destroy()
|
|
|
|
@decorators.proc_lock()
|
|
def erase(self):
|
|
for grp in self.get_groups():
|
|
grp.erase()
|
|
self.delete()
|
|
|
|
def suspend(self, **kwargs):
|
|
for nod in self.get_nodes():
|
|
nod.suspend()
|
|
|
|
def resume(self, **kwargs):
|
|
for nod in self.get_nodes():
|
|
nod.resume()
|
|
|
|
@decorators.proc_lock()
|
|
def snapshot(self, name=None, description=None, force=False, suspend=True):
|
|
"""Snapshot the environment
|
|
|
|
:param name: name of the snapshot. Current timestamp, if name is None
|
|
:param description: any string that will be placed to the 'description'
|
|
section in the snapshot XML
|
|
:param force: If True - overwrite the existing snapshot. Default: False
|
|
:param suspend: suspend environment before snapshot if True (default)
|
|
"""
|
|
if name is None:
|
|
name = str(int(time.time()))
|
|
if self.has_snapshot(name) and not force:
|
|
raise error.DevopsError(
|
|
'Snapshot with name "{0}" already exists.'.format(name))
|
|
if suspend:
|
|
for nod in self.get_nodes():
|
|
nod.suspend()
|
|
|
|
for nod in self.get_nodes():
|
|
nod.snapshot(name=name, description=description, force=force,
|
|
external=settings.SNAPSHOTS_EXTERNAL)
|
|
|
|
@decorators.proc_lock()
|
|
def revert(self, name=None, flag=True, resume=True):
|
|
"""Revert the environment from snapshot
|
|
|
|
:param name: name of the snapshot
|
|
:param flag: raise Exception if True (default) and snapshot not found
|
|
:param resume: resume environment after revert if True (default)
|
|
"""
|
|
if flag and not self.has_snapshot(name):
|
|
raise Exception("some nodes miss snapshot,"
|
|
" test should be interrupted")
|
|
for nod in self.get_nodes():
|
|
nod.revert(name)
|
|
|
|
for grp in self.get_groups():
|
|
for l2netdev in grp.get_l2_network_devices():
|
|
l2netdev.unblock()
|
|
|
|
if resume:
|
|
for nod in self.get_nodes():
|
|
nod.resume()
|
|
|
|
# NOTE: Does not work
|
|
# TO REWRITE FOR LIBVIRT DRIVER ONLY
|
|
@classmethod
|
|
def synchronize_all(cls):
|
|
driver = cls.get_driver()
|
|
nodes = {driver._get_name(e.name, n.name): n
|
|
for e in cls.list_all()
|
|
for n in e.get_nodes()}
|
|
domains = set(driver.node_list())
|
|
|
|
# FIXME (AWoodward) This willy nilly wacks domains when you run this
|
|
# on domains that are outside the scope of devops, if anything this
|
|
# should cause domains to be imported into db instead of undefined.
|
|
# It also leaves network and volumes around too
|
|
# Disabled until a safer implementation arrives
|
|
|
|
# Undefine domains without devops nodes
|
|
#
|
|
# domains_to_undefine = domains - set(nodes.keys())
|
|
# for d in domains_to_undefine:
|
|
# driver.node_undefine_by_name(d)
|
|
|
|
# Remove devops nodes without domains
|
|
nodes_to_remove = set(nodes.keys()) - domains
|
|
for n in nodes_to_remove:
|
|
nodes[n].delete()
|
|
cls.erase_empty()
|
|
|
|
logger.info('Undefined domains: {0}, removed nodes: {1}'.format(
|
|
0, len(nodes_to_remove)
|
|
))
|
|
|
|
# LEGACY
|
|
@classmethod
|
|
def describe_environment(cls, boot_from='cdrom'):
|
|
"""This method is DEPRECATED.
|
|
|
|
Reserved for backward compatibility only.
|
|
Please use self.create_environment() instead.
|
|
"""
|
|
warnings.warn(
|
|
'describe_environment is deprecated in favor of '
|
|
'DevopsClient.create_env_from_config', DeprecationWarning)
|
|
|
|
from devops import client
|
|
dclient = client.DevopsClient()
|
|
|
|
template = settings.DEVOPS_SETTINGS_TEMPLATE
|
|
if template:
|
|
return dclient.create_env_from_config(template)
|
|
else:
|
|
return dclient.create_env()
|
|
|
|
@classmethod
|
|
@decorators.proc_lock()
|
|
def create_environment(cls, full_config):
|
|
"""Create a new environment using full_config object
|
|
|
|
:param full_config: object that describes all the parameters of
|
|
created environment
|
|
|
|
:rtype: Environment
|
|
"""
|
|
|
|
config = full_config['template']['devops_settings']
|
|
environment = cls.create(config['env_name'])
|
|
|
|
try:
|
|
# create groups and drivers
|
|
groups = config['groups']
|
|
environment.add_groups(groups)
|
|
|
|
# create address pools
|
|
address_pools = config['address_pools']
|
|
environment.add_address_pools(address_pools)
|
|
|
|
# process group items
|
|
for group_data in groups:
|
|
group = environment.get_group(name=group_data['name'])
|
|
|
|
# add l2_network_devices
|
|
group.add_l2_network_devices(
|
|
group_data.get('l2_network_devices', {}))
|
|
|
|
# add network_pools
|
|
group.add_network_pools(
|
|
group_data.get('network_pools', {}))
|
|
|
|
# Connect nodes to already created networks
|
|
for group_data in groups:
|
|
group = environment.get_group(name=group_data['name'])
|
|
|
|
# add group volumes
|
|
group.add_volumes(
|
|
group_data.get('group_volumes', []))
|
|
|
|
# add nodes
|
|
group.add_nodes(
|
|
group_data.get('nodes', []))
|
|
except Exception:
|
|
logger.error("Creation of the environment '{0}' failed"
|
|
.format(config['env_name']))
|
|
environment.erase()
|
|
raise
|
|
|
|
return environment
|
|
|
|
# LEGACY - TO MODIFY BY GROUPS
|
|
@classmethod
|
|
def erase_empty(cls):
|
|
for env in cls.list_all():
|
|
if env.get_nodes().count() == 0:
|
|
env.erase()
|
|
|
|
# LEGACY, TO REMOVE
|
|
def router(self, router_name='admin'):
|
|
msg = ('router has been deprecated in favor of '
|
|
'DevopsEnvironment.get_default_gw')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops.client import DevopsClient
|
|
env = DevopsClient().get_env(self.name)
|
|
return env.get_default_gw(l2_network_device_name=router_name)
|
|
|
|
# LEGACY, for fuel-qa compatibility
|
|
# @logwrap
|
|
def get_admin_remote(self,
|
|
login=settings.SSH_CREDENTIALS['login'],
|
|
password=settings.SSH_CREDENTIALS['password']):
|
|
"""SSH to admin node
|
|
|
|
:rtype : SSHClient
|
|
"""
|
|
msg = ('get_admin_remote has been deprecated in favor of '
|
|
'DevopsEnvironment.get_admin_remote')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
env = client.DevopsClient().get_env(self.name)
|
|
return env.get_admin_remote(login=login, password=password)
|
|
|
|
# LEGACY, for fuel-qa compatibility
|
|
# @logwrap
|
|
def get_ssh_to_remote(self, ip,
|
|
login=settings.SSH_SLAVE_CREDENTIALS['login'],
|
|
password=settings.SSH_SLAVE_CREDENTIALS['password']):
|
|
msg = ('get_ssh_to_remote has been deprecated in favor of '
|
|
'DevopsEnvironment.get_node_remote')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
env = client.DevopsClient().get_env(self.name)
|
|
return exec_helpers.SSHClient(
|
|
ip,
|
|
auth=exec_helpers.SSHAuth(
|
|
username=login, password=password,
|
|
keys=env.get_private_keys()))
|
|
|
|
# LEGACY, for fuel-qa compatibility
|
|
# @logwrap
|
|
@staticmethod
|
|
def get_ssh_to_remote_by_key(ip, keyfile):
|
|
warnings.warn('LEGACY, for fuel-qa compatibility', DeprecationWarning)
|
|
try:
|
|
with open(keyfile) as f:
|
|
keys = [paramiko.RSAKey.from_private_key(f)]
|
|
except IOError:
|
|
logger.warning('Loading of SSH key from file failed. Trying to use'
|
|
' SSH agent ...')
|
|
keys = paramiko.Agent().get_keys()
|
|
return exec_helpers.SSHClient(
|
|
ip,
|
|
auth=exec_helpers.SSHAuth(keys=keys))
|
|
|
|
# LEGACY, TO REMOVE (for fuel-qa compatibility)
|
|
def nodes(self): # migrated from EnvironmentModel.nodes()
|
|
warnings.warn(
|
|
'environment.nodes is deprecated in favor of'
|
|
' environment.get_nodes', DeprecationWarning)
|
|
# DEPRECATED. Please use environment.get_nodes() instead.
|
|
|
|
class Nodes(object):
|
|
def __init__(self, environment):
|
|
self.admins = sorted(
|
|
list(environment.get_nodes(role__contains='master')),
|
|
key=lambda node: node.name
|
|
)
|
|
self.others = sorted(
|
|
list(environment.get_nodes(role='fuel_slave')),
|
|
key=lambda node: node.name
|
|
)
|
|
self.ironics = sorted(
|
|
list(environment.get_nodes(role='ironic')),
|
|
key=lambda node: node.name
|
|
)
|
|
self.slaves = self.others
|
|
self.all = self.slaves + self.admins + self.ironics
|
|
if len(self.admins) == 0:
|
|
raise error.DevopsEnvironmentError(
|
|
"No nodes with role 'fuel_master' found in the "
|
|
"environment {env_name}, please check environment "
|
|
"configuration".format(
|
|
env_name=environment.name
|
|
))
|
|
self.admin = self.admins[0]
|
|
|
|
def __iter__(self):
|
|
return self.all.__iter__()
|
|
|
|
return Nodes(self)
|
|
|
|
# BACKWARD COMPATIBILITY LAYER
|
|
def _create_network_object(self, l2_network_device):
|
|
class LegacyNetwork(object):
|
|
def __init__(self):
|
|
self.id = l2_network_device.id
|
|
self.name = l2_network_device.name
|
|
self.uuid = l2_network_device.uuid
|
|
self.environment = self
|
|
self.has_dhcp_server = l2_network_device.dhcp
|
|
self.has_pxe_server = l2_network_device.has_pxe_server
|
|
self.has_reserved_ips = True
|
|
self.tftp_root_dir = ''
|
|
self.forward = l2_network_device.forward.mode
|
|
self.net = l2_network_device.address_pool.net
|
|
self.ip_network = l2_network_device.address_pool.net
|
|
self.ip = l2_network_device.address_pool.ip_network
|
|
self.ip_pool_start = (
|
|
l2_network_device.address_pool.ip_network[2])
|
|
self.ip_pool_end = (
|
|
l2_network_device.address_pool.ip_network[-2])
|
|
self.netmask = (
|
|
l2_network_device.address_pool.ip_network.netmask)
|
|
self.default_gw = l2_network_device.address_pool.ip_network[1]
|
|
|
|
return LegacyNetwork()
|
|
|
|
def get_env_l2_network_device(self, **kwargs):
|
|
try:
|
|
return network.L2NetworkDevice.objects.get(
|
|
group__environment=self, **kwargs)
|
|
except network.L2NetworkDevice.DoesNotExist:
|
|
raise error.DevopsObjNotFound(network.L2NetworkDevice, **kwargs)
|
|
|
|
def get_env_l2_network_devices(self, **kwargs):
|
|
return network.L2NetworkDevice.objects.filter(
|
|
group__environment=self, **kwargs).order_by('id')
|
|
|
|
# LEGACY, TO CHECK IN fuel-qa / PROXY
|
|
def get_network(self, **kwargs):
|
|
l2_network_device = self.get_env_l2_network_device(
|
|
address_pool__isnull=False, **kwargs)
|
|
return self._create_network_object(l2_network_device)
|
|
|
|
# LEGACY, TO CHECK IN fuel-qa / PROXY
|
|
def get_networks(self, **kwargs):
|
|
l2_network_devices = self.get_env_l2_network_devices(
|
|
address_pool__isnull=False, **kwargs)
|
|
return [self._create_network_object(x) for x in l2_network_devices]
|
|
|
|
def get_node(self, *args, **kwargs):
|
|
try:
|
|
return node.Node.objects.get(
|
|
*args, group__environment=self, **kwargs)
|
|
except node.Node.DoesNotExist:
|
|
raise error.DevopsObjNotFound(node.Node, *args, **kwargs)
|
|
|
|
def get_nodes(self, *args, **kwargs):
|
|
return node.Node.objects.filter(
|
|
*args, group__environment=self, **kwargs).order_by('id')
|