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  }