summaryrefslogtreecommitdiff
path: root/tests/unit/test_global_semaphores.py
blob: b76f9d812b96a4ac7f42d62da2f672ffdf477545 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# Copyright 2022 Acme Gating, LLC
#
# 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.

import zuul.configloader

from tests.base import ZuulTestCase


class TestGlobalSemaphoresConfig(ZuulTestCase):
    tenant_config_file = 'config/global-semaphores-config/main.yaml'

    def assertSemaphores(self, tenant, semaphores):
        for k, v in semaphores.items():
            self.assertEqual(
                len(tenant.semaphore_handler.semaphoreHolders(k)),
                v, k)

    def assertSemaphoresMax(self, tenant, semaphores):
        for k, v in semaphores.items():
            abide = tenant.semaphore_handler.abide
            semaphore = tenant.layout.getSemaphore(abide, k)
            self.assertEqual(semaphore.max, v, k)

    def test_semaphore_scope(self):
        # This tests global and tenant semaphore scope
        self.executor_server.hold_jobs_in_build = True
        tenant1 = self.scheds.first.sched.abide.tenants.get('tenant-one')
        tenant2 = self.scheds.first.sched.abide.tenants.get('tenant-two')
        tenant3 = self.scheds.first.sched.abide.tenants.get('tenant-three')

        # The different max values will tell us that we have the right
        # semaphore objects.  Each tenant has one tenant-scope
        # semaphore in a tenant-specific project, and one tenant-scope
        # semaphore with a common definition.  Tenants 1 and 2 share a
        # global-scope semaphore, and tenant 3 has a tenant-scope
        # semaphore with the same name.

        # Here is what is defined in each tenant:
        # Tenant-one:
        #  * global-semaphore:   scope:global max:100 definition:main.yaml
        #  * common-semaphore:   scope:tenant max:10  definition:common-config
        #  * project1-semaphore: scope:tenant max:11  definition:project1
        #  * (global-semaphore): scope:tenant max:2   definition:project1
        #    [unused since it shadows the actual global-semaphore]
        # Tenant-two:
        #  * global-semaphore:   scope:global max:100 definition:main.yaml
        #  * common-semaphore:   scope:tenant max:10  definition:common-config
        #  * project2-semaphore: scope:tenant max:12  definition:project2
        # Tenant-three:
        #  * global-semaphore:   scope:global max:999 definition:project3
        #  * common-semaphore:   scope:tenant max:10  definition:common-config
        #  * project3-semaphore: scope:tenant max:13  definition:project3
        self.assertSemaphoresMax(tenant1, {'global-semaphore': 100,
                                           'common-semaphore': 10,
                                           'project1-semaphore': 11,
                                           'project2-semaphore': 1,
                                           'project3-semaphore': 1})
        self.assertSemaphoresMax(tenant2, {'global-semaphore': 100,
                                           'common-semaphore': 10,
                                           'project1-semaphore': 1,
                                           'project2-semaphore': 12,
                                           'project3-semaphore': 1})
        # This "global" semaphore is really tenant-scoped, it just has
        # the same name.
        self.assertSemaphoresMax(tenant3, {'global-semaphore': 999,
                                           'common-semaphore': 10,
                                           'project1-semaphore': 1,
                                           'project2-semaphore': 1,
                                           'project3-semaphore': 13})

        # We should have a config error in tenant1 due to the
        # redefinition.
        self.assertEquals(len(tenant1.layout.loading_errors), 1)
        self.assertEquals(len(tenant2.layout.loading_errors), 0)
        self.assertEquals(len(tenant3.layout.loading_errors), 0)

        A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
        B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
        C = self.fake_gerrit.addFakeChange('org/project3', 'master', 'C')
        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
        self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
        self.waitUntilSettled()

        # Checking the number of holders tells us whethere we are
        # using global or tenant-scoped semaphores.  Each in-use
        # semaphore in a tenant should have only one holder except the
        # global-scope semaphore shared between tenants 1 and 2.
        self.assertSemaphores(tenant1, {'global-semaphore': 2,
                                        'common-semaphore': 1,
                                        'project1-semaphore': 1,
                                        'project2-semaphore': 0,
                                        'project3-semaphore': 0})
        self.assertSemaphores(tenant2, {'global-semaphore': 2,
                                        'common-semaphore': 1,
                                        'project1-semaphore': 0,
                                        'project2-semaphore': 1,
                                        'project3-semaphore': 0})
        self.assertSemaphores(tenant3, {'global-semaphore': 1,
                                        'common-semaphore': 1,
                                        'project1-semaphore': 0,
                                        'project2-semaphore': 0,
                                        'project3-semaphore': 1})

        self.executor_server.hold_jobs_in_build = False
        self.executor_server.release()
        self.waitUntilSettled()


class TestGlobalSemaphoresBroken(ZuulTestCase):
    validate_tenants = []
    tenant_config_file = 'config/global-semaphores-config/broken.yaml'
    # This test raises a config error during the startup of the test
    # case which makes the first scheduler fail during its startup.
    # The second (or any additional) scheduler won't even run as the
    # startup is serialized in tests/base.py.
    # Thus it doesn't make sense to execute this test with multiple
    # schedulers.
    scheduler_count = 1

    def setUp(self):
        self.assertRaises(zuul.configloader.GlobalSemaphoreNotFoundError,
                          super().setUp)

    def test_broken_global_semaphore_config(self):
        pass


class TestGlobalSemaphores(ZuulTestCase):
    tenant_config_file = 'config/global-semaphores/main.yaml'

    def test_global_semaphores(self):
        # This tests that a job finishing in one tenant will correctly
        # start a job in another tenant waiting on the semahpore.
        self.executor_server.hold_jobs_in_build = True
        A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A')
        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
        self.waitUntilSettled()

        B = self.fake_gerrit.addFakeChange('org/project2', 'master', 'B')
        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
        self.waitUntilSettled()

        self.assertHistory([])
        self.assertBuilds([
            dict(name='test-global-semaphore', changes='1,1'),
        ])

        self.executor_server.hold_jobs_in_build = False
        self.executor_server.release()
        self.waitUntilSettled()

        self.assertHistory([
            dict(name='test-global-semaphore',
                 result='SUCCESS', changes='1,1'),
            dict(name='test-global-semaphore',
                 result='SUCCESS', changes='2,1'),
        ], ordered=False)