~juju-deployers/python-jujuclient/trunk

« back to all changes in this revision

Viewing changes to tests/test_juju1.py

  • Committer: Tim Van Steenburgh
  • Date: 2016-07-12 02:28:46 UTC
  • Revision ID: tvansteenburgh@gmail.com-20160712022846-g14fe37eild7gk2e
juju-2.0beta11 compatibility

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import copy
 
2
import errno
 
3
import mock
 
4
import os
 
5
import pytest
 
6
import shutil
 
7
import socket
 
8
import ssl
 
9
import tempfile
 
10
import unittest
 
11
import yaml
 
12
 
 
13
import jujuclient
 
14
import jujuclient.utils
 
15
 
 
16
from jujuclient.juju1.connector import Connector
 
17
from jujuclient.juju1.environment import Environment
 
18
from jujuclient.juju1.rpc import RPC
 
19
from jujuclient.juju1.watch import WaitForNoMachines
 
20
from jujuclient.juju1.facades import (
 
21
    Actions,
 
22
    Annotations,
 
23
    Backups,
 
24
    Charms,
 
25
    HA,
 
26
    KeyManager,
 
27
    UserManager,
 
28
)
 
29
 
 
30
from jujuclient.exc import (
 
31
    EnvError,
 
32
    EnvironmentNotBootstrapped,
 
33
)
 
34
from jujuclient.connector import SSL_VERSION
 
35
 
 
36
# skip this entire test module unless we're running on juju 1.x
 
37
pytestmark = pytest.mark.skipif(
 
38
    jujuclient.utils.get_juju_major_version() != 1,
 
39
    reason="Running Juju 1.x tests only",
 
40
)
 
41
 
 
42
try:
 
43
    ssl._create_default_https_context = ssl._create_unverified_context
 
44
except AttributeError:
 
45
    # Legacy Python doesn't verify by default (see pep-0476)
 
46
    #   https://www.python.org/dev/peps/pep-0476/
 
47
    pass
 
48
 
 
49
 
 
50
ENV_NAME = os.environ.get("JUJU_TEST_ENV")
 
51
 
 
52
if not ENV_NAME:
 
53
    raise ValueError("No Testing Environment Defined.")
 
54
 
 
55
SAMPLE_CONFIG = {
 
56
    'user': 'tester',
 
57
    'password': 'sekrit',
 
58
    'environ-uuid': 'some-uuid',
 
59
    'server-uuid': 'server-uuid',
 
60
    'state-servers': ['localhost:12345'],
 
61
    'ca-cert': 'test-cert',
 
62
}
 
63
 
 
64
 
 
65
def reset(env):
 
66
    status = env.status()
 
67
    for s in status['Services'].keys():
 
68
        env.destroy_service(s)
 
69
    if env.version == 0:
 
70
        env.destroy_machines(sorted(status['Machines'].keys())[1:], force=True)
 
71
    else:
 
72
        env.destroy_machines(sorted(status['Machines'].keys()), force=True)
 
73
    watch = env.get_watch()
 
74
    WaitForNoMachines(watch, status['Machines']).run()
 
75
    while True:
 
76
        status = env.status()
 
77
        if len(status['Services']) == 0:
 
78
            break
 
79
 
 
80
 
 
81
class ClientRPCTest(unittest.TestCase):
 
82
 
 
83
    @mock.patch('jujuclient.juju1.rpc.RPC._send_request')
 
84
    @mock.patch('jujuclient.juju1.rpc.RPC._upgrade_retry_delay_secs', 0.001)
 
85
    def test_retry_on_upgrade_error(self, send_request):
 
86
        send_request.return_value = {"Error": "upgrade in progress"}
 
87
        rpc_client = RPC()
 
88
        rpc_client.conn = mock.Mock()
 
89
        self.assertRaises(EnvError, rpc_client.login, "password")
 
90
        self.assertEquals(send_request.call_count, 61)
 
91
 
 
92
    @mock.patch('jujuclient.juju1.rpc.RPC._send_request')
 
93
    def test_no_retry_required(self, send_request):
 
94
        send_request.return_value = {"Error": "some other error"}
 
95
        rpc_client = RPC()
 
96
        rpc_client.conn = mock.Mock()
 
