# 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 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, volumes, key_name): 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} self.key_name = key_name @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.project_id) def create(self, name, image_id, flavor_ref, files=None, userdata=None, block_device_mapping_v2=None, security_groups=None, availability_zone=None, nics=None, config_drive=False, scheduler_hints=None, key_name=None): id = "FAKE_%s" % uuid.uuid4() volumes = self._get_volumes_from_bdm_v2(block_device_mapping_v2) server = FakeServer(self, self.context, id, name, image_id, flavor_ref, volumes, key_name) 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.") server.schedule_status("ACTIVE", 1) LOG.info("FAKE_SERVERS_DB : %s", str(FAKE_SERVERS_DB)) return server def _get_volumes_from_bdm_v2(self, block_device_mapping_v2): volumes = [] if block_device_mapping_v2 is not None: # block_device_mapping_v2 is an array of dicts. Every dict is # a volume attachment, which contains "uuid", "source_type", # "destination_type", "device_name", "volume_size" and # "delete_on_termination". for bdm in block_device_mapping_v2: volume = self.volumes.get(bdm['uuid']) 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 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 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.project_id) 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 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, region_name=None): return get_client_data(context)['nova'] def fake_create_nova_volume_client(context, region_name=None): return get_client_data(context)['volume'] def fake_create_cinder_client(context, region_name=None): return get_client_data(context)['volume']