Skip to main content

the avatar of Federico Mena-Quintero

Exposing C and Rust APIs: some thoughts from librsvg

Librsvg exports two public APIs: the C API that is in turn available to other languages through GObject Introspection, and the Rust API.

You could call this a use of the facade pattern on top of the rsvg_internals crate. That crate is the actual implementation of librsvg, and exports an interface with many knobs that are not exposed from the public APIs. The knobs are to allow for the variations in each of those APIs.

This post is about some interesting things that have come up during the creation/separation of those public APIs, and the implications of having an internals library that implements both.

Initial code organization

When librsvg was being ported to Rust, it just had an rsvg_internals crate that compiled as a staticlib to a .a library, which was later linked into the final librsvg.so.

Eventually the code got to the point where it was feasible to port the toplevel C API to Rust. This was relatively easy to do, since everything else underneath was already in Rust. At that point I became interested in also having a Rust API for librsvg — first to port the test suite to Rust and be able to run tests in parallel, and then to actually have a public API in Rust with more modern idioms than the historical, GObject-based API in C.

Version 2.45.5, from February 2019, is the last release that only had a C API.

Most of the C API of librsvg is in the RsvgHandle class. An RsvgHandle gets loaded with SVG data from a file or a stream, and then gets rendered to a Cairo context. The naming of Rust source files more or less matched the C source files, so where there was rsvg-handle.c initially, later we had handle.rs with the Rustified part of that code.

So, handle.rs had the Rust internals of the RsvgHandle class, and a bunch of extern "C" functions callable from C. For example, for this function in the public C API:

void rsvg_handle_set_base_gfile (RsvgHandle *handle,
                                 GFile      *base_file);

The corresponding Rust implementation was this:

#[no_mangle]
pub unsafe extern "C" fn rsvg_handle_rust_set_base_gfile(
    raw_handle: *mut RsvgHandle,
    raw_gfile: *mut gio_sys::GFile,
) {
    let rhandle = get_rust_handle(raw_handle);        // 1

    assert!(!raw_gfile.is_null());                    // 2
    let file: gio::File = from_glib_none(raw_gfile);  // 3

    rhandle.set_base_gfile(&file);                    // 4
}
  1. Get the Rust struct corresponding to the C GObject.
  2. Check the arguments.
  3. Convert from C GObject reference to Rust reference.
  4. Call the actual implementation of set_base_gfile in the Rust struct.

You can see that this function takes in arguments with C types, and converts them to Rust types. It's basically just glue between the C code and the actual implementation.

Then, the actual implementation of set_base_gfile looked like this:

impl Handle {
    fn set_base_gfile(&self, file: &gio::File) {
        if let Some(uri) = file.get_uri() {
            self.set_base_url(&uri);
        } else {
            rsvg_g_warning("file has no URI; will not set the base URI");
        }
    }
}

This is an actual method for a Rust Handle struct, and takes Rust types as arguments — no conversions are necessary here. However, there is a pesky call to rsvg_g_warning, about which I'll talk later.

I found it cleanest, although not the shortest code, to structure things like this:

  • C code: bunch of stub functions where rsvg_blah just calls a corresponding rsvg_rust_blah.

  • Toplevel Rust code: bunch of #[no_mangle] unsafe extern "C" fn rust_blah() that convert from C argument types to Rust types, and call safe Rust functions — for librsvg, these happened to be methods for a struct. Before returning, the toplevel functions convert Rust return values to C return values, and do things like converting the Err(E) of a Result<> into a GError or a boolean or whatever the traditional C API required.

In the very first versions of the code where the public API was implemented in Rust, the extern "C" functions actually contained their implementation. However, after some refactoring, it turned out to be cleaner to leave those functions just with the task of converting C to Rust types and vice-versa, and put the actual implementation in very Rust-y code. This made it easier to keep the unsafe conversion code (unsafe because it deals with raw pointers coming from C) only in the toplevel functions.

Growing out a Rust API