97
        self.assertRaises(EnvError, rpc_client.login, "password")
 
98
        self.assertEquals(send_request.call_count, 1)
 
99
 
 
100
 
 
101
class ClientConnectorTest(unittest.TestCase):
 
102
 
 
103
    def mkdir(self):
 
104
        d = tempfile.mkdtemp()
 
105
        self.addCleanup(shutil.rmtree, d)
 
106
        return d
 
107
 
 
108
    @mock.patch('jujuclient.connector.websocket')
 
109
    def test_connect_socket(self, websocket):
 
110
        address = "wss://abc:17070"
 
111
        Connector.connect_socket(address)
 
112
        websocket.create_connection.assert_called_once_with(
 
113
            address, origin=address, sslopt={
 
114
                'ssl_version': SSL_VERSION,
 
115
                'cert_reqs': ssl.CERT_NONE})
 
116
 
 
117
    @mock.patch('socket.create_connection')
 
118
    def test_is_server_available_unknown_error(self, connect_socket):
 
119
        connect_socket.side_effect = ValueError()
 
120
        self.assertRaises(
 
121
            ValueError, Connector().is_server_available,
 
122
            'foo.example.com:7070')
 
123
 
 
124
    @mock.patch('socket.create_connection')
 
125
    def test_is_server_available_known_error(self, connect_socket):
 
126
        e = socket.error()
 
127
        e.errno = errno.ETIMEDOUT
 
128
        connect_socket.side_effect = e
 
129
        self.assertFalse(
 
130
            Connector().is_server_available("foo.example.com:7070"))
 
131
 
 
132
    def test_split_host_port_dns(self):
 
133
        self.assertEqual(
 
134
            Connector.split_host_port('foo.example.com:7070'),
 
135
            ('foo.example.com', '7070')
 
136
        )
 
137
 
 
138
    def test_is_server_available_ipv4(self):
 
139
        self.assertEqual(
 
140
            Connector.split_host_port('10.0.0.10:7070'),
 
141
            ('10.0.0.10', '7070')
 
142
        )
 
143
 
 
144
    def test_is_server_available_ipv6(self):
 
145
        self.assertEqual(
 
146
            Connector.split_host_port('[2001:db8::1]:7070'),
 
147
            ('2001:db8::1', '7070')
 
148
        )
 
149
 
 
150
    def test_is_server_available_invalid_input(self):
 
151
        self.assertRaises(
 
152
            ValueError, Connector.split_host_port, 'I am not an ip/port combo'
 
153
        )
 
154
 
 
155
    def test_parse_env_missing(self):
 
156
        temp_juju_home = self.mkdir()
 
157
        with mock.patch.dict('os.environ', {'JUJU_HOME': temp_juju_home}):
 
158
            connector = Connector()
 
159
            self.assertRaises(
 
160
                EnvironmentNotBootstrapped,
 
161
                connector.parse_env,
 
162
                'missing')
 
163
 
 
164
    def test_parse_env_jenv(self):
 
165
        temp_juju_home = self.mkdir()
 
166
        self.write_jenv(temp_juju_home, 'test-model', SAMPLE_CONFIG)
 
167
        with mock.patch.dict('os.environ', {'JUJU_HOME': temp_juju_home}):
 
168
            connector = Connector()
 
169
            home, env = connector.parse_env('test-model')
 
170
            self.assertEqual(home, temp_juju_home)
 
171
            self.assertEqual(env, SAMPLE_CONFIG)
 
172
 
 
173
    def test_parse_cache_file(self):
 
174
        temp_juju_home = self.mkdir()
 
175
        self.write_cache_file(temp_juju_home, 'test-model', SAMPLE_CONFIG)
 
176
        with mock.patch.dict('os.environ', {'JUJU_HOME': temp_juju_home}):
 
177
            connector = Connector()
 
178
            home, env = connector.parse_env('test-model')
 
179
            self.assertEqual(home, temp_juju_home)
 
180
            self.assertEqual(env, SAMPLE_CONFIG)
 
181
 
 
182
    def test_parse_cache_file_missing_env(self):
 
183
        """Create a valid cache file, but look for an environment that isn't
 
184
        there.
 
185
        """
 
186
        temp_juju_home = self.mkdir()
 
