|
|
/usr/lib/python2.7/site-packages/openstack/compute/v2
: j% G8 f$ t8 g; S! vserver.py! a8 ^+ J7 |) R3 h- A8 D
文件内容:
% _ `: [! J- F1 f- `2 G2 h8 M! o/ s
# Licensed under the Apache License, Version 2.0 (the "License"); you may% ^& }1 M! {1 J; `1 X2 U7 z
# not use this file except in compliance with the License. You may obtain
6 L# s& o0 I6 `" T- D! S# a copy of the License at
6 Q: m8 \& a4 g& v1 @#$ u, @) Z" H- g( O: i' x$ {
# http://www.apache.org/licenses/LICENSE-2.01 q; u! N( ^; ~) U- ^0 u) _1 t; z
#
$ Y. [: R7 x5 n; O, R# Unless required by applicable law or agreed to in writing, software
- D2 Y6 z, {0 O3 {/ ]# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT! d6 j& y3 ? l$ K* D- Z9 H0 M
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the1 B. X7 J. }. x" J/ q0 l1 Z
# License for the specific language governing permissions and limitations: o ?7 c# C1 J" K& o
# under the License.( N0 X7 |& o$ k+ ?/ P
from openstack.compute.v2 import metadata# |* o8 ^, ^$ J0 ]" m+ x, E
from openstack.image.v2 import image
; S) j+ w4 w5 ~; O X! v8 xfrom openstack import exceptions' o- k; ^0 n8 R
from openstack import resource
1 c" d+ A. L/ T4 o2 M% m! bfrom openstack import utils- Y, ~2 V- }9 {8 |: v- o
CONSOLE_TYPE_ACTION_MAPPING = {7 g+ I& |6 j3 J& c4 q
'novnc': 'os-getVNCConsole',' @. Z# H8 l5 [5 P: {, M' x
'xvpvnc': 'os-getVNCConsole',4 M+ h# h. j# [9 u/ a- \) c2 W
'spice-html5': 'os-getSPICEConsole',
" Q& a( P! T r- M/ g 'rdp-html5': 'os-getRDPConsole',9 t( j+ x/ X5 X1 d+ Y1 i
'serial': 'os-getSerialConsole'0 P+ Q8 r/ Z/ `! Q
}5 `9 P& y" {8 I7 R0 z: I
class Server(resource.Resource, metadata.MetadataMixin, resource.TagMixin):2 ?& E3 r+ D" o |. j. C
resource_key = 'server'; c4 \1 w6 B9 R
resources_key = 'servers'% N- Q" y0 ~* G6 Q8 M
base_path = '/servers' c' F6 Z2 U* k" j+ q& T! U1 V
# capabilities* \4 [# L$ B" K/ L- o7 n, i* ~2 L
allow_create = True _ V. A0 ?8 t. w3 c) s
allow_fetch = True
4 x& e" A N0 F* }% {* m allow_commit = True6 E% h% |( ? q" S+ {
allow_delete = True# Y/ \, k4 Y8 B- y, L4 B6 a) {
allow_list = True
% d4 w7 J* q5 S" N+ B _query_mapping = resource.QueryParameters(4 b; n9 E/ C& k, r5 c% C0 V
"auto_disk_config", "availability_zone",( p" Y$ L2 n9 ]
"created_at", "description", "flavor",
/ I& W8 `" C2 u# r% K. h "hostname", "image", "kernel_id", "key_name",1 \* u- I3 r: G$ [* P1 ~* [3 g
"launch_index", "launched_at", "locked_by", "name",; r# _; k6 T2 v6 n% \6 d3 E
"node", "power_state", "progress", "project_id", "ramdisk_id",; t0 t( p. B( F3 [$ y( r
"reservation_id", "root_device_name",
, x W$ M3 o- h+ N9 ~ "status", "task_state", "terminated_at", "user_id",% P# T9 k/ i$ Y
"vm_state",( }* y" [9 [" Q# F; G: ]
"sort_key", "sort_dir",8 W3 _3 X: O, ~ O# {- p0 y
access_ipv4="access_ip_v4",. r5 R6 b- K# _( ]
access_ipv6="access_ip_v6",) M- A' c Y. o* m
has_config_drive="config_drive",
) H! |- m8 ?* E; s8 k" \ deleted_only="deleted",
* B1 E: Y9 {$ s: y U$ [ compute_host="host",
( r& m$ _9 c- e' P is_soft_deleted="soft_deleted",7 X* s7 x; K& ?7 x' L# [: a
ipv4_address="ip",
# q& ?: v& I/ \% k6 t& ]9 V0 [% { ipv6_address="ip6",
6 G; |% h' x3 h" ^2 S/ B$ X3 | changes_since="changes-since",( ^7 M9 |# a8 o/ d: F- S+ z0 h/ l P
changes_before="changes-before",
' M& o; Y. ?3 _. e2 o id="uuid",& \4 p& C" y$ A# _3 U
all_projects="all_tenants",
2 f2 L* z. j5 B, f# e **resource.TagMixin._tag_query_parameters9 p$ [3 a& [3 H- h
)
% \, D" N9 i3 x: N9 ^ _max_microversion = '2.72'. f8 F+ ]- y8 _$ Y/ F
#: A list of dictionaries holding links relevant to this server.: \; F% z! [% n5 r" p8 K/ s1 M Q
links = resource.Body('links')
- F1 m, I' }" g# C" G! p access_ipv4 = resource.Body('accessIPv4')
~$ l+ M X$ B2 C access_ipv6 = resource.Body('accessIPv6')" l1 e5 K s. ]
#: A dictionary of addresses this server can be accessed through.. u& p0 X9 @. i& k1 U) C9 D
#: The dictionary contains keys such as ``private`` and ``public``,* B1 S! _$ }: n! H, {
#: each containing a list of dictionaries for addresses of that type.0 \% `+ M/ i/ Y# m
#: The addresses are contained in a dictionary with keys ``addr``% q9 w- p4 a8 T
#: and ``version``, which is either 4 or 6 depending on the protocol: U* `8 _; N0 P1 ]0 E& _
#: of the IP address. *Type: dict*
) f. {& I. H A addresses = resource.Body('addresses', type=dict)
$ ? S# T; G& W! ~ #: When a server is first created, it provides the administrator password.
4 Q6 z+ e4 q4 I' y: \ admin_password = resource.Body('adminPass')
+ m2 }) w6 m2 I: g1 z& H0 a7 A0 H #: A list of an attached volumes. Each item in the list contains at least" i9 F5 B1 P' |$ N! j
#: an "id" key to identify the specific volumes.$ `$ `; o! r/ g# f
attached_volumes = resource.Body(
" b6 ~; g# c% V) c 'os-extended-volumes:volumes_attached')/ U& {1 Y0 b2 D- B5 D+ S7 g
#: The name of the availability zone this server is a part of.
1 D5 Q, O6 R d$ d; [# K# @" L, f( r* r availability_zone = resource.Body('OS-EXT-AZ:availability_zone')% Q4 a4 g) E6 a b7 y! [
#: Enables fine grained control of the block device mapping for an
+ R4 e. [* W$ d* y1 d; p, r #: instance. This is typically used for booting servers from volumes.
R3 u. m+ n# X/ V8 O block_device_mapping = resource.Body('block_device_mapping_v2')7 v' z$ c, i- J7 r" q" ~
#: Indicates whether or not a config drive was used for this server.
# c( Z/ o6 m1 x4 x, R config_drive = resource.Body('config_drive')
3 v7 R8 K1 `% ]- G #: The name of the compute host on which this instance is running.
1 t$ y; \: p$ i/ s7 S' P3 B- c" [" D% O #: Appears in the response for administrative users only.
3 J, i: r0 d0 o' {* G: r0 m0 N compute_host = resource.Body('OS-EXT-SRV-ATTR:host')
8 E! M- E* @' v' Q, ^5 l #: Timestamp of when the server was created.; Y S8 c8 v$ J
created_at = resource.Body('created')
/ e: k/ y- h+ ]# } #: The description of the server. Before microversion
1 a. j6 g) H# h. T #: 2.19 this was set to the server name.
7 J% P8 W7 X' c0 e' p9 ?9 J description = resource.Body('description')
" b5 Q' k! y, z8 J9 h0 b0 |- t. \: l6 T #: The disk configuration. Either AUTO or MANUAL.
0 H! r5 L( |8 ?( X& U disk_config = resource.Body('OS-DCF:diskConfig')1 k: B; |& @' g
#: The flavor reference, as a ID or full URL, for the flavor to use for! p9 z2 p! a8 }4 p0 G
#: this server.
/ O0 E9 \9 X7 }! _6 J# H flavor_id = resource.Body('flavorRef')
# t+ |* M$ t/ ^/ T" `8 z #: The flavor property as returned from server.
: F. S9 V0 r1 e% I # TODO(gtema): replace with flavor.Flavor addressing flavor.original_name
. C4 x- n0 @% z! l: x& Z4 Y flavor = resource.Body('flavor', type=dict)
E' P/ l4 U. }8 l" M9 |7 N #: Indicates whether a configuration drive enables metadata injection.3 L. \/ L7 o0 a1 o/ Z
#: Not all cloud providers enable this feature.+ s/ W0 t* M! W0 b6 c
has_config_drive = resource.Body('config_drive')
/ L' F& Z5 M; [- C( O( u8 n #: An ID representing the host of this server.
5 W/ n9 y1 a. ]' [1 q1 }' C% ~ host_id = resource.Body('hostId'): F5 [, [& d8 b
#: The host status., x& \) A+ @/ \2 c/ @( @
host_status = resource.Body('host_status')- @9 j5 a1 Y- U9 Y
#: The hostname set on the instance when it is booted.
/ S, K. V* B+ x #: By default, it appears in the response for administrative users only.2 h* I& T# a! S- F
hostname = resource.Body('OS-EXT-SRV-ATTR:hostname')
% E, Z' [; k- P. z0 t. ^- { [ #: The hypervisor host name. Appears in the response for administrative
% Z7 I& |. T- E2 p1 p5 s4 t( |, @5 L #: users only.
. [" D j7 N1 i0 {# N hypervisor_hostname = resource.Body('OS-EXT-SRV-ATTR:hypervisor_hostname')9 m I( k7 V/ q/ Z* z+ j4 p
#: The image reference, as a ID or full URL, for the image to use for
, {& i4 R' x3 L+ o0 b! | #: this server.1 P. N- j& `% M0 b
image_id = resource.Body('imageRef')
8 C$ h; C$ y$ ~1 D, {9 [, n #: The image property as returned from server.
1 a$ R( G) a2 {6 E+ u8 z image = resource.Body('image', type=image.Image)
5 z j0 P$ `+ p3 `+ M; y #: The instance name. The Compute API generates the instance name from the3 H/ I" J1 y: B2 [9 ?- ~
#: instance name template. Appears in the response for administrative users
^- V2 {7 z3 L0 ^) t #: only.
. q8 b) c' U1 b7 S0 r instance_name = resource.Body('OS-EXT-SRV-ATTR:instance_name')
! L6 V; t0 w3 p' `( M0 }+ ? # The locked status of the server( Z1 J( i' h/ w2 ?, L( ?) {
is_locked = resource.Body('locked', type=bool)
5 h- P, ?5 y2 w8 A/ o #: The UUID of the kernel image when using an AMI. Will be null if not.
9 c- B$ X. Y7 ?1 ^ b% o #: By default, it appears in the response for administrative users only.
F+ t* k7 w- m$ Z' M& s$ O; h kernel_id = resource.Body('OS-EXT-SRV-ATTR:kernel_id')& Y: N/ P$ X5 d& K9 r
#: The name of an associated keypair
?4 K$ j+ c7 W$ v+ t key_name = resource.Body('key_name')( ]2 \8 B5 A4 F6 T( p4 @! S
#: When servers are launched via multiple create, this is the
! @6 P4 [* p. w. y" }& a' z #: sequence in which the servers were launched. By default, it) q& Z7 n( o+ Z
#: appears in the response for administrative users only.
% F, j5 e9 b- K' ?' H. I* N launch_index = resource.Body('OS-EXT-SRV-ATTR:launch_index', type=int): @" p7 {0 G: \/ W( P$ B
#: The timestamp when the server was launched.
/ m2 j' o$ ^ m. @, Y H. k r launched_at = resource.Body('OS-SRV-USG:launched_at')
1 h3 ^# L& ~* e" V5 {# [ #: Metadata stored for this server. *Type: dict*
( a( F" P3 r8 ]& Q* s+ ~ metadata = resource.Body('metadata', type=dict)
t* Q: Q& q' N& M #: A networks object. Required parameter when there are multiple: R6 u% |* R& [0 i5 s
#: networks defined for the tenant. When you do not specify the
/ l# `* e; D1 m# d$ `& M" s! V& F" Y e #: networks parameter, the server attaches to the only network
2 ^- o( p, Z: ^8 Z #: created for the current tenant.
( G7 _* @: J7 H4 L k networks = resource.Body('networks')' N; p! u' M$ [5 U5 Z5 f2 ^7 @
#: The file path and contents, text only, to inject into the server at3 s4 s7 ^; n% X" }8 r# E6 d) }
#: launch. The maximum size of the file path data is 255 bytes.3 `+ J1 j# \6 z2 ?- F; s
#: The maximum limit is The number of allowed bytes in the decoded,
0 H2 b. N+ D0 S4 S #: rather than encoded, data.( }) T4 U0 u4 F4 l" F8 K
personality = resource.Body('personality')
+ }0 O+ G3 G6 M+ `: w #: The power state of this server.
+ N; [& s" w# Y( o power_state = resource.Body('OS-EXT-STS:power_state'). y& r% k% ?4 @! e3 ~/ n
#: While the server is building, this value represents the percentage
# b/ v6 a. w0 Q: h: o2 l- R7 b #: of completion. Once it is completed, it will be 100. *Type: int*
) T1 j$ f/ O1 e/ }8 m progress = resource.Body('progress', type=int)& I4 e+ T) @! s) {* F9 p" f" n0 b
#: The ID of the project this server is associated with.
3 }5 C/ l) ` M: g project_id = resource.Body('tenant_id')
+ Y- G+ p: J9 G9 E) } #: The UUID of the ramdisk image when using an AMI. Will be null if not.
8 n) I; @* |1 ?( g: X9 x+ Q #: By default, it appears in the response for administrative users only., d- J# D+ g) u; l
ramdisk_id = resource.Body('OS-EXT-SRV-ATTR:ramdisk_id')
3 S# g! x& ? }( x1 z& A$ Q5 `- J6 u #: The reservation id for the server. This is an id that can be
" O0 N6 Y0 |( n- }( { #: useful in tracking groups of servers created with multiple create,
* z6 b+ q9 t8 C+ V% m3 d #: that will all have the same reservation_id. By default, it appears( `, {, j0 E0 Y; U- S
#: in the response for administrative users only.( F- K* H, _! \
reservation_id = resource.Body('OS-EXT-SRV-ATTR:reservation_id')
4 k+ t e; k3 [9 s #: The root device name for the instance By default, it appears in the* e( L6 ^% _; M5 C
#: response for administrative users only.8 q' W3 Q; ?. @/ b, g! V
root_device_name = resource.Body('OS-EXT-SRV-ATTR:root_device_name')
4 I3 ^0 g1 Y5 G8 q #: The dictionary of data to send to the scheduler.
. {- C7 @; `. V8 {5 z scheduler_hints = resource.Body('OS-SCH-HNT:scheduler_hints', type=dict)1 a i3 v0 s: b: n
#: A list of applicable security groups. Each group contains keys for% |; m$ r! [8 I' H
#: description, name, id, and rules.( n; r$ M' b z
security_groups = resource.Body('security_groups',
- J9 s, l' o9 z+ C3 |* f7 ~ type=list, list_type=dict), S$ I; G1 h2 H
#: The UUIDs of the server groups to which the server belongs.
& |" s0 W3 A8 M4 D; h- | #: Currently this can contain at most one entry.; Y1 v7 N8 r! z* L3 ?9 h
server_groups = resource.Body('server_groups', type=list, list_type=dict)2 A. s" Q9 t/ k( E" w; `; k
#: The state this server is in. Valid values include ``ACTIVE``,3 v! W7 L2 _' f/ X2 N
#: ``BUILDING``, ``DELETED``, ``ERROR``, ``HARD_REBOOT``, ``PASSWORD``,
8 M4 N) S9 Y4 R" A* m! o #: ``PAUSED``, ``REBOOT``, ``REBUILD``, ``RESCUED``, ``RESIZED``," T8 Z( y# ]6 f' E( X% H+ z
#: ``REVERT_RESIZE``, ``SHUTOFF``, ``SOFT_DELETED``, ``STOPPED``,
3 u% t" z: N1 @ k% x5 T #: ``SUSPENDED``, ``UNKNOWN``, or ``VERIFY_RESIZE``.
1 u8 y; H) v7 T( R: k! l7 q# h status = resource.Body('status')
9 A* D% G- S6 F1 G* W, Y+ h; Y& C7 i #: The task state of this server.6 }7 R$ z5 R0 O6 }1 |0 d/ W
task_state = resource.Body('OS-EXT-STS:task_state')
+ h7 B/ c& `2 j6 f9 D #: The timestamp when the server was terminated (if it has been).
% s" m) P3 l' `3 z6 B, U9 G; Z terminated_at = resource.Body('OS-SRV-USG:terminated_at')* g l( m* [8 x
#: A list of trusted certificate IDs, that were used during image
Z7 V7 u8 f5 N/ [8 f( @ #: signature verification to verify the signing certificate.
$ {8 ?( z; T {; ?; k. G. |- c/ L trusted_image_certificates = resource.Body(
: u8 ^5 J( \ E/ }8 x# i 'trusted_image_certificates', type=list)5 p. n( E/ r: c' I
#: Timestamp of when this server was last updated.4 R* `! |+ k8 c! \- A6 _+ S" g' c- K
updated_at = resource.Body('updated')+ m6 L3 w) w! {4 x$ Q) g9 f
#: Configuration information or scripts to use upon launch.
8 K, Z E1 d5 w6 O& a #: Must be Base64 encoded.
6 I) G& e! \0 W8 H: @5 L! Y* O user_data = resource.Body('OS-EXT-SRV-ATTR:user_data')% T& M$ h7 w# q
#: The ID of the owners of this server.; W( y2 a7 E+ I2 p
user_id = resource.Body('user_id')
, u$ U( W5 d' U4 E( }2 Z, ?) t #: The VM state of this server.5 B7 N- k$ N! z8 r$ i
vm_state = resource.Body('OS-EXT-STS:vm_state')
) U. {! R7 e4 u, A" f8 J7 X+ G' I def _prepare_request(self, requires_id=True, prepend_key=True,
: U8 H5 ~; Z% [4 v5 C- L base_path=None, **kwargs):! J/ x$ T0 s6 T/ p1 |
request = super(Server, self)._prepare_request(requires_id=requires_id,
0 \& i: G$ [1 }4 ? prepend_key=prepend_key,! `' d; F4 _ _& a1 b) c( U
base_path=base_path)
* J" \: V6 @* t" A/ v server_body = request.body[self.resource_key]% ]$ {& \! o7 E
# Some names exist without prefix on requests but with a prefix
- ?) {0 |* V; U8 ~# {: e # on responses. If we find that we've populated one of these
$ I# }6 L) X/ J* K # attributes with something and then go to make a request, swap out! q# C& w, l$ l; W: J" C, g# C
# the name to the bare version." l' [2 g4 Z+ L, q
# Availability Zones exist with a prefix on response, but not request
; R7 z! z2 l, V2 a az_key = "OS-EXT-AZ:availability_zone"5 u7 h; o/ a6 B# }
if az_key in server_body:
1 I5 G5 V: s0 L8 O$ u& f server_body["availability_zone"] = server_body.pop(az_key)
2 ?9 l( A7 S3 Q2 C, o. W # User Data exists with a prefix on response, but not request" \! o3 L- q$ Y$ Q( S
ud_key = "OS-EXT-SRV-ATTR:user_data"$ O h8 u& o6 j ~/ M1 z3 {
if ud_key in server_body:
2 n, d X: E# C3 f! s* b, E1 B server_body["user_data"] = server_body.pop(ud_key)
7 B, G7 L7 [. G1 M# U6 T: z # Scheduler hints are sent in a top-level scope, not within the
' F4 b& Z4 r8 E" P # resource_key scope like everything else. If we try to send
/ e7 @; w b3 j9 f' ` # scheduler_hints, pop them out of the resource_key scope and into. {. |4 |# l4 j& i3 f. o) h0 U4 [
# their own top-level scope.
+ S4 e0 v0 \6 F5 R; r; @ hint_key = "OS-SCH-HNT:scheduler_hints". n4 Y& G, U, L9 w3 `9 w9 f1 h
if hint_key in server_body:5 P/ X( `9 R2 p: Q/ ?
request.body[hint_key] = server_body.pop(hint_key): i# b# U! H# \0 X
return request3 W1 w1 C% `) i- p, {: e
def _action(self, session, body, microversion=None):
/ `, l2 G0 Z( i4 {& J """Preform server actions given the message body."""4 d6 B1 @" ?# D
# NOTE: This is using Server.base_path instead of self.base_path
9 J6 t6 I9 r( H # as both Server and ServerDetail instances can be acted on, but. J% T* |6 o& z, S+ \8 ~, A
# the URL used is sans any additional /detail/ part.( c/ |4 c0 F+ I" C- q6 l, @- v
url = utils.urljoin(Server.base_path, self.id, 'action')
3 X) ?1 ^. e' `0 k headers = {'Accept': ''}. M1 w6 g$ H6 T$ {
response = session.post(7 e# F% F: l+ n% p- A0 A+ M7 f
url, json=body, headers=headers, microversion=microversion)
$ g) H! K0 z2 |& Z2 N0 Q# T5 A, E exceptions.raise_from_response(response)0 s! e. R0 l( r2 Q# Y5 a# L4 A
return response
2 i9 H6 z o+ `- q def change_password(self, session, new_password):
( N `6 W% h. J1 K+ o """Change the administrator password to the given password."""% r: W* n7 g( B+ k! ?% O- U
body = {'changePassword': {'adminPass': new_password}}
0 }% E7 O3 e4 M/ M9 G M self._action(session, body)# C q3 {" ~: g
def get_password(self, session): j* e- w6 `0 S0 ]3 n( q- F6 C
"""Get the encrypted administrator password."""8 R: \8 e; A1 k: R' f9 ?
url = utils.urljoin(Server.base_path, self.id, 'os-server-password')% g5 E6 m( ^* a, i
return session.get(url)& |- ]' f, _( E, S
def reboot(self, session, reboot_type):
$ u# H( w1 f! u$ \ """Reboot server where reboot_type might be 'SOFT' or 'HARD'."""+ @1 ]2 b6 C6 \5 `& V- _% l
body = {'reboot': {'type': reboot_type}}- b. } ^2 z& h/ b2 V
self._action(session, body)
, L$ f7 `) m# f6 {9 O def force_delete(self, session):/ x+ ?5 f# O4 q! Z& G" \
"""Force delete a server."""
' c: u2 p8 ^" v9 b4 ?2 b body = {'forceDelete': None}/ u: A& }# g4 j6 ~+ h' M3 i
self._action(session, body)
( t9 n3 t. b D' a) C5 W def rebuild(self, session, name, admin_password,
9 i; F" K! L9 f( H1 }8 g6 B preserve_ephemeral=False, image=None,
* B2 w0 V% k h y% ?$ ^& S access_ipv4=None, access_ipv6=None,
/ i: o4 l. k$ N o- L metadata=None, personality=None):( i2 }' ~2 S# Y6 v& V4 T
"""Rebuild the server with the given arguments."""
; v$ Y( u0 u% n7 u/ m+ ]# L0 F action = {+ c. `' i9 i+ j9 R( A+ ~. l
'name': name,
4 S9 m0 c% g0 `4 V 'adminPass': admin_password,$ \! p: |: H& P
'preserve_ephemeral': preserve_ephemeral* ?' Y( ?1 S! L# ^/ W6 k% E9 V
}2 [4 `& y" S6 \) ^, O. |/ L$ F
if image is not None:
& U1 v3 B& x k# T3 X/ I action['imageRef'] = resource.Resource._get_id(image)
$ k) l8 n& r' F. V6 V if access_ipv4 is not None:
/ f% L- t( Z. i+ Y! A action['accessIPv4'] = access_ipv4. T3 [. X9 M$ @' C d1 H, a; a4 }
if access_ipv6 is not None:) _- R& R: }. K e
action['accessIPv6'] = access_ipv6
5 ^6 S- @& _' }6 z4 y if metadata is not None:+ z1 \5 W0 {. n
action['metadata'] = metadata! u* f* E+ n! d- E: V2 l
if personality is not None:
9 u4 C9 i K( Y! c/ d) f action['personality'] = personality
, T0 z Q: F9 x% j1 j1 k0 g body = {'rebuild': action}
2 t" X F( C) e7 s" V. ^% A response = self._action(session, body)
) d7 T( F) H5 W! E# Z4 d5 I self._translate_response(response)
3 \1 }' A( C7 \ B* K1 s return self
: E6 F" k$ r) e1 ~$ h$ u def resize(self, session, flavor):# \" k. \% f' p, g! Y( J& R% |
"""Resize server to flavor reference."""5 u% o3 p7 F2 U5 L+ |
body = {'resize': {'flavorRef': flavor}}
) K5 K* J( M* [6 g4 ?8 G) ]7 x self._action(session, body)) \' b; d: W# c1 {# G& z4 U% A
def confirm_resize(self, session):0 ` y# \9 Z9 t0 _
"""Confirm the resize of the server."""0 M- n, O: ]5 v
body = {'confirmResize': None}
/ M/ [ u9 @ w self._action(session, body)3 ?9 F1 d; O& K' i7 @5 m1 s7 |
def revert_resize(self, session):
% }, D. J9 E: w """Revert the resize of the server."""
0 A( c6 q, a; u& u; Z4 }% ^ body = {'revertResize': None}
& }4 u% L8 x: a) f8 T self._action(session, body)5 m+ |( t' a# V9 Y3 f3 l0 A
def create_image(self, session, name, metadata=None):
3 [! f2 d% i8 |- f! v! s """Create image from server."""2 b! r: `! T' ?& N5 M
action = {'name': name}% [: m9 D. ~+ w9 r6 z4 t
if metadata is not None:
" @" M8 A8 O. M action['metadata'] = metadata; z; Q. v$ N, d$ \2 M
body = {'createImage': action}
7 T6 Q9 l+ j! C # You won't believe it - wait, who am I kidding - of course you will!
& O' V3 F0 i' A0 _$ E # Nova returns the URL of the image created in the Location
6 f' B9 K5 P. W& S4 N # header of the response. (what?) But, even better, the URL it responds
& @. X( N; o) B% W6 q U # with has a very good chance of being wrong (it is built from- W7 _% h1 B2 e! T
# nova.conf values that point to internal API servers in any cloud
6 t. r4 m! v4 T* y. g # large enough to have both public and internal endpoints.
$ C: {" r( \- U) I2 |0 B # However, nobody has ever noticed this because novaclient doesn't6 K- T1 K$ f$ S, f. i
# actually use that URL - it extracts the id from the end of8 o/ X# `9 A. o4 w$ N$ M
# the url, then returns the id. This leads us to question:$ N7 l: {7 l! H2 n& w# d4 E
# a) why Nova is going to return a value in a header
# h/ y$ l/ n0 K, X( S # b) why it's going to return data that probably broken- Q/ ^/ Z% B m! W
# c) indeed the very nature of the fabric of reality
6 [7 b& _% I6 s/ p8 W# ] # Although it fills us with existential dread, we have no choice but7 `0 z: `- J4 \6 `
# to follow suit like a lemming being forced over a cliff by evil' K7 k* Q8 w2 w8 ]
# producers from Disney.
4 C5 ?: M! u2 G) _) J microversion = None
$ U) _' [& E% V if utils.supports_microversion(session, '2.45'):
9 D% f3 O. i9 y7 Q microversion = '2.45'& Z* G3 e% f4 \) T* D" P" s
response = self._action(session, body, microversion)
& b- T6 H% w& E8 f; g! e4 _ body = None" K, X4 f2 [/ n1 ^
try:
& T4 |2 d1 T' e' E # There might be body, might be not
+ G6 Z2 w4 \' X body = response.json()% A4 Y2 P( W/ V- E: t7 y, v6 @ Q
except Exception:
' Y! x/ V9 p+ N# G% W pass
. T" h+ U0 d9 Q e( J+ e if body and 'image_id' in body: O- E) `; C/ W0 |3 |! I
image_id = body['image_id']! I$ \$ P6 f6 i- n
else:
' t3 b+ j; p' h L+ W. W u/ Y- E image_id = response.headers['Location'].rsplit('/', 1)[1]
$ E- v# ?- N7 U8 M6 _- d return image_id
3 Z$ I* e( U Z def add_security_group(self, session, security_group_name):6 Q. E* Q# ^0 y0 W* v$ m9 v; w
body = {"addSecurityGroup": {"name": security_group_name}}
8 \: J& U6 T) |# _2 `; X! P: J8 ~: j self._action(session, body)
* R. w% y" w- H0 G8 T7 a) B% s( T' x def remove_security_group(self, session, security_group_name):8 l# R a, o/ i7 T# `# a
body = {"removeSecurityGroup": {"name": security_group_name}}
M- r9 G$ R1 d! k% G/ H' s self._action(session, body)/ G( h2 m% O& Z- a: A
def reset_state(self, session, state):
3 k9 p: u9 [$ a' p/ d body = {"os-resetState": {"state": state}}3 {1 z! o" z5 s! R/ R" G5 _. G5 [, I
self._action(session, body)8 X* f! [; W- I: _1 n8 G' \
def add_fixed_ip(self, session, network_id):- t) L- W0 e3 {! R
body = {"addFixedIp": {"networkId": network_id}}
2 S$ B' M. t8 E* P- L0 { self._action(session, body)( x" N9 E. ^! g, c- X: n
def remove_fixed_ip(self, session, address):2 ?: ?2 U, c, Y' R. \$ |
body = {"removeFixedIp": {"address": address}}! }# q; p O2 _+ M# @, j
self._action(session, body)
3 j3 E5 `) Q/ i6 k def add_floating_ip(self, session, address, fixed_address=None):7 z2 [& A% L6 q" ^! @6 m$ f
body = {"addFloatingIp": {"address": address}}
* N3 o& W6 F; }, Q" V3 M if fixed_address is not None:+ m7 D0 O, O- k1 ?4 }
body['addFloatingIp']['fixed_address'] = fixed_address; {( B+ J4 R% ^/ l: u4 m4 u
self._action(session, body)
* q( C; [0 G# h+ t% o' D" j/ b def remove_floating_ip(self, session, address):2 ]/ V ~+ ?; o1 @3 h _
body = {"removeFloatingIp": {"address": address}}- y0 U" p* u5 g- P; s$ h7 }% @+ o& x
self._action(session, body)1 Z- |* a4 {1 D' {& K
def backup(self, session, name, backup_type, rotation):0 }; X2 B5 e) w: k* u7 S
body = {
2 S5 J. C' l$ r1 L2 `5 \! X "createBackup": {
6 x: H0 n; J+ s4 V2 J4 R& ?$ ? "name": name,: Z! K4 B4 t5 u: c7 t% f
"backup_type": backup_type,7 ~1 {; n$ H$ C5 T
"rotation": rotation
0 K- E% U% h. ]' z q% A) g% j; p }
2 e1 c0 y& c% s( J' \% y }
1 F& K) t% r( A9 n& D, U$ Y4 z self._action(session, body)+ v% V0 }' ~4 v& s+ E; H
def pause(self, session):
+ n- t% i" o w7 t3 P/ \ body = {"pause": None}
' `2 u2 k/ v8 G! e7 y: Q* n0 [- j self._action(session, body)4 q& e3 [& L( O. g9 A
def unpause(self, session):) A$ R( O5 q7 x
body = {"unpause": None}* Z& o3 }" p8 R; `, W$ H% A6 c
self._action(session, body)
5 Y1 t0 p* y9 o( Q def suspend(self, session):
5 n" Y8 t' F8 o3 v0 }8 T9 z$ l body = {"suspend": None}
3 z. ]$ r' b3 {/ E8 J4 ] self._action(session, body)
- \4 `0 T9 T0 \4 D1 e def resume(self, session):% \$ w e/ Y+ ^! k& V
body = {"resume": None}
7 V! O) a3 e" c. J- v& [3 W self._action(session, body)' I1 @8 m+ Z3 u" Y) J
def lock(self, session):
& h# B4 z! c4 Y% v4 Y: ` body = {"lock": None}
4 R; p4 G% y. ~, _7 I0 j self._action(session, body): p) L5 y* ~6 P* P% H( g
def unlock(self, session):' U1 Y* Q% I0 N' ~! ^) x* O
body = {"unlock": None}
. U! o# D" p& E' P) p2 M( ` self._action(session, body)
$ c. N( M3 K, p* o def rescue(self, session, admin_pass=None, image_ref=None):. G' F8 p- S" T9 h) \" n) s$ P8 i' V$ q
body = {"rescue": {}}
/ Z: T; V2 M L$ | if admin_pass is not None:* k" T; g5 m5 i6 `6 E& |
body["rescue"]["adminPass"] = admin_pass
. q% O( u w2 e+ B( a3 l! b5 ] if image_ref is not None:
8 E N# J/ q6 T, a( E body["rescue"]["rescue_image_ref"] = image_ref+ B Y. x* k4 D
self._action(session, body)+ [2 R% ^+ i5 W) J
def unrescue(self, session):) M; X0 x5 g( f3 p: h- ~- p" k! N
body = {"unrescue": None}& z" G+ o7 C# \! D' J) _6 ?
self._action(session, body), m6 ]6 P$ \0 U2 J: q/ {
def evacuate(self, session, host=None, admin_pass=None, force=None):5 r0 R+ ^( r! ]
body = {"evacuate": {}}( k/ Y$ |, H' n2 O% D
if host is not None:
( n. F+ N9 f' _6 i6 |+ `8 v body["evacuate"]["host"] = host
]' ]* H, U2 h6 O+ W if admin_pass is not None:
3 s- [- M8 h7 D( @3 R0 B body["evacuate"]["adminPass"] = admin_pass
/ }0 b, ? x- W. D) t! d if force is not None:6 ~7 ^! c" V' k: _
body["evacuate"]["force"] = force
; v1 } B* o4 n4 c u, J self._action(session, body): A9 K6 A2 k. w- \- B4 c% o
def start(self, session):8 c+ c0 S# ?, j5 S9 |
body = {"os-start": None}
- X q+ h# _) J self._action(session, body). k3 G6 k2 \- a v3 \6 ^8 ?" M
def stop(self, session):. q, I5 j( K! p/ E, L: ^1 q# p3 b
body = {"os-stop": None}# _6 j& [; v/ R: \
self._action(session, body)
1 X3 d- n- R0 Y7 }+ L& k def shelve(self, session):
: G- d* M2 F! Y- P6 u9 I body = {"shelve": None}
7 j% L3 v2 y( o U, T! M$ I7 C- A self._action(session, body)' P: p! d8 a8 u- Y; ?+ s
def unshelve(self, session):1 V5 x$ c9 f3 @7 B. Z8 A, z
body = {"unshelve": None}7 Z! e8 t1 ?! N2 n- m. U' ^6 a
self._action(session, body)
" d$ {, S7 }. E% s! N def migrate(self, session):% Z' ^, _. k$ n0 w6 g
body = {"migrate": None}1 A* r% b5 f- a% f( X! P
self._action(session, body)8 N- ^% ] g; W; Q* ?" \
def get_console_output(self, session, length=None):
) }6 V8 `/ C# e) {% [- Y/ Q9 ~ body = {"os-getConsoleOutput": {}}
1 R$ h: C4 y! H; S( _ if length is not None:
* }0 l% p+ y% q' s3 ~0 ^( C; V body["os-getConsoleOutput"]["length"] = length
6 l6 X4 U% c0 e( N$ a7 S, _ resp = self._action(session, body)8 N1 L- D/ D$ q5 L7 ]2 B) Y
return resp.json() W9 n% Q" F" |) l& x2 r g, h
def get_console_url(self, session, console_type):
# e, F. j4 F1 t8 Z action = CONSOLE_TYPE_ACTION_MAPPING.get(console_type)
7 O r$ S" Q+ z6 S+ ~! z if not action:
3 S/ O9 A. ]4 d raise ValueError("Unsupported console type %s" % console_type)
X" s4 n' U0 m( d+ n0 d body = {action: {'type': console_type}}* t! Y& a- i# k* D# Y: v+ V
resp = self._action(session, body)
1 U5 n y R# m* L! J* e" U" K$ ? return resp.json().get('console')8 }7 S$ [* B1 C8 q( y
def live_migrate(self, session, host, force, block_migration,
. q" s# f7 V6 x0 C5 f' G/ x$ z disk_over_commit=False):5 g+ S, ?4 Z. e
if utils.supports_microversion(session, '2.30'):# A9 W! |2 i4 x& B. _
return self._live_migrate_30(
9 G3 j; {3 |/ I2 c/ E6 {, R session, host,
) v+ V2 Z3 Z3 g: r/ C force=force,& s* x; f1 ^5 b- K6 g* F# O
block_migration=block_migration); I6 E; b% n. T. o: F$ u
elif utils.supports_microversion(session, '2.25'):
" m' ^, t1 m/ s% w3 f5 O return self._live_migrate_25(5 e: X+ J' n4 ?! b! e
session, host,7 ~: j4 v5 t( X; M
force=force,8 }: x$ L; J4 O: i
block_migration=block_migration)' }. Z8 E6 @3 P4 B% ^0 V
else:
: Z5 B+ q1 w9 ~* B% _ return self._live_migrate(
8 f; A. q0 m3 G7 j! ]0 x8 l$ {, N session, host,
0 ~/ e& p' R* u5 ]8 C) f force=force,5 y. O- I+ A8 D4 z
block_migration=block_migration,+ `/ j1 f/ t& M# h; h5 g6 \
disk_over_commit=disk_over_commit)7 i: `% s: P0 H- b, a/ c
def _live_migrate_30(self, session, host, force, block_migration):9 `* ~5 y5 w3 J
microversion = '2.30'' h6 o: u1 O/ N. ?/ r/ V( B
body = {'host': None}
' Z% q, _: I! i* ]' T, D7 E if block_migration is None:# U( ~$ V( b4 B) {# A
block_migration = 'auto'
- W# f3 F: d8 n0 ]+ s body['block_migration'] = block_migration
" }& ^$ h% t ]9 @6 ?" } if host:
$ ?+ V2 T( e+ d' c& p9 ^. j body['host'] = host
, G' X( D" x6 X# D* d: O. a if force:6 k* L8 |, u: |: m/ h4 m2 P; r
body['force'] = force
- Y, _2 O4 i3 p self._action(
1 W5 h2 u/ L* V; W- l3 l& }& U& x session, {'os-migrateLive': body}, microversion=microversion)5 r; C" r, E' T' j' j. n- |8 z9 {
def _live_migrate_25(self, session, host, force, block_migration):8 l9 ] _% v5 |- c, \" q9 F% ~
microversion = '2.25'
( ?" R( i F, E8 b9 h# ^0 i- p# Z, D body = {'host': None}
/ e4 O2 L1 |/ Q/ l% N5 \ if block_migration is None:
6 g7 \! n$ Z- o2 W block_migration = 'auto'
. M2 v1 U7 L( F ^ body['block_migration'] = block_migration+ L0 ]: R: K- M5 d0 I: Q
if host:
8 L% C/ A6 e' D; f# B* u body['host'] = host$ W6 y% S& J. L9 g$ |
if not force:* t3 B4 Y( k. J- X' Q9 ]
raise ValueError(
, v' I2 `3 }! D% H) W "Live migration on this cloud implies 'force'"
- L0 h$ [/ I; X* h! y w" d " if the 'host' option has been given and it is not"
" o" A8 x+ i7 y/ g " possible to disable. It is recommended to not use 'host'"! d( v# I* I/ F1 s3 E
" at all on this cloud as it is inherently unsafe, but if"$ A# K$ L- I* M1 u% Y* A1 m
" it is unavoidable, please supply 'force=True' so that it"
% [: ]/ n' ?2 K) I" s4 T' e+ f2 a1 U " is clear you understand the risks."), C6 A6 e( Y, P m# g7 q
self._action(7 \7 ?) K+ `8 q
session, {'os-migrateLive': body}, microversion=microversion); }! j7 m3 K" o; R
def _live_migrate(self, session, host, force, block_migration,
6 y: m# ^% ], t" f6 d7 c disk_over_commit):
) M+ O$ {9 K2 |* D6 w6 P: r microversion = None
$ s5 v* u% k. X5 l: R body = {3 a! p, @. t! t) S
'host': None,+ g& Z+ w! F+ w! |+ C
}6 I+ @% u9 H* ?1 `7 \2 p+ W3 Y
if block_migration == 'auto':0 ^0 t0 x7 N5 R7 W
raise ValueError(6 [: w8 |& u @
"Live migration on this cloud does not support 'auto' as"
, e8 ^% s) e: n: w3 c* z " a parameter to block_migration, but only True and False.")
8 p8 D4 P% h# X# P- l6 M4 _0 o8 ? body['block_migration'] = block_migration or False
$ L6 H' y4 U$ C# k- L' g1 Z n body['disk_over_commit'] = disk_over_commit or False4 k( e0 z) ^% j9 e) V2 n) E
if host:
2 B( S5 Y' S) |6 i7 }4 j body['host'] = host$ n+ \5 X: U( e
if not force: U" Q# c6 O$ C5 j/ k
raise ValueError(' j5 ]9 ?; N; r1 {6 W
"Live migration on this cloud implies 'force'"
" K, C8 T( K" G2 O " if the 'host' option has been given and it is not"
) y, F5 I5 c K7 }6 f0 N6 Q; p' D " possible to disable. It is recommended to not use 'host'"' a7 u5 b# P: F& w# S5 k
" at all on this cloud as it is inherently unsafe, but if"2 P) a( J/ ?; g" K6 ?0 S+ T8 o4 R
" it is unavoidable, please supply 'force=True' so that it"' N' n. l% L. ?4 N6 m w$ P
" is clear you understand the risks.")
% z4 i) j+ O, {% e! H' i self._action(
0 p( G9 _ C* p' ? {, F/ N session, {'os-migrateLive': body}, microversion=microversion)/ U4 u4 Q* |6 p$ X( O8 R$ n
def fetch_security_groups(self, session):# J! d! u6 E/ d1 r( s- @0 X( X! ?
"""Fetch security groups of a server.
! C i" n& {& o :returns: Updated Server instance.0 I9 v2 o, a: Y7 O
"""; [* F) s5 ?6 H
url = utils.urljoin(Server.base_path, self.id, 'os-security-groups')
0 g" x! @! v, u) E: K# W/ Y. g- O response = session.get(url)& `5 C% Y/ N8 W0 X4 V7 \- A, d
exceptions.raise_from_response(response)
4 O+ H( q \. T( w try:
! d% ]8 ~2 c E8 f) t. o3 s7 U8 |8 O data = response.json()& @9 i# i% ~1 J7 M. @' h1 D; W
if 'security_groups' in data:, X3 b6 e0 e5 E# x
self.security_groups = data['security_groups']" u1 i4 L- b' h( h8 Y
except ValueError:
; A/ s5 J1 Q0 v2 p- D pass) q5 S; L; K. m. b8 b
return self
. h: n- s% F0 ~3 w2 Z' z9 C
5 N9 q7 l9 G' L/ QServerDetail = Server
/ a2 b/ T$ F$ M- d* K' p( s. ~7 c7 k
9 n4 s0 f# {! J/ l D |
|