2478c0d1d4
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
883 lines
28 KiB
Python
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']
|