]>
git.proxmox.com Git - ceph.git/blob - ceph/src/python-common/ceph/tests/test_service_spec.py
9 from ceph
.deployment
.service_spec
import HostPlacementSpec
, PlacementSpec
, \
10 ServiceSpec
, RGWSpec
, NFSServiceSpec
, IscsiServiceSpec
, AlertManagerSpec
, \
11 CustomContainerSpec
, GrafanaSpec
, PrometheusSpec
12 from ceph
.deployment
.drive_group
import DriveGroupSpec
13 from ceph
.deployment
.hostspec
import SpecValidationError
16 @pytest.mark
.parametrize("test_input,expected, require_network",
17 [("myhost", ('myhost', '', ''), False),
18 ("myhost=sname", ('myhost', '', 'sname'), False),
19 ("myhost:10.1.1.10", ('myhost', '10.1.1.10', ''), True),
20 ("myhost:10.1.1.10=sname", ('myhost', '10.1.1.10', 'sname'), True),
21 ("myhost:10.1.1.0/32", ('myhost', '10.1.1.0/32', ''), True),
22 ("myhost:10.1.1.0/32=sname", ('myhost', '10.1.1.0/32', 'sname'), True),
23 ("myhost:[v1:10.1.1.10:6789]", ('myhost', '[v1:10.1.1.10:6789]', ''), True),
24 ("myhost:[v1:10.1.1.10:6789]=sname", ('myhost', '[v1:10.1.1.10:6789]', 'sname'), True),
25 ("myhost:[v1:10.1.1.10:6789,v2:10.1.1.11:3000]", ('myhost', '[v1:10.1.1.10:6789,v2:10.1.1.11:3000]', ''), True),
26 ("myhost:[v1:10.1.1.10:6789,v2:10.1.1.11:3000]=sname", ('myhost', '[v1:10.1.1.10:6789,v2:10.1.1.11:3000]', 'sname'), True),
28 def test_parse_host_placement_specs(test_input
, expected
, require_network
):
29 ret
= HostPlacementSpec
.parse(test_input
, require_network
=require_network
)
30 assert ret
== expected
31 assert str(ret
) == test_input
33 ps
= PlacementSpec
.from_string(test_input
)
34 assert ps
.pretty_str() == test_input
35 assert ps
== PlacementSpec
.from_string(ps
.pretty_str())
37 # Testing the old verbose way of generating json. Don't remove:
38 assert ret
== HostPlacementSpec
.from_json({
39 'hostname': ret
.hostname
,
40 'network': ret
.network
,
44 assert ret
== HostPlacementSpec
.from_json(ret
.to_json())
47 @pytest.mark
.parametrize(
48 "spec, raise_exception, msg",
50 (GrafanaSpec(protocol
=''), True, '^Invalid protocol'),
51 (GrafanaSpec(protocol
='invalid'), True, '^Invalid protocol'),
52 (GrafanaSpec(protocol
='-http'), True, '^Invalid protocol'),
53 (GrafanaSpec(protocol
='-https'), True, '^Invalid protocol'),
54 (GrafanaSpec(protocol
='http'), False, ''),
55 (GrafanaSpec(protocol
='https'), False, ''),
56 (GrafanaSpec(anonymous_access
=False), True, '^Either initial'), # we require inital_admin_password if anonymous_access is False
57 (GrafanaSpec(anonymous_access
=False, initial_admin_password
='test'), False, ''),
59 def test_apply_grafana(spec
: GrafanaSpec
, raise_exception
: bool, msg
: str):
61 with pytest
.raises(SpecValidationError
, match
=msg
):
66 @pytest.mark
.parametrize(
67 "spec, raise_exception, msg",
69 # Valid retention_time values (valid units: 'y', 'w', 'd', 'h', 'm', 's')
70 (PrometheusSpec(retention_time
='1y'), False, ''),
71 (PrometheusSpec(retention_time
=' 10w '), False, ''),
72 (PrometheusSpec(retention_time
=' 1348d'), False, ''),
73 (PrometheusSpec(retention_time
='2000h '), False, ''),
74 (PrometheusSpec(retention_time
='173847m'), False, ''),
75 (PrometheusSpec(retention_time
='200s'), False, ''),
76 (PrometheusSpec(retention_time
=' '), False, ''), # default value will be used
77 # Invalid retention_time values
78 (PrometheusSpec(retention_time
='100k'), True, '^Invalid retention time'), # invalid unit
79 (PrometheusSpec(retention_time
='10'), True, '^Invalid retention time'), # no unit
80 (PrometheusSpec(retention_time
='100.00y'), True, '^Invalid retention time'), # invalid value and valid unit
81 (PrometheusSpec(retention_time
='100.00k'), True, '^Invalid retention time'), # invalid value and invalid unit
82 (PrometheusSpec(retention_time
='---'), True, '^Invalid retention time'), # invalid value
84 # Valid retention_size values (valid units: 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')
85 (PrometheusSpec(retention_size
='123456789B'), False, ''),
86 (PrometheusSpec(retention_size
=' 200KB'), False, ''),
87 (PrometheusSpec(retention_size
='99999MB '), False, ''),
88 (PrometheusSpec(retention_size
=' 10GB '), False, ''),
89 (PrometheusSpec(retention_size
='100TB'), False, ''),
90 (PrometheusSpec(retention_size
='500PB'), False, ''),
91 (PrometheusSpec(retention_size
='200EB'), False, ''),
92 (PrometheusSpec(retention_size
=' '), False, ''), # default value will be used
94 # Invalid retention_size values
95 (PrometheusSpec(retention_size
='100b'), True, '^Invalid retention size'), # invalid unit (case sensitive)
96 (PrometheusSpec(retention_size
='333kb'), True, '^Invalid retention size'), # invalid unit (case sensitive)
97 (PrometheusSpec(retention_size
='2000'), True, '^Invalid retention size'), # no unit
98 (PrometheusSpec(retention_size
='200.00PB'), True, '^Invalid retention size'), # invalid value and valid unit
99 (PrometheusSpec(retention_size
='400.B'), True, '^Invalid retention size'), # invalid value and invalid unit
100 (PrometheusSpec(retention_size
='10.000s'), True, '^Invalid retention size'), # invalid value and invalid unit
101 (PrometheusSpec(retention_size
='...'), True, '^Invalid retention size'), # invalid value
103 # valid retention_size and valid retention_time
104 (PrometheusSpec(retention_time
='1y', retention_size
='100GB'), False, ''),
105 # invalid retention_time and valid retention_size
106 (PrometheusSpec(retention_time
='1j', retention_size
='100GB'), True, '^Invalid retention time'),
107 # valid retention_time and invalid retention_size
108 (PrometheusSpec(retention_time
='1y', retention_size
='100gb'), True, '^Invalid retention size'),
109 # valid retention_time and invalid retention_size
110 (PrometheusSpec(retention_time
='1y', retention_size
='100gb'), True, '^Invalid retention size'),
111 # invalid retention_time and invalid retention_size
112 (PrometheusSpec(retention_time
='1i', retention_size
='100gb'), True, '^Invalid retention time'),
114 def test_apply_prometheus(spec
: PrometheusSpec
, raise_exception
: bool, msg
: str):
116 with pytest
.raises(SpecValidationError
, match
=msg
):
121 @pytest.mark
.parametrize(
122 "test_input,expected",
124 ('', "PlacementSpec()"),
125 ("count:2", "PlacementSpec(count=2)"),
126 ("3", "PlacementSpec(count=3)"),
127 ("host1 host2", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='')])"),
128 ("host1;host2", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='')])"),
129 ("host1,host2", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='')])"),
130 ("host1 host2=b", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='b')])"),
131 ("host1=a host2=b", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='', name='a'), HostPlacementSpec(hostname='host2', network='', name='b')])"),
132 ("host1:1.2.3.4=a host2:1.2.3.5=b", "PlacementSpec(hosts=[HostPlacementSpec(hostname='host1', network='1.2.3.4', name='a'), HostPlacementSpec(hostname='host2', network='1.2.3.5', name='b')])"),
133 ("myhost:[v1:10.1.1.10:6789]", "PlacementSpec(hosts=[HostPlacementSpec(hostname='myhost', network='[v1:10.1.1.10:6789]', name='')])"),
134 ('2 host1 host2', "PlacementSpec(count=2, hosts=[HostPlacementSpec(hostname='host1', network='', name=''), HostPlacementSpec(hostname='host2', network='', name='')])"),
135 ('label:foo', "PlacementSpec(label='foo')"),
136 ('3 label:foo', "PlacementSpec(count=3, label='foo')"),
137 ('*', "PlacementSpec(host_pattern='*')"),
138 ('3 data[1-3]', "PlacementSpec(count=3, host_pattern='data[1-3]')"),
139 ('3 data?', "PlacementSpec(count=3, host_pattern='data?')"),
140 ('3 data*', "PlacementSpec(count=3, host_pattern='data*')"),
141 ("count-per-host:4 label:foo", "PlacementSpec(count_per_host=4, label='foo')"),
143 def test_parse_placement_specs(test_input
, expected
):
144 ret
= PlacementSpec
.from_string(test_input
)
145 assert str(ret
) == expected
146 assert PlacementSpec
.from_string(ret
.pretty_str()) == ret
, f
'"{ret.pretty_str()}" != "{test_input}"'
148 @pytest.mark
.parametrize(
152 ("host=a label:wrong"),
154 ('host=a count-per-host:0'),
155 ('host=a count-per-host:-10'),
156 ('count:2 count-per-host:1'),
157 ('host1=a host2=b count-per-host:2'),
158 ('host1:10/8 count-per-host:2'),
159 ('count-per-host:2'),
162 def test_parse_placement_specs_raises(test_input
):
163 with pytest
.raises(SpecValidationError
):
164 PlacementSpec
.from_string(test_input
)
166 @pytest.mark
.parametrize("test_input",
168 [("myhost:1.1.1.1/24"),
172 def test_parse_host_placement_specs_raises_wrong_format(test_input
):
173 with pytest
.raises(ValueError):
174 HostPlacementSpec
.parse(test_input
)
177 @pytest.mark
.parametrize(
181 PlacementSpec(count
=3),
182 ['host1', 'host2', 'host3', 'host4', 'host5'],
186 PlacementSpec(host_pattern
='*'),
187 ['host1', 'host2', 'host3', 'host4', 'host5'],
191 PlacementSpec(count_per_host
=2, host_pattern
='*'),
192 ['host1', 'host2', 'host3', 'host4', 'host5'],
196 PlacementSpec(host_pattern
='foo*'),
197 ['foo1', 'foo2', 'bar1', 'bar2'],
201 PlacementSpec(count_per_host
=2, host_pattern
='foo*'),
202 ['foo1', 'foo2', 'bar1', 'bar2'],
206 def test_placement_target_size(p
, hosts
, size
):
207 assert p
.get_target_count(
208 [HostPlacementSpec(n
, '', '') for n
in hosts
]
212 def _get_dict_spec(s_type
, s_id
):
215 "service_type": s_type
,
217 dict(hosts
=["host1:1.1.1.1"])
221 elif s_type
== 'iscsi':
222 dict_spec
['pool'] = 'pool'
223 dict_spec
['api_user'] = 'api_user'
224 dict_spec
['api_password'] = 'api_password'
225 elif s_type
== 'osd':
226 dict_spec
['spec'] = {
231 elif s_type
== 'rgw':
232 dict_spec
['rgw_realm'] = 'realm'
233 dict_spec
['rgw_zone'] = 'zone'
238 @pytest.mark
.parametrize(
239 "s_type,o_spec,s_id",
241 ("mgr", ServiceSpec
, 'test'),
242 ("mon", ServiceSpec
, 'test'),
243 ("mds", ServiceSpec
, 'test'),
244 ("rgw", RGWSpec
, 'realm.zone'),
245 ("nfs", NFSServiceSpec
, 'test'),
246 ("iscsi", IscsiServiceSpec
, 'test'),
247 ("osd", DriveGroupSpec
, 'test'),
249 def test_servicespec_map_test(s_type
, o_spec
, s_id
):
250 spec
= ServiceSpec
.from_json(_get_dict_spec(s_type
, s_id
))
251 assert isinstance(spec
, o_spec
)
252 assert isinstance(spec
.placement
, PlacementSpec
)
253 assert isinstance(spec
.placement
.hosts
[0], HostPlacementSpec
)
254 assert spec
.placement
.hosts
[0].hostname
== 'host1'
255 assert spec
.placement
.hosts
[0].network
== '1.1.1.1'
256 assert spec
.placement
.hosts
[0].name
== ''
257 assert spec
.validate() is None
258 ServiceSpec
.from_json(spec
.to_json())
261 @pytest.mark
.parametrize(
262 "realm, zone, frontend_type, raise_exception, msg",
264 ('realm', 'zone1', 'beast', False, ''),
265 ('realm', 'zone2', 'civetweb', False, ''),
266 ('realm', None, 'beast', True, 'Cannot add RGW: Realm specified but no zone specified'),
267 (None, 'zone1', 'beast', True, 'Cannot add RGW: Zone specified but no realm specified'),
268 ('realm', 'zone', 'invalid-beast', True, '^Invalid rgw_frontend_type value'),
269 ('realm', 'zone', 'invalid-civetweb', True, '^Invalid rgw_frontend_type value'),
271 def test_rgw_servicespec_parse(realm
, zone
, frontend_type
, raise_exception
, msg
):
272 spec
= RGWSpec(service_id
='foo',
275 rgw_frontend_type
=frontend_type
)
277 with pytest
.raises(SpecValidationError
, match
=msg
):
282 def test_osd_unmanaged():
283 osd_spec
= {"placement": {"host_pattern": "*"},
284 "service_id": "all-available-devices",
285 "service_name": "osd.all-available-devices",
286 "service_type": "osd",
287 "spec": {"data_devices": {"all": True}, "filter_logic": "AND", "objectstore": "bluestore"},
290 dg_spec
= ServiceSpec
.from_json(osd_spec
)
291 assert dg_spec
.unmanaged
== True
294 @pytest.mark
.parametrize("y",
295 """service_type: crash
307 service_id: default-rgw-realm.eu-central-1.1
308 service_name: rgw.default-rgw-realm.eu-central-1.1
316 rgw_frontend_type: civetweb
317 rgw_realm: default-rgw-realm
318 rgw_zone: eu-central-1
321 service_id: osd_spec_default
322 service_name: osd.osd_spec_default
331 objectstore: bluestore
335 service_type: alertmanager
336 service_name: alertmanager
340 default_webhook_urls:
343 service_type: grafana
344 service_name: grafana
346 anonymous_access: true
350 service_type: grafana
351 service_name: grafana
353 anonymous_access: true
354 initial_admin_password: secure
358 service_type: ingress
360 service_name: ingress.rgw.foo
367 backend_service: rgw.foo
370 virtual_ip: 192.168.20.1/24
374 service_name: nfs.mynfs
380 service_name: iscsi.iscsi
392 service_type: container
393 service_id: hello-world
394 service_name: container.hello-world
401 - destination=/lib/modules
406 entrypoint: /usr/bin/bash
417 image: docker.io/library/hello-world:latest
425 service_type: snmp-gateway
426 service_name: snmp-gateway
431 snmp_community: public
432 snmp_destination: 192.168.1.42:162
435 service_type: snmp-gateway
436 service_name: snmp-gateway
442 snmp_v3_auth_password: mypassword
443 snmp_v3_auth_username: myuser
444 engine_id: 8000C53F00000000
446 snmp_destination: 192.168.1.42:162
449 service_type: snmp-gateway
450 service_name: snmp-gateway
455 snmp_v3_auth_password: mypassword
456 snmp_v3_auth_username: myuser
457 snmp_v3_priv_password: mysecret
458 engine_id: 8000C53F00000000
459 privacy_protocol: AES
460 snmp_destination: 192.168.1.42:162
464 data
= yaml
.safe_load(y
)
465 object = ServiceSpec
.from_json(data
)
467 assert yaml
.dump(object) == y
468 assert yaml
.dump(ServiceSpec
.from_json(object.to_json())) == y
471 def test_alertmanager_spec_1():
472 spec
= AlertManagerSpec()
473 assert spec
.service_type
== 'alertmanager'
474 assert isinstance(spec
.user_data
, dict)
475 assert len(spec
.user_data
.keys()) == 0
476 assert spec
.get_port_start() == [9093, 9094]
479 def test_alertmanager_spec_2():
480 spec
= AlertManagerSpec(user_data
={'default_webhook_urls': ['foo']})
481 assert isinstance(spec
.user_data
, dict)
482 assert 'default_webhook_urls' in spec
.user_data
.keys()
487 val
= """ServiceSpec.from_json(yaml.safe_load('''service_type: crash
493 assert obj
.service_type
== 'crash'
494 assert val
== repr(obj
)
496 @pytest.mark
.parametrize("spec1, spec2, eq",
517 # Add service_type='mgr'
549 RGWSpec(service_id
='foo'),
553 def test_spec_hash_eq(spec1
: ServiceSpec
,
557 assert (spec1
== spec2
) is eq
559 @pytest.mark
.parametrize(
560 "s_type,s_id,s_name",
562 ('mgr', 's_id', 'mgr'),
563 ('mon', 's_id', 'mon'),
564 ('mds', 's_id', 'mds.s_id'),
565 ('rgw', 's_id', 'rgw.s_id'),
566 ('nfs', 's_id', 'nfs.s_id'),
567 ('iscsi', 's_id', 'iscsi.s_id'),
568 ('osd', 's_id', 'osd.s_id'),
570 def test_service_name(s_type
, s_id
, s_name
):
571 spec
= ServiceSpec
.from_json(_get_dict_spec(s_type
, s_id
))
573 assert spec
.service_name() == s_name
575 @pytest.mark
.parametrize(
578 ('mds', 's:id'), # MDS service_id cannot contain an invalid char ':'
579 ('mds', '1abc'), # MDS service_id cannot start with a numeric digit
580 ('mds', ''), # MDS service_id cannot be empty
587 def test_service_id_raises_invalid_char(s_type
, s_id
):
588 with pytest
.raises(SpecValidationError
):
589 spec
= ServiceSpec
.from_json(_get_dict_spec(s_type
, s_id
))
592 def test_custom_container_spec():
593 spec
= CustomContainerSpec(service_id
='hello-world',
594 image
='docker.io/library/hello-world:latest',
595 entrypoint
='/usr/bin/bash',
598 volume_mounts
={'foo': '/foo'},
604 'source=lib/modules',
605 'destination=/lib/modules',
612 'foo.conf': 'foo\nbar',
613 'bar.conf': ['foo', 'bar']
615 assert spec
.service_type
== 'container'
616 assert spec
.entrypoint
== '/usr/bin/bash'
617 assert spec
.uid
== 1000
618 assert spec
.gid
== 2000
619 assert spec
.volume_mounts
== {'foo': '/foo'}
620 assert spec
.args
== ['--foo']
621 assert spec
.envs
== ['FOO=0815']
622 assert spec
.bind_mounts
== [
625 'source=lib/modules',
626 'destination=/lib/modules',
630 assert spec
.ports
== [8080, 8443]
631 assert spec
.dirs
== ['foo', 'bar']
632 assert spec
.files
== {
633 'foo.conf': 'foo\nbar',
634 'bar.conf': ['foo', 'bar']
638 def test_custom_container_spec_config_json():
639 spec
= CustomContainerSpec(service_id
='foo', image
='foo', dirs
=None)
640 config_json
= spec
.config_json()
641 for key
in ['entrypoint', 'uid', 'gid', 'bind_mounts', 'dirs']:
642 assert key
not in config_json
645 def test_ingress_spec():
646 yaml_str
= """service_type: ingress
654 virtual_ip: 192.168.20.1/24
655 backend_service: rgw.foo
659 yaml_file
= yaml
.safe_load(yaml_str
)
660 spec
= ServiceSpec
.from_json(yaml_file
)
661 assert spec
.service_type
== "ingress"
662 assert spec
.service_id
== "rgw.foo"
663 assert spec
.virtual_ip
== "192.168.20.1/24"
664 assert spec
.frontend_port
== 8080
665 assert spec
.monitor_port
== 8081
668 @pytest.mark
.parametrize("y, error_match", [
673 count_per_host: "twelve"
674 """, "count-per-host must be a numeric value",),
680 """, "count-per-host must be an integer value",),
686 """, "count-per-host must be an integer value",),
692 """, "num/count must be a numeric value",),
698 """, "num/count must be an integer value",),
704 """, "num/count must be an integer value",),
710 """, "num/count must be >= 1",),
716 """, "count-per-host must be >= 1",),
718 service_type: snmp-gateway
719 service_name: snmp-gateway
724 snmp_v3_auth_password: mypassword
725 snmp_v3_auth_username: myuser
726 snmp_v3_priv_password: mysecret
728 engine_id: 8000c53f0000000000
729 privacy_protocol: WEIRD
730 snmp_destination: 192.168.122.1:162
731 auth_protocol: BIZARRE
733 """, "auth_protocol unsupported. Must be one of MD5, SHA"),
736 service_type: snmp-gateway
737 service_name: snmp-gateway
742 snmp_community: public
743 snmp_destination: 192.168.1.42:162
745 """, 'snmp_version unsupported. Must be one of V2c, V3'),
748 service_type: snmp-gateway
749 service_name: snmp-gateway
754 snmp_community: public
756 snmp_destination: 192.168.1.42:162
757 """, re
.escape('Missing SNMP version (snmp_version)')),
760 service_type: snmp-gateway
761 service_name: snmp-gateway
766 snmp_v3_auth_username: myuser
767 snmp_v3_auth_password: mypassword
770 snmp_destination: 192.168.1.42:162
772 """, 'auth_protocol unsupported. Must be one of MD5, SHA'),
775 service_type: snmp-gateway
776 service_name: snmp-gateway
781 snmp_v3_auth_username: myuser
782 snmp_v3_auth_password: mypassword
783 snmp_v3_priv_password: mysecret
786 privacy_protocol: weewah
787 snmp_destination: 192.168.1.42:162
789 """, 'privacy_protocol unsupported. Must be one of DES, AES'),
792 service_type: snmp-gateway
793 service_name: snmp-gateway
798 snmp_v3_auth_username: myuser
799 snmp_v3_auth_password: mypassword
800 snmp_v3_priv_password: mysecret
803 privacy_protocol: AES
804 snmp_destination: 192.168.1.42:162
806 """, 'Must provide an engine_id for SNMP V3 notifications'),
809 service_type: snmp-gateway
810 service_name: snmp-gateway
815 snmp_community: public
817 snmp_destination: 192.168.1.42
819 """, re
.escape('SNMP destination (snmp_destination) type (IPv4) is invalid. Must be either: IPv4:Port, Name:Port')),
822 service_type: snmp-gateway
823 service_name: snmp-gateway
828 snmp_v3_auth_username: myuser
829 snmp_v3_auth_password: mypassword
830 snmp_v3_priv_password: mysecret
833 privacy_protocol: AES
835 snmp_destination: 192.168.1.42:162
837 """, 'engine_id must be a string containing 10-64 hex characters. Its length must be divisible by 2'),
840 service_type: snmp-gateway
841 service_name: snmp-gateway
846 snmp_v3_auth_username: myuser
847 snmp_v3_auth_password: mypassword
850 engine_id: 8000C53F0000000000
852 """, re
.escape('SNMP destination (snmp_destination) must be provided')),
855 service_type: snmp-gateway
856 service_name: snmp-gateway
861 snmp_v3_auth_username: myuser
862 snmp_v3_auth_password: mypassword
863 snmp_v3_priv_password: mysecret
866 privacy_protocol: AES
867 engine_id: 8000C53F0000000000
868 snmp_destination: my.imaginary.snmp-host
870 """, re
.escape('SNMP destination (snmp_destination) is invalid: DNS lookup failed')),
873 service_type: snmp-gateway
874 service_name: snmp-gateway
879 snmp_v3_auth_username: myuser
880 snmp_v3_auth_password: mypassword
881 snmp_v3_priv_password: mysecret
884 privacy_protocol: AES
885 engine_id: 8000C53F0000000000
886 snmp_destination: 10.79.32.10:fred
888 """, re
.escape('SNMP destination (snmp_destination) is invalid: Port must be numeric')),
891 service_type: snmp-gateway
892 service_name: snmp-gateway
897 snmp_v3_auth_username: myuser
898 snmp_v3_auth_password: mypassword
899 snmp_v3_priv_password: mysecret
902 privacy_protocol: AES
904 snmp_destination: 10.79.32.10:162
906 """, 'engine_id must be a string containing 10-64 hex characters. Its length must be divisible by 2'),
909 service_type: snmp-gateway
910 service_name: snmp-gateway
915 snmp_v3_auth_username: myuser
916 snmp_v3_auth_password: mypassword
917 snmp_v3_priv_password: mysecret
920 privacy_protocol: AES
921 engine_id: 8000C53DOH!
922 snmp_destination: 10.79.32.10:162
924 """, 'engine_id must be a string containing 10-64 hex characters. Its length must be divisible by 2'),
927 service_type: snmp-gateway
928 service_name: snmp-gateway
933 snmp_v3_auth_username: myuser
934 snmp_v3_auth_password: mypassword
935 snmp_v3_priv_password: mysecret
938 privacy_protocol: AES
939 engine_id: 8000C53FCA7344403DC611EC9B985254002537A6C53FCA7344403DC6112537A60
940 snmp_destination: 10.79.32.10:162
942 """, 'engine_id must be a string containing 10-64 hex characters. Its length must be divisible by 2'),
945 service_type: snmp-gateway
946 service_name: snmp-gateway
951 snmp_v3_auth_username: myuser
952 snmp_v3_auth_password: mypassword
953 snmp_v3_priv_password: mysecret
956 privacy_protocol: AES
957 engine_id: 8000C53F00000
958 snmp_destination: 10.79.32.10:162
960 """, 'engine_id must be a string containing 10-64 hex characters. Its length must be divisible by 2'),
962 def test_service_spec_validation_error(y
, error_match
):
963 data
= yaml
.safe_load(y
)
964 with pytest
.raises(SpecValidationError
) as err
:
965 specObj
= ServiceSpec
.from_json(data
)
966 assert err
.match(error_match
)