187
        self.write_cache_file(temp_juju_home, 'test-model', SAMPLE_CONFIG)
 
188
        with mock.patch.dict('os.environ', {'JUJU_HOME': temp_juju_home}):
 
189
            connector = Connector()
 
190
            self.assertRaises(
 
191
                EnvironmentNotBootstrapped,
 
192
                connector.parse_env,
 
193
                'missing')
 
194
 
 
195
    def test_parse_env_cache_file_first(self):
 
196
        """The cache file has priority over a jenv file."""
 
197
        temp_juju_home = self.mkdir()
 
198
        content = copy.deepcopy(SAMPLE_CONFIG)
 
199
        self.write_jenv(temp_juju_home, 'test-model', content)
 
200
        # Now change the password.
 
201
        content['password'] = 'new password'
 
202
        self.write_cache_file(temp_juju_home, 'test-model', content)
 
203
        with mock.patch.dict('os.environ', {'JUJU_HOME': temp_juju_home}):
 
204
            connector = Connector()
 
205
            home, env = connector.parse_env('test-model')
 
206
            self.assertEqual(home, temp_juju_home)
 
207
            self.assertEqual(env, content)
 
208
 
 
209
    def write_jenv(self, juju_home, env_name, content):
 
210
        env_dir = os.path.join(juju_home, 'environments')
 
211
        if not os.path.exists(env_dir):
 
212
            os.mkdir(env_dir)
 
213
        jenv = os.path.join(env_dir, '%s.jenv' % env_name)
 
214
        with open(jenv, 'w') as f:
 
215
            yaml.dump(content, f, default_flow_style=False)
 
216
 
 
217
    def write_cache_file(self, juju_home, env_name, content):
 
218
        env_dir = os.path.join(juju_home, 'environments')
 
219
        if not os.path.exists(env_dir):
 
220
            os.mkdir(env_dir)
 
221
        filename = os.path.join(env_dir, 'cache.yaml')
 
222
        cache_content = {
 
223
            'environment': {
 
224
                env_name: {'env-uuid': content['environ-uuid'],
 
225
                           'server-uuid': content['server-uuid'],
 
226
                           'user': content['user']}},
 
227
            'server-data': {
 
228
                content['server-uuid']: {
 
229
                    'api-endpoints': content['state-servers'],
 
230
                    'ca-cert': content['ca-cert'],
 
231
                    'identities': {content['user']: content['password']}}},
 
232
            # Explicitly don't care about 'server-user' here.
 
233
            }
 
234
        with open(filename, 'w') as f:
 
235
            yaml.dump(cache_content, f, default_flow_style=False)
 
236
 
 
237
 
 
238
class KeyManagerTest(unittest.TestCase):
 
239
    def setUp(self):
 
240
        self.env = Environment.connect(ENV_NAME)
 
241
        self.keys = KeyManager(self.env)
 
242
 
 
243
    def tearDown(self):
 
244
        self.env.close()
 
245
 
 
246
    def verify_keys(self, expected, user='admin', present=True):
 
247
        keys = self.keys(user)['Results'][0]['Result']
 
248
        for e in expected:
 
249
            found = False
 
250
            for k in keys:
 
251
                if e in k:
 
252
                    found = True
 
253
                    break
 
254
            if not present:
 
255
                if found:
 
256
                    raise AssertionError("%s not found in %s" % (e, keys))
 
257
                return
 
258
            if not found:
 
259
                raise AssertionError("%s not found in %s" % (e, keys))
 
260
 
 
261
    @pytest.mark.skipif(True, reason="not implemented")
 
262
    def test_key_manager(self):
 
263
        self.verify_keys(['juju-client-key', 'juju-system-key'])
 
264
        self.assertEqual(
 
265
            self.key.import_keys('admin', ['hazmat']),
 
266
            {u'Results': [{u'Error': None}]})
 
267
        self.verify_keys(['ssh-import-id lp:hazmat'])
 
268
        self.key.delete(
 
269
            'admin',
 
270
            'kapil@objectrealms-laptop.local # ssh-import-id lp:hazmat')
 
271
        self.verify_keys(['ssh-import-id lp:hazmat'], present=False)
 