This commit is where the new, public Rust API started. That commit just created a Cargo workspace with two crates; the rsvg_internals crate that we already had, and a librsvg_crate with the public Rust API.

The commits over the subsequent couple of months are of intense refactoring:

  • This commit moves the unsafe extern "C" functions to a separate c_api.rs source file. This leaves handle.rs with only the safe Rust implementation of the toplevel API, and c_api.rs with the unsafe entry points that mostly just convert argument types, return values, and errors.

  • The API primitives get expanded to allow for a public Rust API that is "hard to misuse" unlike the C API, which needs to be called in a certain order.

Needing to call a C macro

However, there was a little problem. The Rust code cannot call g_warning, a C macro in glib that prints a message to stderr or uses structured logging. Librsvg used that to signal conditions where something went (recoverably) wrong, but there was no way to return a proper error code to the caller — it's mainly used as a debugging aid.

This is what the rsvg_internals used to be able to call that C macro:

First, the C code exports a function that just calls the macro:

/* This function exists just so that we can effectively call g_warning() from Rust,
 * since glib-rs doesn't bind the g_log functions yet.
 */
void
rsvg_g_warning_from_c(const char *msg)
{
    g_warning ("%s", msg);
}

Second, the Rust code binds that function to be callable from Rust:

pub fn rsvg_g_warning(msg: &str) {
    extern "C" {
        fn rsvg_g_warning_from_c(msg: *const libc::c_char);
    }

    unsafe {
        rsvg_g_warning_from_c(msg.to_glib_none().0);
    }
}

However! Since the standalone librsvg_crate does not link to the C code from the public librsvg.so, the helper rsvg_g_warning_from_c is not available!

A configuration feature for the internals library

And yet! Those warnings are only meaningful for the C API, which is not able to return error codes from all situations. However, the Rust API is able to do that, and so doesn't need the warnings printed to stderr. My first solution was to add a build-time option for whether the rsvg_internals library is being build for the C library, or for the Rust one.

In case we are building for the C library, the code calls rsvg_g_warning_from_c as usual.

But in case we are building for the Rust library, that code is a no-op.

This is the bit in rsvg_internals/Cargo.toml to declare the feature:

[features]
# Enables calling g_warning() when built as part of librsvg.so
c-library = []

And this is the corresponding code:

#[cfg(feature = "c-library")]
pub fn rsvg_g_warning(msg: &str) {
    unsafe {
        extern "C" {
            fn rsvg_g_warning_from_c(msg: *const libc::c_char);
        }

        rsvg_g_warning_from_c(msg.to_glib_none().0);
    }
}

#[cfg(not(feature = "c-library"))]
pub fn rsvg_g_warning(_msg: &str) {
    // The only callers of this are in handle.rs. When those functions
    // are called from the Rust API, they are able to return a
    // meaningful error code, but the C API isn't - so they issues a
    // g_warning() instead.
}

The first function is the one that is compiled when the c-library feature is enabled; this happens when building rsvg_internals to link into librsvg.so.

The second function does nothing; it is what is compiled when rsvg_internals is being used just from the librsvg_crate crate with the Rust API.

While this worked well, it meant that the internals library was built twice on each compilation run of the whole librsvg module: once for librsvg.so, and once for librsvg_crate.

Making programming errors a g_critical

While g_warning() means "something went wrong, but the program will continue", g_critical() means "there is a programming error". For historical reasons Glib does not abort when g_critical() is called, except by setting G_DEBUG=fatal-criticals, or by running a development version of Glib.

This commit turned warnings into critical errors when the C API was called out of order, by using a similar rsvg_g_critical_from_c() wrapper for a C macro.

Separating the C-callable code into yet another crate

To recapitulate, at that point we had this:

