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