1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Portions Copyright Buildbot Team Members 15# Portions Copyright 2013 Cray Inc. 16 17 18import uuid 19 20ACTIVE = 'ACTIVE' 21BUILD = 'BUILD' 22DELETED = 'DELETED' 23ERROR = 'ERROR' 24UNKNOWN = 'UNKNOWN' 25 26TEST_UUIDS = { 27 'image': '28a65eb4-f354-4420-97dc-253b826547f7', 28 'volume': '65fbb9f1-c4d5-40a8-a233-ad47c52bb837', 29 'snapshot': 'ab89152d-3c26-4d30-9ae5-65b705f874b7', 30 'flavor': '853774a1-459f-4f1f-907e-c96f62472531', 31} 32 33 34class FakeNovaClient(): 35 region_name = "" 36 37 38# Parts used from novaclient 39class Client(): 40 41 def __init__(self, version, session): 42 self.glance = ItemManager() 43 self.glance._add_items([Image(TEST_UUIDS['image'], 'CirrOS 0.3.4', 13287936)]) 44 self.volumes = ItemManager() 45 self.volumes._add_items([Volume(TEST_UUIDS['volume'], 'CirrOS 0.3.4', 4)]) 46 self.volume_snapshots = ItemManager() 47 self.volume_snapshots._add_items([Snapshot(TEST_UUIDS['snapshot'], 'CirrOS 0.3.4', 2)]) 48 self.flavors = ItemManager() 49 self.flavors._add_items([Flavor(TEST_UUIDS['flavor'], 'm1.small', 0)]) 50 self.servers = Servers() 51 self.session = session 52 self.client = FakeNovaClient() 53 54 55class ItemManager(): 56 57 def __init__(self): 58 self._items = {} 59 60 def _add_items(self, new_items): 61 for item in new_items: 62 self._items[item.id] = item 63 64 def list(self): 65 return self._items.values() 66 67 def get(self, uuid): 68 if uuid in self._items: 69 return self._items[uuid] 70 else: 71 raise NotFound 72 73 74# This exists because Image needs an attribute that isn't supported by 75# namedtuple. And once the base code is there might as well have Volume and 76# Snapshot use it too. 77class Item(): 78 79 def __init__(self, id, name, size): 80 self.id = id 81 self.name = name 82 self.size = size 83 84 85class Image(Item): 86 87 def __init__(self, *args, **kwargs): 88 super().__init__(*args, **kwargs) 89 setattr(self, 'OS-EXT-IMG-SIZE:size', self.size) 90 91 92class Flavor(Item): 93 pass 94 95 96class Volume(Item): 97 pass 98 99 100class Snapshot(Item): 101 pass 102 103 104class Servers(): 105 fail_to_get = False 106 fail_to_start = False 107 gets_until_active = 3 108 gets_until_disappears = 1 109 instances = {} 110 111 def create(self, *boot_args, **boot_kwargs): 112 instance_id = uuid.uuid4() 113 instance = Instance(instance_id, self, boot_args, boot_kwargs) 114 self.instances[instance_id] = instance 115 return instance 116 117 def get(self, instance_id): 118 if instance_id not in self.instances: 119 raise NotFound 120 inst = self.instances[instance_id] 121 if not self.fail_to_get or inst.gets < self.gets_until_disappears: 122 if not inst.status.startswith('BUILD'): 123 return inst 124 inst.gets += 1 125 if inst.gets >= self.gets_until_active: 126 if not self.fail_to_start: 127 inst.status = ACTIVE 128 else: 129 inst.status = ERROR 130 return inst 131 else: 132 raise NotFound 133 134 def delete(self, instance_id): 135 if instance_id in self.instances: 136 del self.instances[instance_id] 137 138 def findall(self, **kwargs): 139 name = kwargs.get('name', None) 140 if name: 141 return list(filter(lambda item: item.name == name, self.instances.values())) 142 return [] 143 144 def find(self, **kwargs): 145 result = self.findall(**kwargs) 146 if len(result) > 0: 147 raise NoUniqueMatch 148 if len(result) == 0: 149 raise NotFound 150 return result[0] 151 152 153# This is returned by Servers.create(). 154class Instance(): 155 156 def __init__(self, id, servers, boot_args, boot_kwargs): 157 self.id = id 158 self.servers = servers 159 self.boot_args = boot_args 160 self.boot_kwargs = boot_kwargs 161 self.gets = 0 162 self.status = 'BUILD(networking)' 163 self.metadata = boot_kwargs.get('meta', {}) 164 try: 165 self.name = boot_args[0] 166 except IndexError: 167 self.name = 'name' 168 169 def delete(self): 170 self.servers.delete(self.id) 171 172# Parts used from novaclient.exceptions. 173 174 175class NotFound(Exception): 176 pass 177 178 179class NoUniqueMatch(Exception): 180 pass 181 182# Parts used from keystoneauth1. 183 184 185def get_plugin_loader(plugin_type): 186 if plugin_type == 'password': 187 return PasswordLoader() 188 if plugin_type == 'token': 189 return TokenLoader() 190 raise ValueError("plugin_type '{}' is not supported".format(plugin_type)) 191 192 193class PasswordLoader(): 194 195 def load_from_options(self, **kwargs): 196 return PasswordAuth(**kwargs) 197 198 199class TokenLoader(): 200 def load_from_options(self, **kwargs): 201 return TokenAuth(**kwargs) 202 203 204class PasswordAuth(): 205 206 def __init__(self, auth_url, password, project_name, username, user_domain_name=None, 207 project_domain_name=None): 208 self.auth_url = auth_url 209 self.password = password 210 self.project_name = project_name 211 self.username = username 212 self.user_domain_name = user_domain_name 213 self.project_domain_name = project_domain_name 214 215 216class TokenAuth(): 217 def __init__(self, auth_url, token): 218 self.auth_url = auth_url 219 self.token = token 220 self.project_name = 'tenant' 221 self.username = 'testuser' 222 self.user_domain_name = 'token' 223 self.project_domain_name = 'token' 224 225 226class Session(): 227 228 def __init__(self, auth): 229 self.auth = auth 230