librsvg/
|  Cargo.toml - declares the Cargo workspace
|
+- rsvg_internals/
|  |  Cargo.toml
|  +- src/
|       c_api.rs - convert types and return values, call into implementation
|       handle.rs - actual implementation
|       *.rs - all the other internals
|
+- librsvg/
|    *.c - stub functions that call into Rust
|    rsvg-base.c - contains rsvg_g_warning_from_c() among others
|
+- librsvg_crate/
   |  Cargo.toml
   +- src/
   |    lib.rs - public Rust API
   +- tests/ - tests for the public Rust API
        *.rs

At this point c_api.rs with all the unsafe functions looked out of place. That code is only relevant to librsvg.so — the public C API —, not to the Rust API in librsvg_crate.

I started moving the C API glue to a separate librsvg_c_api crate that lives along with the C stubs:

+- librsvg/
|    *.c - stub functions that call into Rust
|    rsvg-base.c - contains rsvg_g_warning_from_c() among others
|    Cargo.toml
|    c_api.rs - what we had before

This made the dependencies look like the following:

      rsvg_internals
       ^           ^
       |             \
       |               \
librsvg_crate     librsvg_c_api
  (Rust API)             ^
                         |
                    librsvg.so
                      (C API)

And also, this made it possible to remove the configuration feature for rsvg_internals, since the code that calls rsvg_g_warning_from_c now lives in librsvg_c_api.

With that, rsvg_internals is compiled only once, as it should be.

This also helped clean up some code in the internals library. Deprecated functions that render SVGs directly to GdkPixbuf are now in librsvg_c_api and don't clutter the rsvg_internals library. All the GObject boilerplate is there as well now; rsvg_internals is mostly safe code except for the glue to libxml2.

Summary

It was useful to move all the code that dealt with incoming C types, our outgoing C return values and errors, into the same place, and separate it from the "pure Rust" code.

This took gradual refactoring and was not done in a single step, but it left the resulting Rust code rather nice and clean.

When we added a new public Rust API, we had to shuffle some code around that could only be linked in the context of a C library.

