github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/rkt/api_service.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "log" 21 "net" 22 "os" 23 "os/signal" 24 "path" 25 "path/filepath" 26 "strings" 27 "syscall" 28 29 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema" 30 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types" 31 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra" 32 "github.com/coreos/rkt/Godeps/_workspace/src/golang.org/x/net/context" 33 "github.com/coreos/rkt/Godeps/_workspace/src/google.golang.org/grpc" 34 "github.com/coreos/rkt/api/v1alpha" 35 "github.com/coreos/rkt/common" 36 "github.com/coreos/rkt/store" 37 "github.com/coreos/rkt/version" 38 ) 39 40 var ( 41 supportedAPIVersion = "1.0.0-alpha" 42 cmdAPIService = &cobra.Command{ 43 Use: "api-service [--listen-client-url=localhost:15441]", 44 Short: "Run API service (experimental, DO NOT USE IT)", 45 Run: runWrapper(runAPIService), 46 } 47 48 flagAPIServiceListenClientURL string 49 ) 50 51 func init() { 52 cmdRkt.AddCommand(cmdAPIService) 53 cmdAPIService.Flags().StringVar(&flagAPIServiceListenClientURL, "--listen-client-url", common.APIServiceListenClientURL, "address to listen on client API requests") 54 } 55 56 // v1AlphaAPIServer implements v1Alpha.APIServer interface. 57 type v1AlphaAPIServer struct { 58 store *store.Store 59 } 60 61 var _ v1alpha.PublicAPIServer = &v1AlphaAPIServer{} 62 63 func newV1AlphaAPIServer() (*v1AlphaAPIServer, error) { 64 s, err := store.NewStore(globalFlags.Dir) 65 if err != nil { 66 return nil, err 67 } 68 69 return &v1AlphaAPIServer{ 70 store: s, 71 }, nil 72 } 73 74 // GetInfo returns the information about the rkt, appc, api server version. 75 func (s *v1AlphaAPIServer) GetInfo(context.Context, *v1alpha.GetInfoRequest) (*v1alpha.GetInfoResponse, error) { 76 return &v1alpha.GetInfoResponse{ 77 Info: &v1alpha.Info{ 78 RktVersion: version.Version, 79 AppcVersion: schema.AppContainerVersion.String(), 80 ApiVersion: supportedAPIVersion, 81 }, 82 }, nil 83 } 84 85 type valueGetter interface { 86 Get(string) (string, bool) 87 } 88 89 // containsKeyValue returns true if the actualKVs contains any of the key-value 90 // pairs listed in requiredKVs, otherwise it returns false. 91 func containsKeyValue(actualKVs valueGetter, requiredKVs []*v1alpha.KeyValue) bool { 92 for _, requiredKV := range requiredKVs { 93 actualValue, ok := actualKVs.Get(requiredKV.Key) 94 if ok && actualValue == requiredKV.Value { 95 return true 96 } 97 } 98 return false 99 } 100 101 // containsString tries to find a string in a string array which satisfies the checkFunc. 102 // The checkFunc takes two strings, and returns whether the two strings satisfy the 103 // given condition. 104 func containsString(needle string, haystack []string, checkFunc func(a, b string) bool) bool { 105 for _, v := range haystack { 106 if checkFunc(needle, v) { 107 return true 108 } 109 } 110 return false 111 } 112 113 // stringsEqual returns true if two strings are equal. 114 func stringsEqual(a, b string) bool { 115 return a == b 116 } 117 118 // hasBaseName returns true if the second string is the base name of 119 // the first string. 120 func hasBaseName(name, baseName string) bool { 121 return path.Base(name) == baseName 122 } 123 124 // hasIntersection returns true if there's any two-string pair from array a and 125 // array b that satisfy the checkFunc. 126 // 127 // e.g. if a = {"a", "b", "c"}, b = {"c", "d", "e"} and c = {"e", "f", "g"}, 128 // then hasIntersection(a, b, stringsEqual) == true, 129 // hasIntersection(a, c, stringsEqual) == false, 130 // containsAnystring(b, c, stringsEqual) == true. 131 // 132 func hasIntersection(a, b []string, checkFunc func(a, b string) bool) bool { 133 for _, aa := range a { 134 if containsString(aa, b, checkFunc) { 135 return true 136 } 137 } 138 return false 139 } 140 141 // filterPod returns true if the pod doesn't satisfy the filter, which means 142 // it should be filtered and not be returned. 143 // It returns false if the filter is nil or the pod satisfies the filter, which 144 // means it should be returned. 145 func filterPod(pod *v1alpha.Pod, manifest *schema.PodManifest, filter *v1alpha.PodFilter) bool { 146 // No filters, return directly. 147 if filter == nil { 148 return false 149 } 150 151 // Filter according to the id. 152 if len(filter.Ids) > 0 { 153 if !containsString(pod.Id, filter.Ids, stringsEqual) { 154 return true 155 } 156 } 157 158 // Filter according to the state. 159 if len(filter.States) > 0 { 160 foundState := false 161 for _, state := range filter.States { 162 if pod.State == state { 163 foundState = true 164 break 165 } 166 } 167 if !foundState { 168 return true 169 } 170 } 171 172 // Filter according to the app names. 173 if len(filter.AppNames) > 0 { 174 var names []string 175 for _, app := range pod.Apps { 176 names = append(names, app.Name) 177 } 178 if !hasIntersection(names, filter.AppNames, stringsEqual) { 179 return true 180 } 181 } 182 183 // Filter according to the image IDs. 184 if len(filter.ImageIds) > 0 { 185 var ids []string 186 for _, app := range pod.Apps { 187 ids = append(ids, app.Image.Id) 188 } 189 if !hasIntersection(ids, filter.ImageIds, stringsEqual) { 190 return true 191 } 192 } 193 194 // Filter according to the network names. 195 if len(filter.NetworkNames) > 0 { 196 var names []string 197 for _, network := range pod.Networks { 198 names = append(names, network.Name) 199 } 200 if !hasIntersection(names, filter.NetworkNames, stringsEqual) { 201 return true 202 } 203 } 204 205 // Filter according to the annotations. 206 if len(filter.Annotations) > 0 { 207 if !containsKeyValue(manifest.Annotations, filter.Annotations) { 208 return true 209 } 210 } 211 212 return false 213 } 214 215 // getPodManifest returns the pod manifest of the pod. 216 // Both marshaled and unmarshaled manifest are returned. 217 func getPodManifest(p *pod) (*schema.PodManifest, []byte, error) { 218 data, err := p.readFile("pod") 219 if err != nil { 220 log.Printf("Failed to read pod manifest for pod %q: %v", p.uuid, err) 221 return nil, nil, err 222 } 223 224 var manifest schema.PodManifest 225 if err := json.Unmarshal(data, &manifest); err != nil { 226 log.Printf("Failed to unmarshal pod manifest for pod %q: %v", p.uuid, err) 227 return nil, nil, err 228 } 229 return &manifest, data, nil 230 } 231 232 // getPodState returns the pod's state. 233 func getPodState(p *pod) v1alpha.PodState { 234 switch p.getState() { 235 case Embryo: 236 return v1alpha.PodState_POD_STATE_EMBRYO 237 case Preparing: 238 return v1alpha.PodState_POD_STATE_PREPARING 239 case AbortedPrepare: 240 return v1alpha.PodState_POD_STATE_ABORTED_PREPARE 241 case Prepared: 242 return v1alpha.PodState_POD_STATE_PREPARED 243 case Running: 244 return v1alpha.PodState_POD_STATE_RUNNING 245 case Deleting: 246 return v1alpha.PodState_POD_STATE_DELETING 247 case Exited: 248 return v1alpha.PodState_POD_STATE_EXITED 249 case Garbage: 250 return v1alpha.PodState_POD_STATE_GARBAGE 251 default: 252 return v1alpha.PodState_POD_STATE_UNDEFINED 253 } 254 } 255 256 // getApplist returns a list of apps in the pod. 257 func getApplist(p *pod) ([]*v1alpha.App, error) { 258 var apps []*v1alpha.App 259 applist, err := p.getApps() 260 if err != nil { 261 log.Printf("Failed to get app list for pod %q: %v", p.uuid, err) 262 return nil, err 263 } 264 265 for _, app := range applist { 266 img := &v1alpha.Image{ 267 BaseFormat: &v1alpha.ImageFormat{ 268 // Only support appc image now. If it's a docker image, then it 269 // will be transformed to appc before storing in the disk store. 270 Type: v1alpha.ImageType_IMAGE_TYPE_APPC, 271 Version: schema.AppContainerVersion.String(), 272 }, 273 Id: app.Image.ID.String(), 274 // Only image format and image ID are returned in 'ListPods()'. 275 } 276 277 apps = append(apps, &v1alpha.App{ 278 Name: app.Name.String(), 279 Image: img, 280 // State and exit code are not returned in 'ListPods()'. 281 }) 282 } 283 return apps, nil 284 } 285 286 // getPodPid returns the pid of the pod if it's running, otherwise returns -1. 287 func getPodPid(p *pod) (int32, error) { 288 var pid int32 = -1 289 if p.getState() == Running { 290 podpid, err := p.getPID() 291 if err != nil { 292 log.Printf("Failed to get PID for pod %q: %v", p.uuid, err) 293 return -1, err 294 } 295 pid = int32(podpid) 296 } 297 return pid, nil 298 } 299 300 // getNetworks returns the list of the info of the network that the pod belongs to. 301 func getNetworks(p *pod) []*v1alpha.Network { 302 var networks []*v1alpha.Network 303 for _, n := range p.nets { 304 networks = append(networks, &v1alpha.Network{ 305 Name: n.NetName, 306 // There will be IPv6 support soon so distinguish between v4 and v6 307 Ipv4: n.IP.String(), 308 }) 309 } 310 return networks 311 } 312 313 // getBasicPod returns *v1alpha.Pod with basic pod information, it also returns a *schema.PodManifest 314 // object. 315 func getBasicPod(p *pod) (*v1alpha.Pod, *schema.PodManifest, error) { 316 manifest, data, err := getPodManifest(p) 317 if err != nil { 318 return nil, nil, err 319 } 320 321 pid, err := getPodPid(p) 322 if err != nil { 323 return nil, nil, err 324 } 325 326 apps, err := getApplist(p) 327 if err != nil { 328 return nil, nil, err 329 } 330 331 return &v1alpha.Pod{ 332 Id: p.uuid.String(), 333 Pid: pid, 334 State: getPodState(p), // Get pod's state. 335 Apps: apps, 336 Manifest: data, 337 Networks: getNetworks(p), // Get pod's network. 338 }, manifest, nil 339 } 340 341 func (s *v1AlphaAPIServer) ListPods(ctx context.Context, request *v1alpha.ListPodsRequest) (*v1alpha.ListPodsResponse, error) { 342 var pods []*v1alpha.Pod 343 if err := walkPods(includeMostDirs, func(p *pod) { 344 pod, manifest, err := getBasicPod(p) 345 if err != nil { // Do not return partial pods. 346 return 347 } 348 349 if !filterPod(pod, manifest, request.Filter) { 350 pod.Manifest = nil // Do not return pod manifest in ListPods(). 351 pods = append(pods, pod) 352 } 353 }); err != nil { 354 log.Printf("Failed to list pod: %v", err) 355 return nil, err 356 } 357 return &v1alpha.ListPodsResponse{Pods: pods}, nil 358 } 359 360 // getImageInfo for a given image ID, returns the *v1alpha.Image object. 361 // 362 // FIXME(yifan): We should get the image manifest from the tree store. 363 // See https://github.com/coreos/rkt/issues/1659 364 func getImageInfo(store *store.Store, imageID string) (*v1alpha.Image, error) { 365 aciInfo, err := store.GetACIInfoWithBlobKey(imageID) 366 if err != nil { 367 log.Printf("Failed to get ACI info for image ID %q: %v", imageID, err) 368 return nil, err 369 } 370 371 image, _, err := aciInfoToV1AlphaAPIImage(store, aciInfo) 372 if err != nil { 373 log.Printf("Failed to convert ACI to v1alphaAPIImage for image ID %q: %v", imageID, err) 374 return nil, err 375 } 376 return image, nil 377 } 378 379 // fillAppInfo fills the apps' state and image info of the pod. 380 func fillAppInfo(store *store.Store, p *pod, v1pod *v1alpha.Pod) error { 381 statusDir, err := p.getStatusDir() 382 if err != nil { 383 log.Printf("Failed to get pod exit status directory: %v", err) 384 return err 385 } 386 387 for _, app := range v1pod.Apps { 388 // Fill the image info in details. 389 image, err := getImageInfo(store, app.Image.Id) 390 if err != nil { 391 return err 392 } 393 image.Manifest = nil // Do not return image manifest in ListPod()/InspectPod(). 394 app.Image = image 395 396 // Fill app's state and exit code. 397 value, err := p.readIntFromFile(filepath.Join(statusDir, app.Name)) 398 if err == nil { 399 app.State = v1alpha.AppState_APP_STATE_EXITED 400 app.ExitCode = int32(value) 401 continue 402 } 403 404 if !os.IsNotExist(err) { 405 log.Printf("Failed to read status for app %q: %v", app.Name, err) 406 return err 407 } 408 // If status file does not exit, that means the 409 // app is either running or aborted. 410 // 411 // FIXME(yifan): This is not acttually true, the app can be aborted while 412 // the pod is still running if the spec changes. 413 switch p.getState() { 414 case Running: 415 app.State = v1alpha.AppState_APP_STATE_RUNNING 416 default: 417 app.State = v1alpha.AppState_APP_STATE_UNDEFINED 418 } 419 420 } 421 return nil 422 } 423 424 func (s *v1AlphaAPIServer) InspectPod(ctx context.Context, request *v1alpha.InspectPodRequest) (*v1alpha.InspectPodResponse, error) { 425 uuid, err := types.NewUUID(request.Id) 426 if err != nil { 427 log.Printf("Invalid pod id %q: %v", request.Id, err) 428 return nil, err 429 } 430 431 p, err := getPod(uuid) 432 if err != nil { 433 log.Printf("Failed to get pod %q: %v", request.Id, err) 434 return nil, err 435 } 436 defer p.Close() 437 438 pod, _, err := getBasicPod(p) 439 if err != nil { 440 return nil, err 441 } 442 443 // Fill the extra pod info that is not available in ListPods(). 444 if err := fillAppInfo(s.store, p, pod); err != nil { 445 return nil, err 446 } 447 448 return &v1alpha.InspectPodResponse{Pod: pod}, nil 449 } 450 451 // aciInfoToV1AlphaAPIImage takes an aciInfo object and construct the v1alpha.Image object. 452 // It also returns the image manifest of the image. 453 func aciInfoToV1AlphaAPIImage(store *store.Store, aciInfo *store.ACIInfo) (*v1alpha.Image, *schema.ImageManifest, error) { 454 manifest, err := store.GetImageManifestJSON(aciInfo.BlobKey) 455 if err != nil { 456 log.Printf("Failed to read the image manifest: %v", err) 457 return nil, nil, err 458 } 459 460 var im schema.ImageManifest 461 if err = json.Unmarshal(manifest, &im); err != nil { 462 log.Printf("Failed to unmarshal image manifest: %v", err) 463 return nil, nil, err 464 } 465 466 version, ok := im.Labels.Get("version") 467 if !ok { 468 version = "latest" 469 } 470 471 return &v1alpha.Image{ 472 BaseFormat: &v1alpha.ImageFormat{ 473 // Only support appc image now. If it's a docker image, then it 474 // will be transformed to appc before storing in the disk store. 475 Type: v1alpha.ImageType_IMAGE_TYPE_APPC, 476 Version: schema.AppContainerVersion.String(), 477 }, 478 Id: aciInfo.BlobKey, 479 Name: im.Name.String(), 480 Version: version, 481 ImportTimestamp: aciInfo.ImportTime.Unix(), 482 Manifest: manifest, 483 }, &im, nil 484 } 485 486 // filterImage returns true if the image doesn't satisfy the filter, which means 487 // it should be filtered and not be returned. 488 // It returns false if the filter is nil or the pod satisfies the filter, which means 489 // it should be returned. 490 func filterImage(image *v1alpha.Image, manifest *schema.ImageManifest, filter *v1alpha.ImageFilter) bool { 491 // No filters, return directly. 492 if filter == nil { 493 return false 494 } 495 496 // Filter according to the IDs. 497 if len(filter.Ids) > 0 { 498 if !containsString(image.Id, filter.Ids, stringsEqual) { 499 return true 500 } 501 } 502 503 // Filter according to the image name prefixes. 504 if len(filter.Prefixes) > 0 { 505 if !containsString(image.Name, filter.Prefixes, strings.HasPrefix) { 506 return true 507 } 508 } 509 510 // Filter according to the image base name. 511 if len(filter.BaseNames) > 0 { 512 if !containsString(image.Name, filter.BaseNames, hasBaseName) { 513 return true 514 } 515 } 516 517 // Filter according to the image keywords. 518 if len(filter.Keywords) > 0 { 519 if !containsString(image.Name, filter.Keywords, strings.Contains) { 520 return true 521 } 522 } 523 524 // Filter according to the imported time. 525 if filter.ImportedAfter > 0 { 526 if image.ImportTimestamp <= filter.ImportedAfter { 527 return true 528 } 529 } 530 if filter.ImportedBefore > 0 { 531 if image.ImportTimestamp >= filter.ImportedBefore { 532 return true 533 } 534 } 535 536 // Filter according to the image labels. 537 if len(filter.Labels) > 0 { 538 if !containsKeyValue(manifest.Labels, filter.Labels) { 539 return true 540 } 541 } 542 543 // Filter according to the annotations. 544 if len(filter.Annotations) > 0 { 545 if !containsKeyValue(manifest.Annotations, filter.Annotations) { 546 return true 547 } 548 } 549 550 return false 551 } 552 553 func (s *v1AlphaAPIServer) ListImages(ctx context.Context, request *v1alpha.ListImagesRequest) (*v1alpha.ListImagesResponse, error) { 554 aciInfos, err := s.store.GetAllACIInfos(nil, false) 555 if err != nil { 556 log.Printf("Failed to get all ACI infos: %v", err) 557 return nil, err 558 } 559 560 var images []*v1alpha.Image 561 for _, aciInfo := range aciInfos { 562 image, manifest, err := aciInfoToV1AlphaAPIImage(s.store, aciInfo) 563 if err != nil { 564 continue 565 } 566 if !filterImage(image, manifest, request.Filter) { 567 image.Manifest = nil // Do not return image manifest in ListImages(). 568 images = append(images, image) 569 } 570 } 571 return &v1alpha.ListImagesResponse{Images: images}, nil 572 } 573 574 func (s *v1AlphaAPIServer) InspectImage(ctx context.Context, request *v1alpha.InspectImageRequest) (*v1alpha.InspectImageResponse, error) { 575 image, err := getImageInfo(s.store, request.Id) 576 if err != nil { 577 return nil, err 578 } 579 return &v1alpha.InspectImageResponse{Image: image}, nil 580 } 581 582 func (s *v1AlphaAPIServer) GetLogs(request *v1alpha.GetLogsRequest, server v1alpha.PublicAPI_GetLogsServer) error { 583 return fmt.Errorf("not implemented yet") 584 } 585 586 func (s *v1AlphaAPIServer) ListenEvents(request *v1alpha.ListenEventsRequest, server v1alpha.PublicAPI_ListenEventsServer) error { 587 return fmt.Errorf("not implemented yet") 588 } 589 590 func runAPIService(cmd *cobra.Command, args []string) (exit int) { 591 log.Print("API service starting...") 592 593 tcpl, err := net.Listen("tcp", flagAPIServiceListenClientURL) 594 if err != nil { 595 stderr("api-service: %v", err) 596 return 1 597 } 598 defer tcpl.Close() 599 600 publicServer := grpc.NewServer() // TODO(yifan): Add TLS credential option. 601 602 v1AlphaAPIServer, err := newV1AlphaAPIServer() 603 if err != nil { 604 stderr("api-service: failed to create API service: %v", err) 605 return 1 606 } 607 608 v1alpha.RegisterPublicAPIServer(publicServer, v1AlphaAPIServer) 609 610 go publicServer.Serve(tcpl) 611 612 log.Printf("API service running on %v...", flagAPIServiceListenClientURL) 613 614 signal.Notify(exitCh, syscall.SIGINT, syscall.SIGTERM) 615 <-exitCh 616 617 log.Print("API service exiting...") 618 619 return 620 }