272
 
 
273
 
 
274
class BackupTest(unittest.TestCase):
 
275
    def setUp(self):
 
276
        self.env = Environment.connect(ENV_NAME)
 
277
        self.bm = Backups(self.env)
 
278
 
 
279
    def tearDown(self):
 
280
        self.env.close()
 
281
 
 
282
    @pytest.mark.skipif(True, reason="broken cleanup")
 
283
    def test_backups(self):
 
284
        assert self.bm.list()['List'] == []
 
285
        info = self.bm.create('abc')
 
286
        assert len(self.bm.list()['List']) == 2
 
287
        assert self.bm.info(info['ID'])['Notes'] == 'abc'
 
288
        self.bm.remove(info['ID'])
 
289
        assert len(self.bm.list()['List']) == []
 
290
 
 
291
 
 
292
class UserManagerTest(unittest.TestCase):
 
293
 
 
294
    def setUp(self):
 
295
        self.env = Environment.connect(ENV_NAME)
 
296
        self.um = UserManager(self.env)
 
297
 
 
298
    def tearDown(self):
 
299
        self.env.close()
 
300
 
 
301
    def assert_user(self, user):
 
302
        result = self.um.info(user['username'])
 
303
        result = result['results'][0]['result']
 
304
        result.pop('date-created')
 
305
        self.assertEqual(result, user)
 
306
 
 
307
    @pytest.mark.skipif(True, reason="broken cleanup")
 
308
    def test_user_manager(self):
 
309
        result = self.um.add(
 
310
            {'username': 'magicmike', 'display-name': 'zerocool',
 
311
             'password': 'guess'})
 
312
        assert result == {'results': [{'tag': 'user-magicmike@local'}]}
 
313
        self.assert_user({
 
314
            'username': 'magicmike',
 
315
            'disabled': False,
 
316
            'display-name': 'zerocool',
 
317
            'created-by': 'admin@local'})
 
318
        self.um.disable('mike')
 
319
        self.assert_user({
 
320
            'username': 'magicmike',
 
321
            'disabled': True,
 
322
            'display-name': 'zerocool',
 
323
            'created-by': 'admin@local'})
 
324
        self.um.enable('mike')
 
325
        self.assert_user({
 
326
            'username': 'magicmike',
 
327
            'disabled': False,
 
328
            'display-name': 'zerocool',
 
329
            'created-by': 'admin@local'})
 
330
        self.assertEqual(
 
331
            self.um.set_password({'username': 'mike', 'password': 'iforgot'}),
 
332
            {u'Results': [{u'Error': None}]})
 
333
        self.um.disable('mike')
 
334
 
 
335
 
 
336
class CharmBase(object):
 
337
 
 
338
    _repo_dir = None
 
339
 
 
340
    @property
 
341
    def repo_dir(self):
 
342
        if not self._repo_dir:
 
343
            self._repo_dir = self.mkdir()
 
344
        return self._repo_dir
 
345
 
 
346
    def mkdir(self):
 
347
        d = tempfile.mkdtemp()
 
348
        self.addCleanup(shutil.rmtree, d)
 
349
        return d
 
350
 
 
351
    def write_local_charm(self, md, config=None, actions=None):
 
352
        charm_dir = os.path.join(self.repo_dir, md['series'], md['name'])
 
353
        if not os.path.exists(charm_dir):
 
354
            os.makedirs(charm_dir)
 
355
        md_path = os.path.join(charm_dir, 'metadata.yaml')
 
356
        with open(md_path, 'w') as fh:
 
357
            md.pop('series', None)
 
358
            fh.write(yaml.safe_dump(md))
 
359
 
 
360
        if config is not None:
 
361
            cfg_path = os.path.join(charm_dir, 'config.yaml')
 
362
            with open(cfg_path, 'w') as fh:
 
363
                fh.write(yaml.safe_dump(config))
 
364
 
 
365
        if actions is not None:
 
366
            act_path = os.path.join(charm_dir, 'actions.yaml')
 
367
            with open(act_path, 'w') as fh:
 
368
                fh.write(yaml.safe_dump(actions))
 
369
 
 
370
        with open(os.path.join(charm_dir, 'revision'), 'w') as fh:
 
371
            fh.write('1')
 
