blob: cebfa593de4b2f933788e538898b4b00d2f069f0 [file] [log] [blame]
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -08001package servers
2
3import (
Ash Wilson6a310e02014-09-29 08:24:02 -04004 "encoding/base64"
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -07005 "fmt"
Ash Wilson01626a32014-09-17 10:38:07 -04006
Jon Perritt30558642014-04-14 17:07:12 -05007 "github.com/racker/perigee"
Ash Wilson01626a32014-09-17 10:38:07 -04008 "github.com/rackspace/gophercloud"
9 "github.com/rackspace/gophercloud/pagination"
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080010)
11
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080012// List makes a request against the API to list servers accessible to you.
Ash Wilson01626a32014-09-17 10:38:07 -040013func List(client *gophercloud.ServiceClient) pagination.Pager {
14 createPage := func(r pagination.LastHTTPResponse) pagination.Page {
Ash Wilson397c78b2014-09-25 15:19:14 -040015 return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080016 }
17
Jon Perritt703bfc02014-10-08 14:35:00 -050018 return pagination.NewPager(client, listDetailURL(client), createPage)
Samuel A. Falvo IIc007c272014-02-10 20:49:26 -080019}
20
Ash Wilson2206a112014-10-02 10:57:38 -040021// CreateOptsBuilder describes struct types that can be accepted by the Create call.
Ash Wilson6a310e02014-09-29 08:24:02 -040022// The CreateOpts struct in this package does.
Ash Wilson2206a112014-10-02 10:57:38 -040023type CreateOptsBuilder interface {
Ash Wilsone45c9732014-09-29 10:54:12 -040024 ToServerCreateMap() map[string]interface{}
Ash Wilson6a310e02014-09-29 08:24:02 -040025}
26
27// Network is used within CreateOpts to control a new server's network attachments.
28type Network struct {
29 // UUID of a nova-network to attach to the newly provisioned server.
30 // Required unless Port is provided.
31 UUID string
32
33 // Port of a neutron network to attach to the newly provisioned server.
34 // Required unless UUID is provided.
35 Port string
36
37 // FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
38 FixedIP string
39}
40
41// CreateOpts specifies server creation parameters.
42type CreateOpts struct {
43 // Name [required] is the name to assign to the newly launched server.
44 Name string
45
46 // ImageRef [required] is the ID or full URL to the image that contains the server's OS and initial state.
47 // Optional if using the boot-from-volume extension.
48 ImageRef string
49
50 // FlavorRef [required] is the ID or full URL to the flavor that describes the server's specs.
51 FlavorRef string
52
53 // SecurityGroups [optional] lists the names of the security groups to which this server should belong.
54 SecurityGroups []string
55
56 // UserData [optional] contains configuration information or scripts to use upon launch.
57 // Create will base64-encode it for you.
58 UserData []byte
59
60 // AvailabilityZone [optional] in which to launch the server.
61 AvailabilityZone string
62
63 // Networks [optional] dictates how this server will be attached to available networks.
64 // By default, the server will be attached to all isolated networks for the tenant.
65 Networks []Network
66
67 // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
68 Metadata map[string]string
69
70 // Personality [optional] includes the path and contents of a file to inject into the server at launch.
71 // The maximum size of the file is 255 bytes (decoded).
72 Personality []byte
73
74 // ConfigDrive [optional] enables metadata injection through a configuration drive.
75 ConfigDrive bool
76}
77
Ash Wilsone45c9732014-09-29 10:54:12 -040078// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
79func (opts CreateOpts) ToServerCreateMap() map[string]interface{} {
Ash Wilson6a310e02014-09-29 08:24:02 -040080 server := make(map[string]interface{})
81
82 server["name"] = opts.Name
83 server["imageRef"] = opts.ImageRef
84 server["flavorRef"] = opts.FlavorRef
85
86 if opts.UserData != nil {
87 encoded := base64.StdEncoding.EncodeToString(opts.UserData)
88 server["user_data"] = &encoded
89 }
90 if opts.Personality != nil {
91 encoded := base64.StdEncoding.EncodeToString(opts.Personality)
92 server["personality"] = &encoded
93 }
94 if opts.ConfigDrive {
95 server["config_drive"] = "true"
96 }
97 if opts.AvailabilityZone != "" {
98 server["availability_zone"] = opts.AvailabilityZone
99 }
100 if opts.Metadata != nil {
101 server["metadata"] = opts.Metadata
102 }
103
104 if len(opts.SecurityGroups) > 0 {
105 securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
106 for i, groupName := range opts.SecurityGroups {
107 securityGroups[i] = map[string]interface{}{"name": groupName}
108 }
109 }
110 if len(opts.Networks) > 0 {
111 networks := make([]map[string]interface{}, len(opts.Networks))
112 for i, net := range opts.Networks {
113 networks[i] = make(map[string]interface{})
114 if net.UUID != "" {
115 networks[i]["uuid"] = net.UUID
116 }
117 if net.Port != "" {
118 networks[i]["port"] = net.Port
119 }
120 if net.FixedIP != "" {
121 networks[i]["fixed_ip"] = net.FixedIP
122 }
123 }
124 }
125
126 return map[string]interface{}{"server": server}
127}
128
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800129// Create requests a server to be provisioned to the user in the current tenant.
Ash Wilson2206a112014-10-02 10:57:38 -0400130func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400131 var result CreateResult
Ash Wilson31f6bde2014-09-25 14:52:12 -0400132 _, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
Ash Wilson6a310e02014-09-29 08:24:02 -0400133 Results: &result.Resp,
Ash Wilsone45c9732014-09-29 10:54:12 -0400134 ReqBody: opts.ToServerCreateMap(),
Ash Wilson01626a32014-09-17 10:38:07 -0400135 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIe246ac02014-02-13 23:20:09 -0800136 OkCodes: []int{202},
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800137 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400138 return result
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800139}
140
141// Delete requests that a server previously provisioned be removed from your account.
Ash Wilson01626a32014-09-17 10:38:07 -0400142func Delete(client *gophercloud.ServiceClient, id string) error {
Jon Perritt703bfc02014-10-08 14:35:00 -0500143 _, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
Ash Wilson01626a32014-09-17 10:38:07 -0400144 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIe246ac02014-02-13 23:20:09 -0800145 OkCodes: []int{204},
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800146 })
147 return err
148}
149
Ash Wilson7ddf0362014-09-17 10:59:09 -0400150// Get requests details on a single server, by ID.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400151func Get(client *gophercloud.ServiceClient, id string) GetResult {
152 var result GetResult
Jon Perritt703bfc02014-10-08 14:35:00 -0500153 _, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400154 Results: &result.Resp,
Ash Wilson01626a32014-09-17 10:38:07 -0400155 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800156 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400157 return result
Samuel A. Falvo IIce000732014-02-13 18:53:53 -0800158}
159
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400160// UpdateOptsLike allows extentions to add additional attributes to the Update request.
161type UpdateOptsLike interface {
Ash Wilsone45c9732014-09-29 10:54:12 -0400162 ToServerUpdateMap() map[string]interface{}
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400163}
164
165// UpdateOpts specifies the base attributes that may be updated on an existing server.
166type UpdateOpts struct {
167 // Name [optional] changes the displayed name of the server.
168 // The server host name will *not* change.
169 // Server names are not constrained to be unique, even within the same tenant.
170 Name string
171
172 // AccessIPv4 [optional] provides a new IPv4 address for the instance.
173 AccessIPv4 string
174
175 // AccessIPv6 [optional] provides a new IPv6 address for the instance.
176 AccessIPv6 string
177}
178
Ash Wilsone45c9732014-09-29 10:54:12 -0400179// ToServerUpdateMap formats an UpdateOpts structure into a request body.
180func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400181 server := make(map[string]string)
182 if opts.Name != "" {
183 server["name"] = opts.Name
184 }
185 if opts.AccessIPv4 != "" {
186 server["accessIPv4"] = opts.AccessIPv4
187 }
188 if opts.AccessIPv6 != "" {
189 server["accessIPv6"] = opts.AccessIPv6
190 }
191 return map[string]interface{}{"server": server}
192}
193
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800194// Update requests that various attributes of the indicated server be changed.
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400195func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400196 var result UpdateResult
Jon Perritt703bfc02014-10-08 14:35:00 -0500197 _, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
Ash Wilsondcbc8fb2014-09-29 09:05:44 -0400198 Results: &result.Resp,
Ash Wilsone45c9732014-09-29 10:54:12 -0400199 ReqBody: opts.ToServerUpdateMap(),
Ash Wilson01626a32014-09-17 10:38:07 -0400200 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800201 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400202 return result
Samuel A. Falvo II22d3b772014-02-13 19:27:05 -0800203}
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700204
Ash Wilson01626a32014-09-17 10:38:07 -0400205// ChangeAdminPassword alters the administrator or root password for a specified server.
206func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) error {
Ash Wilsondc7daa82014-09-23 16:34:42 -0400207 var req struct {
208 ChangePassword struct {
209 AdminPass string `json:"adminPass"`
210 } `json:"changePassword"`
211 }
212
213 req.ChangePassword.AdminPass = newPassword
214
Ash Wilson31f6bde2014-09-25 14:52:12 -0400215 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Ash Wilsondc7daa82014-09-23 16:34:42 -0400216 ReqBody: req,
Ash Wilson01626a32014-09-17 10:38:07 -0400217 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500218 OkCodes: []int{202},
Samuel A. Falvo IIca5f9a32014-03-11 17:52:58 -0700219 })
220 return err
221}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700222
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700223// ErrArgument errors occur when an argument supplied to a package function
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700224// fails to fall within acceptable values. For example, the Reboot() function
225// expects the "how" parameter to be one of HardReboot or SoftReboot. These
226// constants are (currently) strings, leading someone to wonder if they can pass
227// other string values instead, perhaps in an effort to break the API of their
228// provider. Reboot() returns this error in this situation.
229//
230// Function identifies which function was called/which function is generating
231// the error.
232// Argument identifies which formal argument was responsible for producing the
233// error.
234// Value provides the value as it was passed into the function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700235type ErrArgument struct {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700236 Function, Argument string
Jon Perritt30558642014-04-14 17:07:12 -0500237 Value interface{}
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700238}
239
240// Error yields a useful diagnostic for debugging purposes.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700241func (e *ErrArgument) Error() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700242 return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
243}
244
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700245func (e *ErrArgument) String() string {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700246 return e.Error()
247}
248
Ash Wilson01626a32014-09-17 10:38:07 -0400249// RebootMethod describes the mechanisms by which a server reboot can be requested.
250type RebootMethod string
251
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700252// These constants determine how a server should be rebooted.
253// See the Reboot() function for further details.
254const (
Ash Wilson01626a32014-09-17 10:38:07 -0400255 SoftReboot RebootMethod = "SOFT"
256 HardReboot RebootMethod = "HARD"
257 OSReboot = SoftReboot
258 PowerCycle = HardReboot
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700259)
260
261// Reboot requests that a given server reboot.
262// Two methods exist for rebooting a server:
263//
Ash Wilson01626a32014-09-17 10:38:07 -0400264// HardReboot (aka PowerCycle) restarts the server instance by physically cutting power to the machine, or if a VM,
265// terminating it at the hypervisor level.
266// It's done. Caput. Full stop.
267// Then, after a brief while, power is restored or the VM instance restarted.
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700268//
Ash Wilson01626a32014-09-17 10:38:07 -0400269// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
270// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
271func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) error {
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700272 if (how != SoftReboot) && (how != HardReboot) {
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700273 return &ErrArgument{
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700274 Function: "Reboot",
275 Argument: "how",
Jon Perritt30558642014-04-14 17:07:12 -0500276 Value: how,
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700277 }
278 }
Jon Perritt30558642014-04-14 17:07:12 -0500279
Ash Wilson31f6bde2014-09-25 14:52:12 -0400280 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500281 ReqBody: struct {
282 C map[string]string `json:"reboot"`
283 }{
Ash Wilson01626a32014-09-17 10:38:07 -0400284 map[string]string{"type": string(how)},
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700285 },
Ash Wilson01626a32014-09-17 10:38:07 -0400286 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500287 OkCodes: []int{202},
Samuel A. Falvo II41c9f612014-03-11 19:00:10 -0700288 })
289 return err
290}
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700291
Ash Wilson01626a32014-09-17 10:38:07 -0400292// Rebuild requests that the Openstack provider reprovision the server.
293// The rebuild will need to know the server's name and new image reference or ID.
294// In addition, and unlike building a server with Create(), you must provide an administrator password.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700295//
296// Additional options may be specified with the additional map.
297// This function treats a nil map the same as an empty map.
298//
Ash Wilson01626a32014-09-17 10:38:07 -0400299// Rebuild returns a server result as though you had called GetDetail() on the server's ID.
300// The information, however, refers to the new server, not the old.
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400301func Rebuild(client *gophercloud.ServiceClient, id, name, password, imageRef string, additional map[string]interface{}) RebuildResult {
302 var result RebuildResult
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700303
304 if id == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400305 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700306 Function: "Rebuild",
307 Argument: "id",
Jon Perritt30558642014-04-14 17:07:12 -0500308 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700309 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400310 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700311 }
312
313 if name == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400314 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700315 Function: "Rebuild",
316 Argument: "name",
Jon Perritt30558642014-04-14 17:07:12 -0500317 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700318 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400319 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700320 }
321
322 if password == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400323 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700324 Function: "Rebuild",
325 Argument: "password",
Jon Perritt30558642014-04-14 17:07:12 -0500326 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700327 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400328 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700329 }
330
331 if imageRef == "" {
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400332 result.Err = &ErrArgument{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700333 Function: "Rebuild",
334 Argument: "imageRef",
Jon Perritt30558642014-04-14 17:07:12 -0500335 Value: "",
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700336 }
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400337 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700338 }
339
340 if additional == nil {
341 additional = make(map[string]interface{}, 0)
342 }
343
344 additional["name"] = name
345 additional["imageRef"] = imageRef
346 additional["adminPass"] = password
347
Ash Wilson31f6bde2014-09-25 14:52:12 -0400348 _, result.Err = perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500349 ReqBody: struct {
350 R map[string]interface{} `json:"rebuild"`
351 }{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700352 additional,
353 },
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400354 Results: &result.Resp,
Ash Wilson01626a32014-09-17 10:38:07 -0400355 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500356 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700357 })
Ash Wilsond27e0ff2014-09-25 11:50:31 -0400358 return result
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700359}
360
361// Resize instructs the provider to change the flavor of the server.
Ash Wilson01626a32014-09-17 10:38:07 -0400362// Note that this implies rebuilding it.
363// Unfortunately, one cannot pass rebuild parameters to the resize function.
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700364// When the resize completes, the server will be in RESIZE_VERIFY state.
365// While in this state, you can explore the use of the new server's configuration.
366// If you like it, call ConfirmResize() to commit the resize permanently.
367// Otherwise, call RevertResize() to restore the old configuration.
Ash Wilson01626a32014-09-17 10:38:07 -0400368func Resize(client *gophercloud.ServiceClient, id, flavorRef string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400369 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500370 ReqBody: struct {
371 R map[string]interface{} `json:"resize"`
372 }{
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700373 map[string]interface{}{"flavorRef": flavorRef},
374 },
Ash Wilson01626a32014-09-17 10:38:07 -0400375 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500376 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700377 })
378 return err
379}
380
381// ConfirmResize confirms a previous resize operation on a server.
382// See Resize() for more details.
Ash Wilson01626a32014-09-17 10:38:07 -0400383func ConfirmResize(client *gophercloud.ServiceClient, id string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400384 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500385 ReqBody: map[string]interface{}{"confirmResize": nil},
Ash Wilson01626a32014-09-17 10:38:07 -0400386 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500387 OkCodes: []int{204},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700388 })
389 return err
390}
391
392// RevertResize cancels a previous resize operation on a server.
393// See Resize() for more details.
Ash Wilson01626a32014-09-17 10:38:07 -0400394func RevertResize(client *gophercloud.ServiceClient, id string) error {
Ash Wilson31f6bde2014-09-25 14:52:12 -0400395 _, err := perigee.Request("POST", actionURL(client, id), perigee.Options{
Jon Perritt30558642014-04-14 17:07:12 -0500396 ReqBody: map[string]interface{}{"revertResize": nil},
Ash Wilson01626a32014-09-17 10:38:07 -0400397 MoreHeaders: client.Provider.AuthenticatedHeaders(),
Jon Perritt30558642014-04-14 17:07:12 -0500398 OkCodes: []int{202},
Samuel A. Falvo II808bb632014-03-12 00:07:50 -0700399 })
400 return err
401}