Compile-time configuration features are useful (like #ifdef in the C world), but they do cause double compilation if you need a C-internals and a Rust-internals library from the same code.

Having proper error reporting throughout the Rust code is a lot of work, but pretty much invaluable. The glue code to C can then convert and expose those errors as needed.

If you need both C and Rust APIs into the same code base, you may end up naturally using a facade pattern for each. It helps to gradually refactor the internals to be as "pure idiomatic Rust" as possible, while letting API idiosyncrasies bubble up to each individual facade.

the avatar of Kubic Project

Kubic with Kubernetes 1.17.0 released

Announcement

The Kubic Project is proud to announce that Snapshot 20200113 has just been released containing Kubernetes 1.17.0.

This is a particually exciting release with Cloud Provider Labels becoming a GA-status feature, and Volume Snapshotting now reaching Beta-status.

Release Notes are avaialble HERE.

Special Thanks

Special thanks to Sascha Grunert, Ralf Haferkamp and Michal Rostecki who all contributed to this release, either through debugging issues or by picking up other open Kubic tasks so other contributors could focus on this release.

Please look at our github projects board for an incomplete list of open tasks, everyone is free to help out so please join in!

Upgrade Steps

All newly deployed Kubic clusters will automatically be Kubernetes 1.17.0 from this point.

For existing clusters, you will need to follow the following steps after your nodes have transactionally-updated to 20200113 or later.

For a kubic-control cluster please run kubicctl upgrade.

For a kubeadm cluster you need to follow the following steps on your master node: kubectl drain <master-node-name> --ignore-daemonsets.

Then run kubeadm upgrade plan to show the planned upgrade from your current v1.16.x cluster to v1.17.0.

kubeadm upgrade apply v1.17.0 will then upgrade the cluster.

kubectl uncordon <master-node-name> will then make the node functional in your cluster again.

For then for all other nodes please repeat the above steps but using kubeadm upgrade node instead of kubeadm upgrade apply, starting with any additional master nodes if you have a multi-master cluster.

Thanks and have a lot of fun!

The Kubic Team

the avatar of openSUSE News

LibreOffice, Firefox, Curl Receive Updates in Tumbleweed

Several packages were updated this week for openSUSE Tumbleweed as was expected after the holiday season. Five snapshots of the rolling release have been delivered so far this week after passing the rigorous testing applied by openQA.

The releases are trending incredibly stable with trending or recorded ratings abovea 96 rating, according to the Tumbleweed snapshot reviewer.

The most recent snapshot, 20200112, updated Xfce desktop environment with an update for xfce4-session 4.14.1 and xfce4-settings 4.14.2. Various developer visible changes were made with Google’s 20200101 re2 library for regular expressions updates. GNOME’s application for managing images with a users Flickr account, frogr 1.6, removed the deprecated use of GTimeVal. The open source platform for the scale-out of public and private cloud storage, glusterfs 7.1, fixed storage rebalancing caused by an input error and fixed a memory leak in the glusterfsd process. ImageMagick version 7.0.9.14 optimized the special effects performance of Fx and virglrenderer 0.8.1, which is a project to investigate the possibility of creating a virtual 3D GPU for use inside qemu virtual machines to accelerate 3D rendering, added some patches. The snapshot continued to update packages for KDE Applications 19.12.1 that started in the 20200111 snapshot. Improvements to the scroll wheel speed was made for KDE’s Dolphin, the video editing software Kdenlive had multiple fixes and an adjustment for faster rendering, and obsolete code was removed from Applications’ diagram package umbrello. Most of the KDE Applications packages also updated the Copyright year to 2020.

In addition to the  KDE Applications 19.12.1 packages that began arriving in snapshot 20200111, KDE’s Plasma 5.17.5 also arrived in the snapshot. The updated Plasma fixed a regression in the “Port the pager applet away from QtWidgets” and fixed the drag from Dolphin to a virtual desktop switcher widget. The Plasma NetworkManager also had a fix for a crash when changing advanced IPv4 configurations. The much-anticipated fix for the security vulnerability in Firefox was made with the Mozilla update to Firefox 72.0.1; there were eight Common Vulnerabilities and Exposures (CVE) fixes in the update from the previous 71 version included in Tumbleweed, but the 72.0.1 fixed the bug that hackers could use to access a computer of anyone using the browser because of incorrect alias information in the IonMonkey JIT compiler. LibreOffice 6.4.0.1 added a patch to fix a button that allowed the wrong ordering of a Qt interface and curl 7.68.0 had a lengthy amount of fixes and changes to include adding a BearSSL vtls implementation for the Transport Layer Security (TLS). openSUSE’s snapper 0.8.8 version had a rewrite of a subpackage from Python to C++ and several YaST packages were updated, which included the fixing of an error during an upgrade if /var/lib/YaST2 was missing when using Btrfs.

Troubleshooting tool sysdig was updated in snapshot 20200110; it fixed a memory leak as well as updated the use of Kubernetes APIs to support version 1.16.vMany GNOME packages were updated to version 3.34.3 and the fwupd 1.3.6 package for updating firmware added a new plugin for working with embedded MultiMediaCard (eMMC) devices. A drop of python3-setuptools dependencies from rpm-build was made with the update of rpm 4.15.1 and Optimized Inner Loop Runtime Compiler (orc) 0.4.31 fixed various PowerPC issues.

Snapshots 20200109 and 20200108 had a minimal amount of package updates, but the Linux Kernel was updated to version 5.4.7 in the 20200108, which provided a large amount of updates from the previous 5.3.12 kernel Tumbleweed was running. Updates for Btrfs in the kernel were plentiful and there were about a handful of fixes in the kernel for IBM’s s390 and for the file system ext4.

a silhouette of a person's head and shoulders, used as a default avatar

Running for the openSUSE Board again or: reelect (Sarah);

I was in the openSUSE Board for 2 years in the past and I have enjoyed this time to bring along the openSUSE project.
I want to run for the openSUSE Board again after a short break about 1 year. I am happy that the existing openSUSE Board has proceeded my idea with the foundation so successfully. But I would be happy about being allowed to finalize this/ my topic together with the other Board Members as my old idea.
Additionally, I have watched the decreasing reputation. Public representations of openSUSE have been missing by the openSUSE Board in the last year. I would increase that on the same way I have done that at our university.

My main activity at openSUSE is the Coordination of the global Localization Team besides my studies. However, that is not the sole task. I contribute to QA, give presentations, update the wiki (English and German) and create PRs on Github. I would be able to contribute some code as a Computer Scientist, too. But SUSE does not want to see any Computer Scientists from our university. Therefore, my focus is on improving our study plan with open source development.

I love to represent openSUSE. So I have decided to run for reelection. 😉

Do you want to know more about my goals and thoughts? You can find more about my goals for these openSUSE Board elections on my openSUSE Board election platform.

  • Email: sarah.kriesch@opensuse.org
  • Blog: https://sarah-julia-kriesch.eu (my blog)
  • facebook: https://www.facebook.com/sarahjulia.kriesch
  • LinkedIn: https://www.linkedin.com/in/sarah-julia-kriesch-16874b82
  • Connect: https://connect.opensuse.org//pg/profile/AdaLovelace
  • I wish all candidates good luck, and hope that we‘ll see lots of voters!

    The post Running for the openSUSE Board again or: reelect (Sarah); first appeared on Sarah Julia Kriesch.

    the avatar of Nathan Wolf

    Solaar | Application for Logitech Unifying Receivers and Devices on openSUSE

    I recently purchased a new Logitech wireless keyboard for my kitchen computer because the Bluetooth keyboard I previously used was driving me nuts. Mostly for the keyboard layout and sometimes because it didn’t want to connect. Possibly due to hardware failure or bad design. It also doesn’t have media keys so I thought it best … Continue reading Solaar | Application for Logitech Unifying Receivers and Devices on openSUSE

    the avatar of Efstathios Iosifidis

    How to survive a health crisis during a FOSS conference

    How to survive a health crisis during a FOSS conference

    The title describes everything. This is not only for FOSS conferences but events in general. Attending a conference means to meeting friends (usually you meet once a year) and have fun in general.

    The organizers are responsible for everything that happens during the conference hours. We are grown people, so we have to be responsible for the rest of the day. Sometimes bad things might happen (bad: the critical meaning is health issues). Although the organizers aren't responsible for that, they are the key people, who know the system in their country and it's a good and human thing to help the person with the problem. Everyone wants to have fun and be happy at the end of the conference.

    Being an organizer and volunteer, I felt the frustration of having everything covered. I lived a couple of times the health crisis during the conference.

    Here are some points to cover before and during the conference. Please leave a comment if you want to share your experience.

    Before the conference:


    0. If the conference takes place within European Union, you can ask the European citizens to get the European Insurance Card. It doesn't cover everything, but at least you'll avoid some stupid bureaucracy. If they're not EU citizens or they don't have insurance, ask them to buy one for their trip. Usually, when you buy a plane ticket, they suggest buying medical insurance. Do it, because it will save you from a lot of trouble.

    1. During the online registration, ask for emergency contact (ask for a name and phone number). It will help you and doctors just in case they need the medical history of the person, allergies, etc.

    2. If they're not feeling comfortable providing their name and number, ask them to enable a feature of emergency call on their phone or even better to install a program with the emergency call and some medical history and their medication. Personally, I have the Medical ID (Free) installed (you can choose other). It will help the doctors because it has a field to add your medical history so they can treat you correctly.

    3. This might be very personal, but ask them to enter where they will stay. You might need to go to their hotel and bring a special medication they "forgot" to bring with them. And that leads to another issue. Ask them to bring the medication to the conference with them.

    4. During registration, it's good to have a field for write down a disease that the attendee feels comfortable sharing. There is a code of conduct committee that can guarantee this information. For example, if the attendee has epilepsy, it's good for the organizers to know and be prepared. Usually, people with epilepsy tell that information to their friends, so they won't be surprised just in case of a seizure.

    5. During registration, ask for special types of food. It's not the only religion that prohibits some kinds of food but there are some other categories such as vegan, vegetarian, gluten-free, etc. Some of them are just a way of life but others are health-related.

    6. Gather information and write on the website everything health-related that the attendees must know because usually there are some illnesses that come up after the attendees return back home. A special quote is if there are special vaccines that the attendees have to be vaccinated. It's better for the locals to write down

    7. Gather information and write on the website what everyone has to be careful about. Examples are, don't drink tap water, buy water from kiosks and that costs ..., don't walk alone after midnight at that place because you might get robbed, smoking pot is illegal, traffic police violations. Find out what the attendees need to know so they won't end up either at the hospital or police station.

    8. Every country has a different health system. Make sure that you find out the hospitals that accept emergencies and also the pharmacies that are open during the night. Make a list with details (address, phone number, specialties, etc) for the days of the conference. It's better not to use it rather not have it at all.

    9. Gather all addresses and phone numbers of embassies and consulates of the attendees. It's the place they have to call if they have a problem and there are people who speak the same language that can help them.

    10. On the name tags, write down the emergency phone numbers (like 112). In my country it's different for the police, fire department, ambulance. Also, buy a disposable cell phone number and put the number on the name tags, so attendees can call a local that knows the system and can help.

    11. Contact Red Cross volunteers. I guess there are different organizations for every country. You can search for their information. They might be present during the conference for free. They are people with jobs and they are volunteers, so if they cannot join you, ask them to train someone for some basics.

    12. Write a document "Protocol if we have to call the authorities". That includes everything from fire to ambulance and from the police to traffic police. Of course, you cannot include everything (because you never know) but at least you can act for some basic situations.


    During the conference:


    1. Keep a central booth for Red Cross volunteers. They usually carry a bag with heavy equipment. Ask them how they want to have communication for emergencies. Usually, many venues have defibrillators (I think it's called like that). So help them to find one. Ask the volunteers what else they need to operate. Don't forget they are volunteers, so please make sure they have free food and water, and maybe you can give them a T-shirt of your project for free.

    2. Make sure you have a private room that the Red Cross volunteers can examine someone. That can be an office. Nothing fancy. Usually, people don't like to be examined in public. Also, the Red Cross Volunteers are more concentrated when the place is quiet.

    3. If you don't have a Red Cross volunteer, try to train someone or find a key person (name and phone number) who knows CPR, how to use a defibrillator, etc. He might save a life before the ambulance arrives.

    4. Have an Emergency mobile number that an attendee can call you when he faces a problem. Have in mind that everything will happen during the night (because people tend to do stupid things during the night). So be prepared to go to the police (got robbed or had a fight with a local etc), go to a hospital (got hit by a local or got a bicycle and fell, etc). As I wrote, the organizers aren't responsible for what happens after the conference hours. But leaving someone who doesn't know how the foreign system works, will be bad for you and the organization in general. So make shifts for locals on-call :-)

    5. Try to pass everything as quietly as you can. It's not good to make a fuss out of the incident. The person with the problem doesn't like everyone to see and ask if he's OK or feel pity for him. That's why the private room helps.


    If you have an idea that I forgot, please leave a comment. Please share it with people that organize conferences because it might give them some tips.

    Some of the above ideas are overdone. It's better to be prepared rather than be stressed.
    I hope following all or some of the above will lead you to a successful conference.
    the avatar of Vojtěch Zeisek

    Low genetic differentiation despite high fragmentation in the endemic serpentinophyte Minuartia smejkalii (M. verna agg., Caryophyllaceae) revealed by RADSeq SNP markers

    Low genetic differentiation despite high fragmentation in the endemic serpentinophyte Minuartia smejkalii (M. verna agg., Caryophyllaceae) revealed by RADSeq SNP markers

    Minuartia smejkalii is an obligate serpentinophyte plant endemic to the Czech Republic. Since the 1960s, the species’ habitat has undergone strong human-mediated fragmentation, resulting in extinction of some populations and dramatic size reduction of the remaining populations. Thus, contrary to the typically stable serpentine habitats, M. smejkalii habitats underwent a recent and severe decline, which can exacerbate the effects of fragmentation on population genetic structure. We examined the genetic structure of all known M.

    vojta Po, 01/13/2020 - 10:46
    the avatar of Nathan Wolf

    the avatar of Nathan Wolf
    the avatar of Chun-Hung sakana Huang

    使用 Google Stackdriver Monitoring API Python Samples 監控 過去一週 CPU 小於 20% VM小記

    20191229

    使用 Google Stackdriver Monitoring API Python Samples 監控 過去一週 CPU 小於 20% VM小記

    OS: container with openSUSE Leap 15.1

    今天要來嘗試使用 Stackdriver monitor API 透過 python 列出過去一週 CPU 小於 20 % 的 VM

    環境使用之前建立好的 openSUSE Leap 15.1, 上面已經安裝好雲端的工具

    ==== 在主機上面 ====

    啟動 container

    > docker  run  -v  ~/.aws:/root/.aws -v  ~/.azure:/root/.azure  -v ~/.config/gcloud:/root/.config/gcloud  -v  ~/.ssh:/root/.ssh  -it  sakana/ansible_opensuse151  /bin/bash

    ==== 在 container 內 ====

    在這邊使用服務帳戶的方式來處理, 所以要先建立服務帳戶

    建立的方式有 2 種

    ==== 1. GCP Console 方式 ====

    登入 GCP console

    點選 導覽選單 -- > IAM 與管理 -- > 服務帳戶 

    點選 建立服務帳戶


    輸入帳戶名稱 -- > 建立

    角色的部分選取 監控/Monitoring 管理員  -- > 繼續


    點選 建立金鑰

    選取金鑰類型, 這邊我選 JSON  -- > 建立


    下載金鑰到本機

    點選 完成
    完成服務帳號建立以及金鑰建立

    ==== 2. 指令方式 ====

    參考官方網頁

    建立服務帳戶

    # gcloud iam service-accounts create test20200111 --project  YOUR_PROJECT_ID

    • test20200111 是服務帳戶名稱
    • YOUR_PROJECT_ID 請換成自己的 project id

    向服務帳戶授予權限

    用您的專案 ID 取代 [PROJECT_ID]
    #gcloud  projects  add-iam-policy-binding [PROJECT_ID] --member "serviceAccount:[NAME]@[PROJECT_ID].iam.gserviceaccount.com" --role "roles/monitoring.admin"

    • 所以這邊可以觀察到 監控管理員的權限是綁定在 project
    • roles/monitoring.admin 是觀察來的, 可以透過 #gcloud  projects  get-iam-policy [PROJECT_ID] 來看相關的權限

    另外網路上也有看到找出服務帳戶 Role 的過濾方式

    # gcloud projects get-iam-policy [PROJECT_ID] --flatten="bindings[].members" --filter="bindings.members:[NAME]@[PROJECT_ID].iam.gserviceaccount.com"

    ---
    bindings:
      members: serviceAccount:sakanatest-20200111-monitor@xxxxxxx.iam.gserviceaccount.com
      role: roles/monitoring.admin
    etag: BwWb1FtLJ4s=
    version: 1

    產生金鑰檔案

    用金鑰檔案的名稱取代 [FILE_NAME]
    #gcloud  iam  service-accounts  keys  create  [FILE_NAME].json --iam-account [NAME]@[PROJECT_ID].iam.gserviceaccount.com

    created key [5abac9d47cd2g96386a05bad735d241d59a00897] of type [json] as [/root/test20200111.json] for [test20200111@xxxxxxxxx.iam.gserviceaccount.com]

    • 用指令的好處是可以直接指定憑證的位置, 我在下指令的時候是用 /root/test20200111.json

    =================================

    服務帳戶與金鑰準備好了之後, 接下來就是取得範例程式


    更動的部分如下 ( 使用 diff 指令, 左邊是改寫, 右邊是 google 原來的範例 )

    > diff snippets.py snippets-orig.py 

    14,18d13
    < # 
    < # Edit by Max 2020/1/11
    < # 因應工作需求, 改寫 snippets.py
    < # File from https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/monitoring/api/v3/cloud-client
    119,130d113
    < # 改寫 list_time_series_aggregate
    < # 
    < # 這邊定義要查詢的區間
    < query_time = input("Please input your query time in seconds, for example, 7 Days is 604800:  ") or 604800
    < query_time_int=int(query_time) # 這邊將 query_time type 從 str 轉型為 int
    < print("")
    < cpu_threshold = input("Please input cpu usage under threshold, like 0.2 for 20%:  ") or 0.2
    < cpu_threshold_float = float(cpu_threshold)
    < print("")
    140c123
    <     interval.start_time.seconds = int(now - query_time_int) # 這邊用 query_time 來彈性調整
    ---
    >     interval.start_time.seconds = int(now - 3600)
    143c126
    <     aggregation.alignment_period.seconds = query_time_int  # 這邊用 query_time 來彈性調整
    ---
    >     aggregation.alignment_period.seconds = 1200  # 20 minutes
    154,160c137
    <       cpuUtil = result.points[0].value.double_value
    <       if cpuUtil < cpu_threshold_float: #需要判斷的 CPU threshold, 用 cpu_threshold 來彈性調整
    <           print("instance name:", result.metric.labels) # 列出 instance 名稱
    <           # instance_id 是放在 resource.labels 下, 以字典的方式儲存, 所以透過 ['KEY_NAME'] 取出
    <           print("instance id:", result.resource.labels['instance_id']) # 列出 instance id 來區別同樣名稱的 VM
    <           print("CPU utilization:", cpuUtil *100, "% \n") # 列出 cpu 使用量
    <     # print(result) 註解原來的方式
    ---
    >         print(result)

    接下來設定應用程式驗證作業需要的環境變數

    定義 project , 請帶入自己 project ID
    # export  GOOGLE_CLOUD_PROJECT=[PROJECT_ID]

    定義剛剛的 json 檔案的路徑, 例如 /root/test20200111.json
    # export GOOGLE_APPLICATION_CREDENTIALS="[path/key-name.json]"


    確認有定義完成

    # echo  $GOOGLE_CLOUD_PROJECT
    # echo  $GOOGLE_APPLICATION_CREDENTIALS

    使用 pip 安裝 python 相依性的套件
    # pip  install  -r  requirements.txt

    執行剛剛下載修改的 snippet.py

    # python3  snippets.py  list-time-series-aggregate

    Please input your query time in seconds, for example, 7 Days is 604800:  604800

    Please input cpu usage under threshold, like 0.2 for 20%:  0.3

    instance name: {'instance_name': 'test'}
    instance id: 3255911329394624968
    CPU utilization: 17.11500122 % 

    instance name: {'instance_name': 'test2'}
    instance id: 3713200083697575388
    CPU utilization: 0.6797669215641027 % 

    instance name: {'instance_name': 'test'}
    instance id: 8590409858061083796
    CPU utilization: 0.6412688036029409 % 

    • 這邊我改成互動的方式, 輸入要查詢的區間, 使用秒數爲單位
    • 輸入CPU 使用量的門檻值, 如果要列出 20% 以下就輸入 0.2

    這樣就可以列出過去一週 CPU 小於 20 % 的 VM :)

    另外補充, 當服務帳戶刪除的時候, 剛 members 與 Roles 還是在 project 上面, 所以要記得移除權限

    例如
    # gcloud projects remove-iam-policy-binding [PROJECT_ID] --member=serviceAccount:[NAME]@[PROJECT_ID].iam.gserviceaccount.com --role=roles/monitoring.admin

    這樣又朝向 GCP 邁進一步

    ~ enjoy  it

    Reference