372
 
 
373
 
 
374
class ActionTest(unittest.TestCase, CharmBase):
 
375
 
 
376
    def setUp(self):
 
377
        self.env = Environment.connect(ENV_NAME)
 
378
        self.actions = Actions(self.env)
 
379
        self.setupCharm()
 
380
 
 
381
    def tearDown(self):
 
382
        self.env.close()
 
383
 
 
384
    def setupCharm(self):
 
385
        actions = {
 
386
            'deepsix': {
 
387
                'description': 'does something with six',
 
388
                'params': {
 
389
                    'optiona': {
 
390
                        'type': 'string',
 
391
                        'default': 'xyz'}}}}
 
392
        self.write_local_charm({
 
393
            'name': 'mysql',
 
394
            'summary': 'its a db',
 
395
            'description': 'for storing things',
 
396
            'series': 'trusty',
 
397
            'provides': {
 
398
                'db': {
 
399
                    'interface': 'mysql'}}}, actions=actions)
 
400
 
 
401
    @pytest.mark.skipif(True, reason="broken test call to api")
 
402
    def test_actions(self):
 
403
        result = self.env.add_local_charm_dir(
 
404
            os.path.join(self.repo_dir, 'trusty', 'mysql'),
 
405
            'trusty')
 
406
        charm_url = result['CharmURL']
 
407
        self.env.deploy('action-db', charm_url)
 
408
        actions = self.actions.service_actions('action-db')
 
409
        self.assertEqual(
 
410
            actions,
 
411
            {u'results': [
 
412
                {u'servicetag': u'service-action-db',
 
413
                 u'actions': {u'ActionSpecs':
 
414
                              {u'deepsix': {
 
415
                                  u'Params': {u'title': u'deepsix',
 
416
                                              u'type': u'object',
 
417
                                              u'description':
 
418
                                              u'does something with six',
 
419
                                              u'properties': {
 
420
                                                  u'optiona': {
 
421
                                                      u'default': u'xyz',
 
422
                                                      u'type': u'string'}}},
 
423
                                  u'Description': u'does something with six'
 
424
                              }}}}]})
 
425
        result = self.actions.enqueue_units(
 
426
            'action-db/0', 'deepsix', {'optiona': 'bez'})
 
427
        self.assertEqual(result, [])
 
428
 
 
429
 
 
430
class CharmTest(unittest.TestCase):
 
431
 
 
432
    def setUp(self):
 
433
        self.env = Environment.connect(ENV_NAME)
 
434
        self.charms = Charms(self.env)
 
435
 
 
436
    def tearDown(self):
 
437
        self.env.close()
 
438
 
 
439
    def test_charm(self):
 
440
        self.charms.list()
 
441
        self.env.add_charm('cs:~hazmat/trusty/etcd-6')
 
442
        self.charms.list()
 
443
        self.charms.info('cs:~hazmat/trusty/etcd-6')
 
444
 
 
445
 
 
446
class HATest(unittest.TestCase):
 
447
 
 
448
    def setUp(self):
 
449
        self.env = Environment.connect(ENV_NAME)
 
450
        self.ha = HA(self.env)
 
451
 
 
452
    def tearDown(self):
 
453
        self.env.close()
 
454
 
 
455
    @pytest.mark.skipif(True, reason="incomplete implementation issue")
 
456
    def test_ha(self):
 
457
        previous = self.env.status()
 
458
        self.ha.ensure_availability(3)
 
459
        current = self.env.status()
 
460
        self.assertNotEqual(previous, current)
 
461
 
 
462
 
 
463
class AnnotationTest(unittest.TestCase):
 
464
 
 
465
    def setUp(self):
 
466
        self.env = Environment.connect(ENV_NAME)
 
467
        self.charms = Annotations(self.env)
 
468
 
 
469
    def tearDown(self):
 
470
        self.env.close()
 
471
 
 
472
 
 
473
class ClientTest(unittest.TestCase):
 
474
 
 
475
    def setUp(self):
 
476
        self.env = Environment.connect(ENV_NAME)
 
477
 
 
478
    def tearDown(self):
 
479
        reset(self.env)
 
480
        self.env = None
 
481
 
 
482
    def destroy_service(self, svc):
 
