Files
trove/trove/tests/fakes/nova.py
T
Morgan Jones 2478c0d1d4 Implement Instance Upgrade
Implments Instance Upgrade functionality to support upgrading the
image of a Trove datastore instance from datastore_version to a
newer datastore_version of the same datastore.

This functionality builds on the Nova rebuild API to upgrade
the image of an instance with minimal downtime.

Includes datastore implementation of the upgrade functionality
for the Mysql based datastores.

Change-Id: Ie6e48d78ac07df52f686f359ca7fdadaae6ad064
Implements: blueprint image-upgrade
Depends-On: I6ec2ebb78019c014f87ba5d8cbfd284686c64f30
2016-09-08 11:32:14 -04:00

883 lines
28 KiB
Python

# Copyright 2010-2012 OpenStack Foundation
# All Rights Reserved.
#
# 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.
from novaclient import exceptions as nova_exceptions
from oslo_log import log as logging
from trove.common.exception import PollTimeOut
from trove.common.i18n import _
from trove.common import instance as rd_instance
from trove.tests.fakes.common import authorize
import collections
import eventlet
import uuid
LOG = logging.getLogger(__name__)
FAKE_HOSTS = ["fake_host_1", "fake_host_2"]
class FakeFlavor(object):
def __init__(self, id, disk, name, ram, ephemeral=0, vcpus=10):
self.id = id
self.disk = disk
self.name = name
self.ram = ram
self.vcpus = vcpus
self.ephemeral = ephemeral
@property
def links(self):
url = ("http://localhost:8774/v2/5064d71eb09c47e1956cf579822bae9a/"
"flavors/%s") % self.id
return [{"href": url, "rel": link_type}
for link_type in ['self', 'bookmark']]
@property
def href_suffix(self):
return "flavors/%s" % self.id
class FakeFlavors(object):
def __init__(self):
self.db = {}
self._add(1, 0, "m1.tiny", 512)
self._add(2, 20, "m1.small", 2048)
self._add(3, 40, "m1.medium", 4096)
self._add(4, 80, "m1.large", 8192)
self._add(5, 160, "m1.xlarge", 16384)
self._add(6, 0, "m1.nano", 64)
self._add(7, 0, "m1.micro", 128)
self._add(8, 2, "m1.rd-smaller", 768)
self._add(9, 10, "tinier", 506)
self._add(10, 2, "m1.rd-tiny", 512)
self._add(11, 0, "eph.rd-tiny", 512, 1)
self._add(12, 20, "eph.rd-smaller", 768, 2)
self._add("custom", 25, "custom.small", 512, 1)
def _add(self, *args, **kwargs):
new_flavor = FakeFlavor(*args, **kwargs)
self.db[new_flavor.id] = new_flavor
def get(self, id):
try:
id = int(id)
except ValueError:
pass
if id not in self.db:
raise nova_exceptions.NotFound(404, "Flavor id not found %s" % id)
return self.db[id]
def get_by_href(self, href):
for id in self.db:
value = self.db[id]
# Use inexact match since faking the exact endpoints would be
# difficult.
if href.endswith(value.href_suffix):
return value
raise nova_exceptions.NotFound(404, "Flavor href not found %s" % href)
def list(self):
return [self.get(id) for id in self.db]
class FakeServer(object):
next_local_id = 0
def __init__(self, parent, owner, id, name, image_id, flavor_ref,
block_device_mapping, volumes):
self.owner = owner # This is a context.
self.id = id
self.parent = parent
self.name = name
self.image_id = image_id
self.flavor_ref = flavor_ref
self.old_flavor_ref = None
self._current_status = "BUILD"
self.volumes = volumes
# This is used by "RdServers". Its easier to compute the
# fake value in this class's initializer.
self._local_id = self.next_local_id
self.next_local_id += 1
info_vols = []
for volume in self.volumes:
info_vols.append({'id': volume.id})
volume.set_attachment(id)
volume.schedule_status("in-use", 1)
self.host = FAKE_HOSTS[0]
self.old_host = None
setattr(self, 'OS-EXT-AZ:availability_zone', 'nova')
self._info = {'os:volumes': info_vols}
@property
def addresses(self):
return {"private": [{"addr": "123.123.123.123"}]}
def confirm_resize(self):
if self.status != "VERIFY_RESIZE":
raise RuntimeError("Not in resize confirm mode.")
self._current_status = "ACTIVE"
def revert_resize(self):
if self.status != "VERIFY_RESIZE":
raise RuntimeError("Not in resize confirm mode.")
self.host = self.old_host
self.old_host = None
self.flavor_ref = self.old_flavor_ref
self.old_flavor_ref = None
self._current_status = "ACTIVE"
def reboot(self):
LOG.debug("Rebooting server %s" % (self.id))
def set_to_active():
self._current_status = "ACTIVE"
self.parent.schedule_simulate_running_server(self.id, 1.5)
self._current_status = "REBOOT"
eventlet.spawn_after(1, set_to_active)
def delete(self):
self.schedule_status = []
# TODO(pdmars): This is less than ideal, but a quick way to force it
# into the error state before scheduling the delete.
if (self.name.endswith("_ERROR_ON_DELETE") and
self._current_status != "SHUTDOWN"):
# Fail to delete properly the first time, just set the status
# to SHUTDOWN and break. It's important that we only fail to delete
# once in fake mode.
self._current_status = "SHUTDOWN"
return
self._current_status = "SHUTDOWN"
self.parent.schedule_delete(self.id, 1.5)
@property
def flavor(self):
return FLAVORS.get_by_href(self.flavor_ref).__dict__
@property
def links(self):
url = "https://localhost:9999/v1.0/1234/instances/%s" % self.id
return [{"href": url, "rel": link_type}
for link_type in ['self', 'bookmark']]
def migrate(self, force_host=None):
self.resize(None, force_host)
def resize(self, new_flavor_id=None, force_host=None):
self._current_status = "RESIZE"
if self.name.endswith("_RESIZE_TIMEOUT"):
raise PollTimeOut()
def set_to_confirm_mode():
self._current_status = "VERIFY_RESIZE"
def set_to_active():
self.parent.schedule_simulate_running_server(self.id, 1.5)
eventlet.spawn_after(1, set_to_active)
def change_host():
self.old_host = self.host
if not force_host:
self.host = [host for host in FAKE_HOSTS
if host != self.host][0]
else:
self.host = force_host
def set_flavor():
if self.name.endswith("_RESIZE_ERROR"):
self._current_status = "ACTIVE"
return
if new_flavor_id is None:
# Migrations are flavorless flavor resizes.
# A resize MIGHT change the host, but a migrate
# deliberately does.
LOG.debug("Migrating fake instance.")
eventlet.spawn_after(0.75, change_host)
else:
LOG.debug("Resizing fake instance.")
self.old_flavor_ref = self.flavor_ref
flavor = self.parent.flavors.get(new_flavor_id)
self.flavor_ref = flavor.links[0]['href']
eventlet.spawn_after(1, set_to_confirm_mode)
eventlet.spawn_after(0.8, set_flavor)
def schedule_status(self, new_status, time_from_now):
"""Makes a new status take effect at the given time."""
def set_status():
self._current_status = new_status
eventlet.spawn_after(time_from_now, set_status)
@property
def status(self):
return self._current_status
@property
def created(self):
return "2012-01-25T21:55:51Z"
@property
def updated(self):
return "2012-01-25T21:55:51Z"
@property
def tenant(self): # This is on the RdServer extension type.
return self.owner.tenant
@property
def tenant_id(self):
return self.owner.tenant
# The global var contains the servers dictionary in use for the life of these
# tests.
FAKE_SERVERS_DB = {}
class FakeServers(object):
def __init__(self, context, flavors):
self.context = context
self.db = FAKE_SERVERS_DB
self.flavors = flavors
def can_see(self, id):
"""Can this FakeServers, with its context, see some resource?"""
server = self.db[id]
return (self.context.is_admin or
server.owner.tenant == self.context.tenant)
def create(self, name, image_id, flavor_ref, files=None, userdata=None,
block_device_mapping=None, volume=None, security_groups=None,
availability_zone=None, nics=None, config_drive=False,
scheduler_hints=None):
id = "FAKE_%s" % uuid.uuid4()
if volume:
volume = self.volumes.create(volume['size'], volume['name'],
volume['description'])
while volume.status == "BUILD":
eventlet.sleep(0.1)
if volume.status != "available":
LOG.info(_("volume status = %s") % volume.status)
raise nova_exceptions.ClientException("Volume was bad!")
mapping = "%s::%s:%s" % (volume.id, volume.size, 1)
block_device_mapping = {'vdb': mapping}
volumes = [volume]
LOG.debug("Fake Volume Create %(volumeid)s with "
"status %(volumestatus)s" %
{'volumeid': volume.id, 'volumestatus': volume.status})
else:
volumes = self._get_volumes_from_bdm(block_device_mapping)
for volume in volumes:
volume.schedule_status('in-use', 1)
server = FakeServer(self, self.context, id, name, image_id, flavor_ref,
block_device_mapping, volumes)
self.db[id] = server
if name.endswith('SERVER_ERROR'):
raise nova_exceptions.ClientException("Fake server create error.")
if availability_zone == 'BAD_ZONE':
raise nova_exceptions.ClientException("The requested availability "
"zone is not available.")
if nics:
if 'port-id' in nics[0] and nics[0]['port-id'] == "UNKNOWN":
raise nova_exceptions.ClientException("The requested "
"port-id is not "
"available.")
server.schedule_status("ACTIVE", 1)
LOG.info("FAKE_SERVERS_DB : %s" % str(FAKE_SERVERS_DB))
return server
def _get_volumes_from_bdm(self, block_device_mapping):
volumes = []
if block_device_mapping is not None:
# block_device_mapping is a dictionary, where the key is the
# device name on the compute instance and the mapping info is a
# set of fields in a string, separated by colons.
# For each device, find the volume, and record the mapping info
# to another fake object and attach it to the volume
# so that the fake API can later retrieve this.
for device in block_device_mapping:
mapping = block_device_mapping[device]
(id, _type, size, delete_on_terminate) = mapping.split(":")
volume = self.volumes.get(id)
volume.mapping = FakeBlockDeviceMappingInfo(
id, device, _type, size, delete_on_terminate)
volumes.append(volume)
return volumes
def get(self, id):
if id not in self.db:
LOG.error(_("Couldn't find server id %(id)s, collection=%(db)s") %
{'id': id, 'db': self.db})
raise nova_exceptions.NotFound(404, "Not found")
else:
if self.can_see(id):
return self.db[id]
else:
raise nova_exceptions.NotFound(404, "Bad permissions")
def get_server_volumes(self, server_id):
"""Fake method we've added to grab servers from the volume."""
return [volume.mapping
for volume in self.get(server_id).volumes
if volume.mapping is not None]
def list(self):
return [v for (k, v) in self.db.items() if self.can_see(v.id)]
def schedule_delete(self, id, time_from_now):
def delete_server():
LOG.info(_("Simulated event ended, deleting server %s.") % id)
del self.db[id]
eventlet.spawn_after(time_from_now, delete_server)
def schedule_simulate_running_server(self, id, time_from_now):
from trove.instance.models import DBInstance
from trove.instance.models import InstanceServiceStatus
def set_server_running():
instance = DBInstance.find_by(compute_instance_id=id)
LOG.debug("Setting server %s to running" % instance.id)
status = InstanceServiceStatus.find_by(instance_id=instance.id)
status.status = rd_instance.ServiceStatuses.RUNNING
status.save()
eventlet.spawn_after(time_from_now, set_server_running)
class FakeRdServer(object):
def __init__(self, server):
self.server = server
self.deleted = False
self.deleted_at = None # Not sure how to simulate "True" for this.
self.local_id = server._local_id
def __getattr__(self, name):
return getattr(self.server, name)
class FakeRdServers(object):
def __init__(self, servers):
self.servers = servers
def get(self, id):
return FakeRdServer(self.servers.get(id))
def list(self):
# Attach the extra Rd Server stuff to the normal server.
return [FakeRdServer(server) for server in self.servers.list()]
class FakeServerVolumes(object):
def __init__(self, context):
self.context = context
def get_server_volumes(self, server_id):
class ServerVolumes(object):
def __init__(self, block_device_mapping):
LOG.debug("block_device_mapping = %s" %
block_device_mapping)
device = block_device_mapping['vdb']
(self.volumeId,
self.type,
self.size,
self.delete_on_terminate) = device.split(":")
fake_servers = FakeServers(self.context, FLAVORS)
server = fake_servers.get(server_id)
return [ServerVolumes(server.block_device_mapping)]
class FakeVolume(object):
def __init__(self, parent, owner, id, size, name,
description, volume_type):
self.attachments = []
self.parent = parent
self.owner = owner # This is a context.
self.id = id
self.size = size
self.name = name
self.description = description
self._current_status = "BUILD"
# For some reason we grab this thing from device then call it mount
# point.
self.device = "vdb"
self.volume_type = volume_type
def __repr__(self):
msg = ("FakeVolume(id=%s, size=%s, name=%s, "
"description=%s, _current_status=%s)")
params = (self.id, self.size, self.name,
self.description, self._current_status)
return (msg % params)
@property
def availability_zone(self):
return "fake-availability-zone"
@property
def created_at(self):
return "2001-01-01-12:30:30"
def get(self, key):
return getattr(self, key)
def schedule_status(self, new_status, time_from_now):
"""Makes a new status take effect at the given time."""
def set_status():
self._current_status = new_status
eventlet.spawn_after(time_from_now, set_status)
def set_attachment(self, server_id):
"""Fake method we've added to set attachments. Idempotent."""
for attachment in self.attachments:
if attachment['server_id'] == server_id:
return # Do nothing
self.attachments.append({'server_id': server_id,
'device': self.device})
@property
def status(self):
return self._current_status
class FakeBlockDeviceMappingInfo(object):
def __init__(self, id, device, _type, size, delete_on_terminate):
self.volumeId = id
self.device = device
self.type = _type
self.size = size
self.delete_on_terminate = delete_on_terminate
FAKE_VOLUMES_DB = {}
class FakeVolumes(object):
def __init__(self, context):
self.context = context
self.db = FAKE_VOLUMES_DB
def can_see(self, id):
"""Can this FakeVolumes, with its context, see some resource?"""
server = self.db[id]
return (self.context.is_admin or
server.owner.tenant == self.context.tenant)
def get(self, id):
if id not in self.db:
LOG.error(_("Couldn't find volume id %(id)s, collection=%(db)s") %
{'id': id, 'db': self.db})
raise nova_exceptions.NotFound(404, "Not found")
else:
if self.can_see(id):
return self.db[id]
else:
raise nova_exceptions.NotFound(404, "Bad permissions")
def create(self, size, name=None, description=None, volume_type=None):
id = "FAKE_VOL_%s" % uuid.uuid4()
volume = FakeVolume(self, self.context, id, size, name,
description, volume_type)
self.db[id] = volume
if size == 9:
volume.schedule_status("error", 2)
elif size == 13:
raise Exception("No volume for you!")
else:
volume.schedule_status("available", 2)
LOG.debug("Fake volume created %(volumeid)s with "
"status %(volumestatus)s" %
{'volumeid': volume.id, 'volumestatus': volume.status})
LOG.info("FAKE_VOLUMES_DB : %s" % FAKE_VOLUMES_DB)
return volume
def list(self, detailed=True):
return [self.db[key] for key in self.db]
def extend(self, volume_id, new_size):
LOG.debug("Resize volume id (%(volumeid)s) to size (%(size)s)" %
{'volumeid': volume_id, 'size': new_size})
volume = self.get(volume_id)
if volume._current_status != 'available':
raise Exception("Invalid volume status: "
"expected 'in-use' but was '%s'" %
volume._current_status)
def finish_resize():
volume.size = new_size
eventlet.spawn_after(1.0, finish_resize)
def delete_server_volume(self, server_id, volume_id):
volume = self.get(volume_id)
if volume._current_status != 'in-use':
raise Exception("Invalid volume status: "
"expected 'in-use' but was '%s'" %
volume._current_status)
def finish_detach():
volume._current_status = "available"
eventlet.spawn_after(1.0, finish_detach)
def create_server_volume(self, server_id, volume_id, device_path):
volume = self.get(volume_id)
if volume._current_status != "available":
raise Exception("Invalid volume status: "
"expected 'available' but was '%s'" %
volume._current_status)
def finish_attach():
volume._current_status = "in-use"
eventlet.spawn_after(1.0, finish_attach)
class FakeAccount(object):
def __init__(self, id, servers):
self.id = id
self.servers = self._servers_to_dict(servers)
def _servers_to_dict(self, servers):
ret = []
for server in servers:
server_dict = {}
server_dict['id'] = server.id
server_dict['name'] = server.name
server_dict['status'] = server.status
server_dict['host'] = server.host
ret.append(server_dict)
return ret
class FakeAccounts(object):
def __init__(self, context, servers):
self.context = context
self.db = FAKE_SERVERS_DB
self.servers = servers
def _belongs_to_tenant(self, tenant, id):
server = self.db[id]
return server.tenant == tenant
def get_instances(self, id):
authorize(self.context)
servers = [v for (k, v) in self.db.items()
if self._belongs_to_tenant(id, v.id)]
return FakeAccount(id, servers)
FLAVORS = FakeFlavors()
class FakeHost(object):
def __init__(self, name, servers):
self.name = name
self.servers = servers
self.instances = []
self.percentUsed = 0
self.totalRAM = 0
self.usedRAM = 0
@property
def instanceCount(self):
return len(self.instances)
def recalc(self):
"""
This fake-mode exclusive method recalculates the fake data this
object passes back.
"""
self.instances = []
self.percentUsed = 0
self.totalRAM = 32000 # 16384
self.usedRAM = 0
for server in self.servers.list():
print(server)
if server.host != self.name:
print("\t...not on this host.")
continue
self.instances.append({
'uuid': server.id,
'name': server.name,
'status': server.status
})
if (str(server.flavor_ref).startswith('http:') or
str(server.flavor_ref).startswith('https:')):
flavor = FLAVORS.get_by_href(server.flavor_ref)
else:
flavor = FLAVORS.get(server.flavor_ref)
ram = flavor.ram
self.usedRAM += ram
decimal = float(self.usedRAM) / float(self.totalRAM)
self.percentUsed = int(decimal * 100)
class FakeHosts(object):
def __init__(self, servers):
# Use an ordered dict to make the results of the fake api call
# return in the same order for the example generator.
self.hosts = collections.OrderedDict()
for host in FAKE_HOSTS:
self.add_host(FakeHost(host, servers))
def add_host(self, host):
self.hosts[host.name] = host
return host
def get(self, name):
try:
self.hosts[name].recalc()
return self.hosts[name]
except KeyError:
raise nova_exceptions.NotFound(404, "Host not found %s" % name)
def list(self):
for name in self.hosts:
self.hosts[name].recalc()
return [self.hosts[name] for name in self.hosts]
class FakeRdStorage(object):
def __init__(self, name):
self.name = name
self.type = ""
self.used = 0
self.capacity = {}
self.provision = {}
def recalc(self):
self.type = "test_type"
self.used = 10
self.capacity['total'] = 100
self.capacity['available'] = 90
self.provision['total'] = 50
self.provision['available'] = 40
self.provision['percent'] = 10
class FakeRdStorages(object):
def __init__(self):
self.storages = {}
self.add_storage(FakeRdStorage("fake_storage"))
def add_storage(self, storage):
self.storages[storage.name] = storage
return storage
def list(self):
for name in self.storages:
self.storages[name].recalc()
return [self.storages[name] for name in self.storages]
class FakeSecurityGroup(object):
def __init__(self, name=None, description=None, context=None):
self.name = name
self.description = description
self.id = "FAKE_SECGRP_%s" % uuid.uuid4()
self.rules = {}
def get_id(self):
return self.id
def add_rule(self, fakeSecGroupRule):
self.rules.append(fakeSecGroupRule)
return self.rules
def get_rules(self):
result = ""
for rule in self.rules:
result = result + rule.data()
return result
def data(self):
return {
'id': self.id,
'name': self.name,
'description': self.description
}
class FakeSecurityGroups(object):
def __init__(self, context=None):
self.context = context
self.securityGroups = {}
def create(self, name=None, description=None):
secGrp = FakeSecurityGroup(name, description)
self.securityGroups[secGrp.get_id()] = secGrp
return secGrp
def delete(self, group_id):
pass
def list(self):
pass
class FakeSecurityGroupRule(object):
def __init__(self, ip_protocol=None, from_port=None, to_port=None,
cidr=None, parent_group_id=None, context=None):
self.group_id = parent_group_id
self.protocol = ip_protocol
self.from_port = from_port
self.to_port = to_port
self.cidr = cidr
self.context = context
self.id = "FAKE_SECGRP_RULE_%s" % uuid.uuid4()
def get_id(self):
return self.id
def data(self):
return {
'id': self.id,
'group_id': self.group_id,
'protocol': self.protocol,
'from_port': self.from_port,
'to_port': self.to_port,
'cidr': self.cidr
}
class FakeSecurityGroupRules(object):
def __init__(self, context=None):
self.context = context
self.securityGroupRules = {}
def create(self, parent_group_id, ip_protocol, from_port, to_port, cidr):
secGrpRule = FakeSecurityGroupRule(ip_protocol, from_port, to_port,
cidr, parent_group_id)
self.securityGroupRules[secGrpRule.get_id()] = secGrpRule
return secGrpRule
def delete(self, id):
if id in self.securityGroupRules:
del self.securityGroupRules[id]
class FakeServerGroup(object):
def __init__(self, name=None, policies=None, context=None):
self.name = name
self.description = description
self.id = "FAKE_SRVGRP_%s" % uuid.uuid4()
self.policies = policies or {}
def get_id(self):
return self.id
def data(self):
return {
'id': self.id,
'name': self.name,
'policies': self.policies
}
class FakeServerGroups(object):
def __init__(self, context=None):
self.context = context
self.server_groups = {}
def create(self, name=None, policies=None):
server_group = FakeServerGroup(name, policies, context=self.context)
self.server_groups[server_group.get_id()] = server_group
return server_group
def delete(self, group_id):
pass
def list(self):
return self.server_groups
class FakeClient(object):
def __init__(self, context):
self.context = context
self.flavors = FLAVORS
self.servers = FakeServers(context, self.flavors)
self.volumes = FakeVolumes(context)
self.servers.volumes = self.volumes
self.accounts = FakeAccounts(context, self.servers)
self.rdhosts = FakeHosts(self.servers)
self.rdstorage = FakeRdStorages()
self.rdservers = FakeRdServers(self.servers)
self.security_groups = FakeSecurityGroups(context)
self.security_group_rules = FakeSecurityGroupRules(context)
self.server_groups = FakeServerGroups(context)
def get_server_volumes(self, server_id):
return self.servers.get_server_volumes(server_id)
def rescan_server_volume(self, server, volume_id):
LOG.info("FAKE rescanning volume.")
CLIENT_DATA = {}
def get_client_data(context):
if context not in CLIENT_DATA:
nova_client = FakeClient(context)
volume_client = FakeClient(context)
volume_client.servers = nova_client
CLIENT_DATA[context] = {
'nova': nova_client,
'volume': volume_client
}
return CLIENT_DATA[context]
def fake_create_nova_client(context):
return get_client_data(context)['nova']
def fake_create_nova_volume_client(context):
return get_client_data(context)['volume']
def fake_create_cinder_client(context):
return get_client_data(context)['volume']