/
containers.py
1285 lines (1141 loc) · 55 KB
/
containers.py
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2013 UnitedStack Inc.
# All Rights Reserved.
#
# 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 shlex
from neutronclient.common import exceptions as n_exc
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import uuidutils
import pecan
import six
from zun.api.controllers import base
from zun.api.controllers import link
from zun.api.controllers.v1 import collection
from zun.api.controllers.v1.schemas import containers as schema
from zun.api.controllers.v1.views import containers_view as view
from zun.api import utils as api_utils
from zun.api import validation
from zun.common import consts
from zun.common import context as zun_context
from zun.common import exception
from zun.common.i18n import _
from zun.common import name_generator
from zun.common.policies import container as policies
from zun.common import policy
from zun.common import quota
from zun.common import utils
import zun.conf
from zun.network import model as network_model
from zun.network import neutron
from zun import objects
from zun.pci import request as pci_request
from zun.volume import cinder_api as cinder
CONF = zun.conf.CONF
LOG = logging.getLogger(__name__)
QUOTAS = quota.QUOTAS
def check_policy_on_container(container, action):
context = pecan.request.context
policy.enforce(context, action, container, action=action)
class ContainerCollection(collection.Collection):
"""API representation of a collection of containers."""
fields = {
'containers',
'next'
}
"""A list containing containers objects"""
def __init__(self, **kwargs):
super(ContainerCollection, self).__init__(**kwargs)
self._type = 'containers'
@staticmethod
def convert_with_links(rpc_containers, limit, url=None,
expand=False, **kwargs):
context = pecan.request.context
collection = ContainerCollection()
collection.containers = \
[view.format_container(context, url, p.as_dict())
for p in rpc_containers]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class ContainersActionsController(base.Controller):
"""Controller for Container Actions."""
def __init__(self):
super(ContainersActionsController, self).__init__()
self._action_keys = ['action', 'container_uuid', 'request_id',
'user_id', 'project_id', 'start_time',
'message']
self._event_keys = ['event', 'start_time', 'finish_time', 'result',
'traceback']
def _format_action(self, action_raw):
action = {}
action_dict = action_raw.as_dict()
for key in self._action_keys:
action[key] = action_dict.get(key)
return action
def _format_event(self, event_raw, show_traceback=False):
event = {}
event_dict = event_raw.as_dict()
for key in self._event_keys:
# By default, non-admins are not allowed to see traceback details.
if key == 'traceback' and not show_traceback:
event['traceback'] = None
continue
event[key] = event_dict.get(key)
return event
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_all(self, container_ident, **kwargs):
"""Retrieve a list of container actions."""
context = pecan.request.context
policy.enforce(context, "container:actions",
action="container:actions")
container = utils.get_container(container_ident)
actions_raw = objects.ContainerAction.get_by_container_uuid(
context, container.uuid)
actions = [self._format_action(action) for action in actions_raw]
return {"containerActions": actions}
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_one(self, container_ident, request_ident, **kwargs):
"""Retrieve information about the action."""
context = pecan.request.context
policy.enforce(context, "container:actions",
action="container:actions")
container = utils.get_container(container_ident)
action = objects.ContainerAction.get_by_request_id(
context, container.uuid, request_ident)
if action is None:
raise exception.ResourceNotFound(name="Action", id=request_ident)
action_id = action.id
if CONF.database.backend == 'etcd':
# etcd using action.uuid get the unique action instead of action.id
action_id = action.uuid
action = self._format_action(action)
show_traceback = False
if policy.enforce(context, "container:action:events",
do_raise=False, action="container:action:events"):
show_traceback = True
events_raw = objects.ContainerActionEvent.get_by_action(context,
action_id)
action['events'] = [self._format_event(evt, show_traceback)
for evt in events_raw]
return action
class ContainersController(base.Controller):
"""Controller for Containers."""
_custom_actions = {
'start': ['POST'],
'stop': ['POST'],
'reboot': ['POST'],
'rebuild': ['POST'],
'pause': ['POST'],
'unpause': ['POST'],
'logs': ['GET'],
'execute': ['POST'],
'execute_resize': ['POST'],
'kill': ['POST'],
'rename': ['POST'],
'attach': ['GET'],
'resize': ['POST'],
'resize_container': ['POST'],
'top': ['GET'],
'get_archive': ['GET'],
'put_archive': ['POST'],
'stats': ['GET'],
'commit': ['POST'],
'add_security_group': ['POST'],
'network_detach': ['POST'],
'network_attach': ['POST'],
'network_list': ['GET'],
'remove_security_group': ['POST']
}
container_actions = ContainersActionsController()
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_all(self, **kwargs):
"""Retrieve a list of containers.
"""
context = pecan.request.context
policy.enforce(context, "container:get_all",
action="container:get_all")
return self._get_containers_collection(**kwargs)
def _get_containers_collection(self, **kwargs):
context = pecan.request.context
if utils.is_all_projects(kwargs):
policy.enforce(context, "container:get_all_all_projects",
action="container:get_all_all_projects")
context.all_projects = True
kwargs.pop('all_projects', None)
limit = api_utils.validate_limit(kwargs.pop('limit', None))
sort_dir = api_utils.validate_sort_dir(kwargs.pop('sort_dir', 'asc'))
sort_key = kwargs.pop('sort_key', 'id')
resource_url = kwargs.pop('resource_url', None)
expand = kwargs.pop('expand', None)
container_allowed_filters = ['name', 'image', 'project_id', 'user_id',
'memory', 'host', 'task_state', 'status',
'auto_remove']
filters = {}
for filter_key in container_allowed_filters:
if filter_key in kwargs:
policy_action = policies.CONTAINER % ('get_one:' + filter_key)
context.can(policy_action, might_not_exist=True)
filter_value = kwargs.pop(filter_key)
filters[filter_key] = filter_value
marker_obj = None
marker = kwargs.pop('marker', None)
if marker:
marker_obj = objects.Container.get_by_uuid(context,
marker)
if kwargs:
unknown_params = [str(k) for k in kwargs]
msg = _("Unknown parameters: %s") % ", ".join(unknown_params)
raise exception.InvalidValue(msg)
containers = objects.Container.list(context,
limit,
marker_obj,
sort_key,
sort_dir,
filters=filters)
return ContainerCollection.convert_with_links(containers, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_one(self, container_ident, **kwargs):
"""Retrieve information about the given container.
:param container_ident: UUID or name of a container.
"""
context = pecan.request.context
if utils.is_all_projects(kwargs):
policy.enforce(context, "container:get_one_all_projects",
action="container:get_one_all_projects")
context.all_projects = True
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:get_one")
if container.host:
compute_api = pecan.request.compute_api
try:
container = compute_api.container_show(context, container)
except exception.ContainerHostNotUp:
raise exception.ServerNotUsable
return view.format_container(context, pecan.request.host_url,
container.as_dict())
def _generate_name_for_container(self):
"""Generate a random name like: zeta-22-container."""
name_gen = name_generator.NameGenerator()
name = name_gen.generate()
return name + '-container'
@base.Controller.api_version("1.1", "1.19")
@pecan.expose('json')
@api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_create)
@validation.validated(schema.legacy_container_create)
def post(self, run=False, **container_dict):
# NOTE(hongbin): We convert the representation of 'command' from
# string to list. For example:
# '"nginx" "-g" "daemon off;"' -> ["nginx", "-g", "daemon off;"]
command = container_dict.pop('command', None)
if command is not None:
if isinstance(command, six.string_types):
command = shlex.split(command)
container_dict['command'] = command
return self._do_post(run, **container_dict)
@base.Controller.api_version("1.20") # noqa
@pecan.expose('json')
@api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_create)
@validation.validated(schema.container_create)
def post(self, run=False, **container_dict):
return self._do_post(run, **container_dict)
def _do_post(self, run=False, **container_dict):
"""Create or run a new container.
:param run: if true, starts the container
:param container_dict: a container within the request body.
"""
context = pecan.request.context
compute_api = pecan.request.compute_api
policy.enforce(context, "container:create",
action="container:create")
if container_dict.get('security_groups'):
# remove duplicate security_groups from list
container_dict['security_groups'] = list(set(
container_dict.get('security_groups')))
for index, sg in enumerate(container_dict['security_groups']):
security_group_id = self._check_security_group(context,
{'name': sg})
container_dict['security_groups'][index] = security_group_id
try:
run = strutils.bool_from_string(run, strict=True)
container_dict['interactive'] = strutils.bool_from_string(
container_dict.get('interactive', False), strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid run or interactive '
'values are: %s') % bools)
# Check container quotas
self._check_container_quotas(context, container_dict)
auto_remove = container_dict.pop('auto_remove', None)
if auto_remove is not None:
api_utils.version_check('auto_remove', '1.3')
try:
container_dict['auto_remove'] = strutils.bool_from_string(
auto_remove, strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS +
strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid auto_remove '
'values are: %s') % bools)
runtime = container_dict.pop('runtime', None)
if runtime is not None:
api_utils.version_check('runtime', '1.5')
policy.enforce(context, "container:create:runtime",
action="container:create:runtime")
container_dict['runtime'] = runtime
hostname = container_dict.pop('hostname', None)
if hostname is not None:
api_utils.version_check('hostname', '1.9')
container_dict['hostname'] = hostname
nets = container_dict.get('nets', [])
requested_networks = utils.build_requested_networks(context, nets)
pci_req = self._create_pci_requests_for_sriov_ports(context,
requested_networks)
healthcheck = container_dict.pop('healthcheck', {})
if healthcheck:
api_utils.version_check('healthcheck', '1.22')
healthcheck['test'] = healthcheck.pop('cmd', '')
container_dict['healthcheck'] = healthcheck
mounts = container_dict.pop('mounts', [])
if mounts:
api_utils.version_check('mounts', '1.11')
requested_volumes = self._build_requested_volumes(context, mounts)
cpu_policy = container_dict.pop('cpu_policy', None)
container_dict['cpu_policy'] = cpu_policy
privileged = container_dict.pop('privileged', None)
if privileged is not None:
api_utils.version_check('privileged', '1.21')
policy.enforce(context, "container:create:privileged",
action="container:create:privileged")
try:
container_dict['privileged'] = strutils.bool_from_string(
privileged, strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS +
strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid privileged values '
'are: %s') % bools)
# Valiadtion accepts 'None' so need to convert it to None
if container_dict.get('image_driver'):
container_dict['image_driver'] = api_utils.string_or_none(
container_dict.get('image_driver'))
container_dict['project_id'] = context.project_id
container_dict['user_id'] = context.user_id
name = container_dict.get('name') or \
self._generate_name_for_container()
container_dict['name'] = name
self._set_default_resource_limit(container_dict)
if container_dict.get('restart_policy'):
utils.check_for_restart_policy(container_dict)
exposed_ports = container_dict.pop('exposed_ports', None)
if exposed_ports is not None:
api_utils.version_check('exposed_ports', '1.24')
exposed_ports = utils.build_exposed_ports(exposed_ports)
container_dict['exposed_ports'] = exposed_ports
container_dict['status'] = consts.CREATING
extra_spec = {}
extra_spec['hints'] = container_dict.get('hints', None)
extra_spec['pci_requests'] = pci_req
extra_spec['availability_zone'] = container_dict.get(
'availability_zone')
new_container = objects.Container(context, **container_dict)
new_container.create(context)
kwargs = {}
kwargs['extra_spec'] = extra_spec
kwargs['requested_networks'] = requested_networks
kwargs['requested_volumes'] = requested_volumes
if pci_req.requests:
kwargs['pci_requests'] = pci_req
kwargs['run'] = run
compute_api.container_create(context, new_container, **kwargs)
# Set the HTTP Location Header
pecan.response.location = link.build_url('containers',
new_container.uuid)
pecan.response.status = 202
return view.format_container(context, pecan.request.host_url,
new_container.as_dict())
def _check_container_quotas(self, context, container_delta_dict,
update_container=False):
deltas = {
'containers': 0 if update_container else 1,
'cpu': container_delta_dict.get('cpu', 0),
'memory': container_delta_dict.get('memory', 0),
'disk': container_delta_dict.get('disk', 0)
}
def _check_deltas(context, deltas):
"""Check usage deltas against quota limits.
This does QUOTAS.count() followed by a QUOTAS.limit_check()
using the provided deltas.
:param context: The request context, for access check.
:param deltas: A dict of {resource_name: delta, ...} to check
against the quota limits.
"""
check_kwargs = {}
count_as_dict = {}
project_id = context.project_id
for res_name, res_delta in deltas.items():
# TODO(kiennt): Apply count_as_dict method, query count usage
# once rather than count each resource.
count_as_dict[res_name] = QUOTAS.count(context, res_name,
project_id)
total = None
try:
if isinstance(count_as_dict[res_name], six.integer_types):
total = count_as_dict[res_name] + int(res_delta)
else:
total = float(count_as_dict[res_name]) + \
float(res_delta)
except TypeError as e:
raise e
check_kwargs[res_name] = total
QUOTAS.limit_check(context, project_id, **check_kwargs)
_check_deltas(context, deltas)
def _set_default_resource_limit(self, container_dict):
# NOTE(kiennt): Default disk size will be set later.
container_dict['disk'] = container_dict.get('disk')
container_dict['memory'] = container_dict.get(
'memory', CONF.default_memory)
container_dict['memory'] = str(container_dict['memory'])
container_dict['cpu'] = container_dict.get(
'cpu', CONF.default_cpu)
def _create_pci_requests_for_sriov_ports(self, context,
requested_networks):
pci_requests = objects.ContainerPCIRequests(requests=[])
if not requested_networks:
return pci_requests
neutron_api = neutron.NeutronAPI(context)
for request_net in requested_networks:
phynet_name = None
vnic_type = network_model.VNIC_TYPE_NORMAL
if request_net.get('port'):
vnic_type, phynet_name = self._get_port_vnic_info(
context, neutron_api, request_net['port'])
pci_request_id = None
if vnic_type in network_model.VNIC_TYPES_SRIOV:
spec = {pci_request.PCI_NET_TAG: phynet_name}
dev_type = pci_request.DEVICE_TYPE_FOR_VNIC_TYPE.get(vnic_type)
if dev_type:
spec[pci_request.PCI_DEVICE_TYPE_TAG] = dev_type
request = objects.ContainerPCIRequest(
count=1,
spec=[spec],
request_id=uuidutils.generate_uuid())
pci_requests.requests.append(request)
pci_request_id = request.request_id
request_net['pci_request_id'] = pci_request_id
return pci_requests
def _get_port_vnic_info(self, context, neutron, port_id):
"""Retrieve port vnic info
Invoked with a valid port_id.
Return vnic type and the attached physical network name.
"""
phynet_name = None
port = self._show_port(context, port_id, neutron_client=neutron,
fields=['binding:vnic_type', 'network_id'])
vnic_type = port.get('binding:vnic_type',
network_model.VNIC_TYPE_NORMAL)
if vnic_type in network_model.VNIC_TYPES_SRIOV:
net_id = port['network_id']
phynet_name = self._get_phynet_info(context, net_id)
return vnic_type, phynet_name
def _show_port(self, context, port_id, neutron_client=None, fields=None):
"""Return the port for the client given the port id.
:param context: Request context.
:param port_id: The id of port to be queried.
:param neutron_client: A neutron client.
:param fields: The condition fields to query port data.
:returns: A dict of port data.
e.g. {'port_id': 'abcd', 'fixed_ip_address': '1.2.3.4'}
"""
if not neutron_client:
neutron_client = neutron.NeutronAPI(context)
if fields:
result = neutron_client.show_port(port_id, fields=fields)
else:
result = neutron_client.show_port(port_id)
return result.get('port')
def _get_phynet_info(self, context, net_id):
# NOTE(hongbin): Use admin context here because non-admin users are
# unable to retrieve provider:* attributes.
admin_context = zun_context.get_admin_context()
neutron_api = neutron.NeutronAPI(admin_context)
network = neutron_api.show_network(
net_id, fields='provider:physical_network')
net = network.get('network')
phynet_name = net.get('provider:physical_network')
return phynet_name
def _build_requested_volumes(self, context, mounts):
cinder_api = cinder.CinderAPI(context)
requested_volumes = []
for mount in mounts:
volume_dict = {
'cinder_volume_id': None,
'container_path': None,
'auto_remove': False,
'contents': None,
'user_id': context.user_id,
'project_id': context.project_id,
}
volume_type = mount.get('type', 'volume')
if volume_type == 'volume':
if mount.get('source'):
volume = cinder_api.search_volume(mount['source'])
cinder_api.ensure_volume_usable(volume)
volume_dict['cinder_volume_id'] = volume.id
volume_dict['container_path'] = mount['destination']
volume_dict['volume_provider'] = 'cinder'
elif mount.get('size'):
volume = cinder_api.create_volume(mount['size'])
cinder_api.ensure_volume_usable(volume)
volume_dict['cinder_volume_id'] = volume.id
volume_dict['container_path'] = mount['destination']
volume_dict['volume_provider'] = 'cinder'
volume_dict['auto_remove'] = True
elif volume_type == 'bind':
volume_dict['contents'] = mount.pop('source', '')
volume_dict['container_path'] = mount['destination']
volume_dict['volume_provider'] = 'local'
volmapp = objects.VolumeMapping(context, **volume_dict)
requested_volumes.append(volmapp)
return requested_volumes
def _check_security_group(self, context, security_group):
neutron_api = neutron.NeutronAPI(context)
try:
return neutron_api.find_resourceid_by_name_or_id(
'security_group', security_group['name'], context.project_id)
except n_exc.NeutronClientNoUniqueMatch as e:
msg = _("Multiple security group matches found for name "
"%(name)s, use an ID to be more specific.") % {
'name': security_group['name']}
raise exception.Conflict(msg)
except n_exc.NeutronClientException as e:
if e.status_code == 404:
msg = _("Security group %(name)s not found.") % {
'name': security_group['name']}
raise exception.InvalidValue(msg)
else:
raise
@base.Controller.api_version("1.1", "1.14")
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validated(schema.add_security_group)
def add_security_group(self, container_ident, **security_group):
"""Add security group to an existing container.
:param container_ident: UUID or Name of a container.
:param security_group: security_group to be added to container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(
container.as_dict(), "container:add_security_group")
utils.validate_container_state(container, 'add_security_group')
# check if security group already presnt in container
context = pecan.request.context
compute_api = pecan.request.compute_api
security_group_id = self._check_security_group(context, security_group)
if security_group_id in container.security_groups:
msg = _("Security group %(id)s has been added to container.") % {
'id': security_group_id}
raise exception.InvalidValue(msg)
compute_api.add_security_group(context, container,
security_group_id)
pecan.response.status = 202
@base.Controller.api_version("1.1", "1.14")
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validated(schema.remove_security_group)
def remove_security_group(self, container_ident, **security_group):
"""Remove security group from an existing container.
:param container_ident: UUID or Name of a container.
:param security_group: security_group to be removed from container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(
container.as_dict(), "container:remove_security_group")
utils.validate_container_state(container, 'remove_security_group')
context = pecan.request.context
compute_api = pecan.request.compute_api
security_group_id = self._check_security_group(context, security_group)
if security_group_id not in container.security_groups:
msg = _("Security group %(id)s was not added to container.") % {
'id': security_group_id}
raise exception.InvalidValue(msg)
compute_api.remove_security_group(context, container,
security_group_id)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validated(schema.container_update)
def patch(self, container_ident, **patch):
"""Update an existing container.
:param container_ident: UUID or name of a container.
:param patch: a json PATCH document to apply to this container.
"""
container = utils.get_container(container_ident)
context = pecan.request.context
container_deltas = {}
check_policy_on_container(container.as_dict(), "container:update")
utils.validate_container_state(container, 'update')
if 'memory' in patch:
container_deltas['memory'] = \
int(patch['memory']) - int(container.memory)
patch['memory'] = str(patch['memory'])
if 'cpu' in patch:
patch['cpu'] = float(patch['cpu'])
container_deltas['cpu'] = patch['cpu'] - container.cpu
if 'name' in patch:
patch['name'] = str(patch['name'])
if 'memory' not in patch and 'cpu' not in patch:
for field, patch_val in patch.items():
if getattr(container, field) != patch_val:
setattr(container, field, patch_val)
container.save(context)
else:
# Check container quotas
self._check_container_quotas(context, container_deltas,
update_container=True)
compute_api = pecan.request.compute_api
container = compute_api.container_update(context, container, patch)
return view.format_container(context, pecan.request.host_url,
container.as_dict())
@base.Controller.api_version("1.1", "1.13")
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_rename)
def rename(self, container_ident, name):
"""Rename an existing container.
:param container_ident: UUID or Name of a container.
:param name: a new name for this container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:rename")
if container.name == name:
raise exception.Conflict('The new name for the container is the '
'same as the old name.')
container.name = name
context = pecan.request.context
container.save(context)
return view.format_container(context, pecan.request.host_url,
container.as_dict())
@base.Controller.api_version("1.19")
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validated(schema.container_update)
def resize_container(self, container_ident, **kwargs):
"""Resize an existing container.
:param container_ident: UUID or name of a container.
:param kwargs: cpu/memory to be updated.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(),
"container:resize_container")
utils.validate_container_state(container, 'resize_container')
if 'memory' in kwargs:
kwargs['memory'] = str(kwargs['memory'])
if 'cpu' in kwargs:
kwargs['cpu'] = float(kwargs['cpu'])
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.resize_container(context, container, kwargs)
pecan.response.status = 202
return view.format_container(context, pecan.request.host_url,
container.as_dict())
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_delete)
def delete(self, container_ident, force=False, **kwargs):
"""Delete a container.
:param container_ident: UUID or Name of a container.
:param force: If True, allow to force delete the container.
"""
context = pecan.request.context
if utils.is_all_projects(kwargs):
policy.enforce(context, "container:delete_all_projects",
action="container:delete_all_projects")
context.all_projects = True
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:delete")
stop = kwargs.pop('stop', False)
try:
force = strutils.bool_from_string(force, strict=True)
stop = strutils.bool_from_string(stop, strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid force or stop values '
'are: %s') % bools)
compute_api = pecan.request.compute_api
if not force and not stop:
utils.validate_container_state(container, 'delete')
elif force and not stop:
api_utils.version_check('force', '1.7')
policy.enforce(context, "container:delete_force",
action="container:delete_force")
utils.validate_container_state(container, 'delete_force')
elif stop:
api_utils.version_check('stop', '1.12')
utils.validate_container_state(container,
'delete_after_stop')
if container.status == consts.RUNNING:
check_policy_on_container(container.as_dict(),
"container:stop")
LOG.debug('Calling compute.container_stop with %s '
'before delete', container.uuid)
compute_api.container_stop(context, container, 10)
container.status = consts.DELETING
if container.host:
compute_api.container_delete(context, container, force)
else:
container.destroy(context)
pecan.response.status = 204
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def rebuild(self, container_ident, **kwargs):
"""Rebuild container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:rebuild")
utils.validate_container_state(container, 'rebuild')
if kwargs.get('image'):
container.image = kwargs.get('image')
if kwargs.get('image_driver'):
utils.validate_image_driver(kwargs.get('image_driver'))
container.image_driver = kwargs.get('image_driver')
LOG.debug('Calling compute.container_rebuild with %s',
container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.container_rebuild(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def start(self, container_ident, **kwargs):
"""Start container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:start")
utils.validate_container_state(container, 'start')
LOG.debug('Calling compute.container_start with %s',
container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.container_start(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_stop)
def stop(self, container_ident, timeout=None, **kwargs):
"""Stop container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:stop")
utils.validate_container_state(container, 'stop')
LOG.debug('Calling compute.container_stop with %s',
container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.container_stop(context, container, timeout)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_reboot)
def reboot(self, container_ident, timeout=None, **kwargs):
"""Reboot container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:reboot")
utils.validate_container_state(container, 'reboot')
LOG.debug('Calling compute.container_reboot with %s',
container.uuid)
context = pecan.request.context
container.status = consts.RESTARTING
container.save(context)
compute_api = pecan.request.compute_api
compute_api.container_reboot(context, container, timeout)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def pause(self, container_ident, **kwargs):
"""Pause container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:pause")
utils.validate_container_state(container, 'pause')
LOG.debug('Calling compute.container_pause with %s',
container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.container_pause(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def unpause(self, container_ident, **kwargs):
"""Unpause container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:unpause")
utils.validate_container_state(container, 'unpause')
LOG.debug('Calling compute.container_unpause with %s',
container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
compute_api.container_unpause(context, container)
pecan.response.status = 202
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_logs)
def logs(self, container_ident, stdout=True, stderr=True,
timestamps=False, tail='all', since=None):
"""Get logs of the given container.
:param container_ident: UUID or Name of a container.
:param stdout: Get standard output if True.
:param stderr: Get standard error if True.
:param timestamps: Show timestamps.
:param tail: Number of lines to show from the end of the logs.
(default: get all logs)
:param since: Show logs since a given datetime or
integer epoch (in seconds).
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:logs")
utils.validate_container_state(container, 'logs')
try:
stdout = strutils.bool_from_string(stdout, strict=True)
stderr = strutils.bool_from_string(stderr, strict=True)
timestamps = strutils.bool_from_string(timestamps, strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid stdout, stderr and '
'timestamps values are: %s')
% bools)
LOG.debug('Calling compute.container_logs with %s', container.uuid)
context = pecan.request.context
compute_api = pecan.request.compute_api
return compute_api.container_logs(context, container, stdout, stderr,
timestamps, tail, since)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request,
schema.query_param_execute_command)
def execute(self, container_ident, run=True, interactive=False, **kwargs):
"""Execute command in a running container.
:param container_ident: UUID or Name of a container.
:param run: If True, execute run.
:param interactive: Keep STDIN open and allocate a
pseudo-TTY for interactive.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:execute")
utils.validate_container_state(container, 'execute')
try:
run = strutils.bool_from_string(run, strict=True)
interactive = strutils.bool_from_string(interactive, strict=True)
except ValueError:
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
raise exception.InvalidValue(_('Valid run or interactive '
'values are: %s') % bools)
LOG.debug('Calling compute.container_exec with %(uuid)s command '
'%(command)s',
{'uuid': container.uuid, 'command': kwargs['command']})
context = pecan.request.context
compute_api = pecan.request.compute_api
return compute_api.container_exec(context, container,
kwargs['command'],
run, interactive)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request,
schema.query_param_execute_resize)
def execute_resize(self, container_ident, exec_id, **kwargs):
"""Resize the tty session used by the exec
:param container_ident: UUID or Name of a container.
:param exec_id: ID of a exec.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(),
"container:execute_resize")
utils.validate_container_state(container, 'execute_resize')
LOG.debug('Calling tty resize used by exec %s', exec_id)
context = pecan.request.context
compute_api = pecan.request.compute_api
return compute_api.container_exec_resize(
context, container, exec_id, kwargs.get('h', None),
kwargs.get('w', None))