483
        self.env.destroy_service(svc)
 
484
        while True:
 
485
            if svc not in self.env.status().get('Services', {}):
 
486
                break
 
487
 
 
488
    def assert_service(self, svc_name, num_units=None):
 
489
        status = self.env.status()
 
490
        services = status.get('Services', {})
 
491
        self.assertTrue(
 
492
            svc_name in services,
 
493
            "Service {} does not exist".format(svc_name)
 
494
        )
 
495
        if num_units is not None:
 
496
            count = len(services[svc_name]['Units'])
 
497
            self.assertTrue(
 
498
                count == num_units,
 
499
                "Service {} has {} units, expected {}".format(
 
500
                    svc_name, count, num_units)
 
501
            )
 
502
 
 
503
    def assert_not_service(self, svc_name):
 
504
        status = self.env.status()
 
505
        services = status.get('Services', {})
 
506
        if svc_name in services:
 
507
            self.assertTrue(
 
508
                services[svc_name]['Life'] in ('dying', 'dead'))
 
509
 
 
510
    def test_juju_info(self):
 
511
        info_keys = list(sorted(self.env.info().keys()))
 
512
        control = [
 
513
            'DefaultSeries', 'Name', 'ProviderType', 'ServerUUID', 'UUID']
 
514
        assert info_keys == control
 
515
 
 
516
    def test_add_get_charm(self):
 
517
        self.env.add_charm('cs:~hazmat/trusty/etcd-6')
 
518
        charm = self.env.get_charm(
 
519
            'cs:~hazmat/trusty/etcd-6')
 
520
        assert charm['URL'] == 'cs:~hazmat/trusty/etcd-6'
 
521
 
 
522
    def test_add_local_charm(self):
 
523
        with tempfile.NamedTemporaryFile() as f:
 
524
            self.assertRaises(
 
525
                EnvError, self.env.add_local_charm, f.name, 'trusty')
 
526
 
 
527
    def test_deploy_and_destroy(self):
 
528
        self.assert_not_service('db')
 
529
        self.env.deploy('db', 'cs:trusty/mysql-1')
 
530
        self.assert_service('db')
 
531
        self.destroy_service('db')
 
532
        self.assert_not_service('db')
 
533
 
 
534
    def xtest_expose_unexpose(self):
 
535
        pass
 
536
 
 
537
    def test_add_remove_units(self):
 
538
        self.assert_not_service('db')
 
539
        machine_1 = self.env.add_machine(series="trusty")['Machine']
 
540
        machine_2 = self.env.add_machine(series="trusty")['Machine']
 
541
        self.env.deploy('db', 'cs:trusty/mysql-1', machine_spec=machine_1)
 
542
        self.env.add_unit('db', machine_spec=machine_2)
 
543
        self.assert_service('db', num_units=2)
 
544
        services = self.env.status().get('Services', {})
 
545
        # Remove the first unit
 
546
        remove_unit = list(services['db']['Units'].keys())[0]
 
547
        self.env.remove_units([remove_unit])
 
548
        self.assert_service('db', num_units=1)
 
549
        self.destroy_service('db')
 
550
        self.assert_not_service('db')
 
551
 
 
552
    def test_deploy_and_add_unit_lxc(self):
 
553
        self.assert_not_service('db')
 
554
        machine = self.env.add_machine(series="trusty")['Machine']
 
555
        self.env.deploy('db', 'cs:trusty/mysql-1', machine_spec=machine)
 
556
        self.env.add_unit('db', machine_spec='lxc:{}'.format(machine))
 
557
        self.assert_service('db', num_units=2)
 
558
        self.destroy_service('db')
 
559
        self.assert_not_service('db')
 
560
 
 
561
    def xtest_get_set_config(self):
 
562
        pass
 
563
 
 
564
    def test_get_set_constraints(self):
 
565
        self.assert_not_service('db')
 
566
        in_constraints = {'cpu-cores': '2'}
 
567
        self.env.deploy('db', 'cs:trusty/mysql-1', constraints=in_constraints)
 
568
        self.assert_service('db')
 
569
        out_constraints = self.env.get_constraints('db')
 
570
        self.assertEqual(in_constraints, out_constraints)
 
571
        self.destroy_service('db')
 
572
        self.assert_not_service('db')
 
573
 
 
574
    def test_get_set_annotations(self):
 
575
        machine = self.env.add_machine(series="trusty")['Machine']
 
576
        in_annotation = {'foo': 'bar'}
 
577
        self.env.set_annotation(machine, 'machine', in_annotation)
 
578
        out_annotation = self.env.get_annotation(machine, 'machine')
 
579
        self.assertEqual(in_annotation, out_annotation['Annotations'])
 
580
 
 
581
    def xtest_add_remove_relation(self):
 
582
        pass
 
583
 
 
584
    def xtest_status(self):
 
585
        pass
 
586
 
 
587
    def xtest_info(self):
 
588
        pass
 
589
 
 
590
    def test_deploy_and_destroy_placement_machine(self):
 
591
        self.assert_not_service('db')
 
592
        machine = self.env.add_machine(series="trusty")['Machine']
 
593
        self.env.deploy('db', 'cs:trusty/mysql-1', machine_spec=machine)
 
594
        self.assert_service('db')
 
595
        self.destroy_service('db')
 
596
        self.assert_not_service('db')
 
597
 
 
598
    def test_deploy_and_destroy_placement_lxc(self):
 
599
        self.assert_not_service('db')
 
600
        machine = self.env.add_machine(series="trusty")['Machine']
 
601
        machine_spec = 'lxc:{}'.format(machine)
 
602
        self.env.deploy('db', 'cs:trusty/mysql-1', machine_spec=machine_spec)
 
603
        self.assert_service('db')
 
604
        self.destroy_service('db')
 
605
        self.assert_not_service('db')
 
606
 
 
607
 
 
608
class TestEnvironment(unittest.TestCase):
 
609
 
 
610
    def setUp(self):
 
611
        self.env = Environment.connect(ENV_NAME)
 
612
 
 
613
    def test_make_headers(self):
 
614
        headers = self.env._make_headers()
 
615
        self.assertTrue('Authorization' in headers)
 
616
 
 
617
    def test_run_no_target(self):
 
618
        self.assertRaises(AssertionError, self.env.run, "sudo test")
 
619
 
 
620
    def test_run_target_machines(self):
 
621
        with mock.patch.object(self.env, '_rpc',
 
622
                               return_value=None) as rpc:
 
623
            self.env.run("sudo test", machines=["0", "1"])
 
624
 
 
625
            rpc.assert_called_once_with({
 
626
                "Type": "Client",
 
627
                "Version": 0,
 
628
                "Request": "Run",
 
629
                "Params": {
 
630
                    "Commands": "sudo test",
 
631
                    "Timeout": None,
 
632
                    "Machines": [
 
633
                        '0',
 
634
                        '1',
 
635
                    ]
 
636
                }
 
637
            })
 
638
 
 
639
    def test_run_target_services(self):
 
640
        with mock.patch.object(self.env, '_rpc',
 
641
                               return_value=None) as rpc:
 
642
            self.env.run("sudo test", services=["cinder", "glance"])
 
643
 
 
644
            rpc.assert_called_once_with({
 
645
                "Type": "Client",
 
646
                "Version": 0,
 
647
                "Request": "Run",
 
648
                "Params": {
 
649
                    "Commands": "sudo test",
 
650
                    "Timeout": None,
 
651
                    "Services": [
 
652
                        'cinder',
 
653
                        'glance',
 
654
                    ]
 
655
                }
 
656
            })
 
657
 
 
658
    def test_run_target_units(self):
 
659
        with mock.patch.object(self.env, '_rpc',
 
660
                               return_value=None) as rpc:
 
661
            self.env.run("sudo test", units=["mysql/0", "mysql/1"])
 
662
 
 
663
            rpc.assert_called_once_with({
 
664
                "Type": "Client",
 
665
                "Version": 0,
 
666
                "Request": "Run",
 
667
                "Params": {
 
668
                    "Commands": "sudo test",
 
669
                    "Timeout": None,
 
670
                    "Units": [
 
671
                        'mysql/0',
 
672
                        'mysql/1',
 
673
                    ]
 
674
                }
 
675
            })
 
676
 
 
677
 
 
678
if __name__ == '__main__':
 
679
    unittest.main()